<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>cloudonaut</title>
    <link>https://cloudonaut.io/</link>
    <description>Launchpad for Amazon Web Services</description>
    <language>en</language>
    <copyright>All rights reserved 2026, Andreas and Michael Wittig</copyright>
    <lastBuildDate>Fri, 13 Mar 2026 06:00:00 GMT</lastBuildDate>
    <generator>Hexo</generator>
    <atom:link href="https://cloudonaut.io/feed/rss.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>AWS European Sovereign Cloud (EUSC) – a field report</title>
      <link>https://cloudonaut.io/aws-european-sovereign-cloud-field-report/</link>
      <description>What is it like to deploy a workload in the AWS European Sovereign Cloud (EUSC)? Learn about obstacles like missing services and features.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/eusc/">eusc</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-european-sovereign-cloud-field-report/</guid>
      <pubDate>Fri, 13 Mar 2026 06:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Over the past weeks, I’ve been working on bringing our products to the AWS European Sovereign Cloud, which <a href="https://aws.amazon.com/blogs/aws/opening-the-aws-european-sovereign-cloud/" target="_blank" rel="noopener">launched in January 2026</a>. With <a href="https://bucketav.com/blog/virus-scan-for-amazon-s3-in-aws-european-sovereign-cloud/" target="_blank" rel="noopener">yesterday’s release,</a> we were the 2nd company to make it into the AWS Marketplace in the European Sovereign Cloud. In the following, I will share my learnings and thoughts.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2026/03/europe@730w.webp 730w, /images/2026/03/europe@730w2x.webp 1460w, /images/2026/03/europe@610w.webp 610w, /images/2026/03/europe@610w2x.webp 1220w, /images/2026/03/europe@450w.webp 450w, /images/2026/03/europe@450w2x.webp 900w, /images/2026/03/europe@330w.webp 330w, /images/2026/03/europe@330w2x.webp 660w, /images/2026/03/europe@545w.webp 545w, /images/2026/03/europe@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2026/03/europe@730w.jpg 730w, /images/2026/03/europe@730w2x.jpg 1460w, /images/2026/03/europe@610w.jpg 610w, /images/2026/03/europe@610w2x.jpg 1220w, /images/2026/03/europe@450w.jpg 450w, /images/2026/03/europe@450w2x.jpg 900w, /images/2026/03/europe@330w.jpg 330w, /images/2026/03/europe@330w2x.jpg 660w, /images/2026/03/europe@545w.jpg 545w, /images/2026/03/europe@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2026/03/europe.jpg" alt="AWS European Sovereign Cloud (EUSC) – a field report" title="AWS European Sovereign Cloud (EUSC) – a field report"></picture></p><h2 id="What’s-the-AWS-European-Sovereign-Cloud-EUSC"><a href="#What’s-the-AWS-European-Sovereign-Cloud-EUSC" class="headerlink" title="What’s the AWS European Sovereign Cloud (EUSC)?"></a>What’s the AWS European Sovereign Cloud (EUSC)?</h2><p>The AWS European Sovereign Cloud (<code>aws-eusc</code>) is an independent partition similar to the AWS GovCloud (<code>aws-us-gov</code>) and AWS China (<code>aws-cn</code>). In contrast to the GovCloud, the EUSC is open for all customers. And unlike AWS China, EUSC is not operated by a third party but by AWS itself.</p><p>Today, the EUSC consists of a single region in Brandenburg called <code>eusc-de-east-1</code>. The EUSC is designed to be independent from the US from both a technical and organizational perspective.</p><p>From a technical point of view, AWS did a great job. For example, AWS built a separate identity and access management (IAM), domain name system (DNS), trust service provider, and much more. All services are available under separate  domains like <code>amazonaws.eu</code> and <code>amazonaws-eusc.eu</code>. Even the documentation for the EUSC stands on its own.</p><p>Besides that, AWS operates the EUSC exclusively by EU residents: operations, technical support, and customer service.<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup> Although AWS has also established companies under German law, these are subsidiaries of the US company. Critics therefore question its legal independence.</p><h2 id="Why-does-widdix-go-to-AWS-European-Sovereign-Cloud"><a href="#Why-does-widdix-go-to-AWS-European-Sovereign-Cloud" class="headerlink" title="Why does widdix go to AWS European Sovereign Cloud?"></a>Why does widdix go to AWS European Sovereign Cloud?</h2><p>We decided to bring our products to the EUSC for three reasons:</p><ol><li>The AWS EUSC opens up a new market. We want to leverage our advantages as first movers. Many of our competitors are not yet represented in the EUSC.</li><li>We like to try new things. It is a challenge to deal with the shortcomings of new services. And you can write wonderful rants.</li><li>In politically unstable times, we support the goal of establishing a sovereign European cloud infrastructure.</li></ol><h2 id="What-did-we-learn-about-the-AWS-European-Sovereign-Cloud"><a href="#What-did-we-learn-about-the-AWS-European-Sovereign-Cloud" class="headerlink" title="What did we learn about the AWS European Sovereign Cloud?"></a>What did we learn about the AWS European Sovereign Cloud?</h2><p>We opened our first EUSC account in February. Here is what we learned from practice.</p><h3 id="Missing-services-and-features"><a href="#Missing-services-and-features" class="headerlink" title="Missing services and features"></a>Missing services and features</h3><p>Not all AWS services are yet available in the AWS European Sovereign Cloud (EUSC). For example, the following services are not yet available: CodeCommit, CodeBuild, CodePipeline, Elastic Beanstalk, IAM Identity Center, CloudFront, Inspector, MQ, Managed Grafana, Managed Prometheus, and Security Hub. Check out the <a href="https://builder.aws.com/build/capabilities/explore" target="_blank" rel="noopener">AWS Capabilities by Region</a> overview for details.</p><p>It is annoying but understandable that not all services are available at the launch of the <code>eusc-de-east-1</code> region. What totally surprised us is that even for services that are actually available, not all features are provided.</p><ul><li><strong>EC2 Instance Connect</strong> to temporarily grant SSH access is not available.</li><li><strong>IAM Identity Center</strong> is missing, which makes access management of multiple accounts much harder.</li><li><strong>AWS Marketplace</strong> does not support AMI+CloudFormation products nor free trials for AMI products. Also, metered billing is missing.</li></ul><h3 id="Two-instead-of-three-availability-zones"><a href="#Two-instead-of-three-availability-zones" class="headerlink" title="Two instead of three availability zones"></a>Two instead of three availability zones</h3><p>It is worth noting that the <code>eusc-de-east-1</code> region consists of only two availability zones. We actually thought that those days were behind us and that all regions would provide at least three AZs. But here, it seems that a mistake from the past is repeating itself. Anyway, it’s nothing that can’t be solved with another condition in Infrastructure as Code.</p><h3 id="So-so-tooling-support"><a href="#So-so-tooling-support" class="headerlink" title="So-so tooling support"></a>So-so tooling support</h3><p>Working with the new AWS partition <code>aws-eusc</code> requires all tools to send requests to different API endpoints due to a separate domain name. Therefore, you need to update tools, like the AWS CLI or Terraform, for example, to the latest version. In case you are using AWS SDKs, you need to upgrade to the latest version as well. Unfortunately, not all tools have shipped a new version that supports the EUSC already. Sometimes, there is a possibility to override the API endpoint with a configuration option.</p><h3 id="Increasing-service-quotas-requires-support-tickets"><a href="#Increasing-service-quotas-requires-support-tickets" class="headerlink" title="Increasing service quotas requires support tickets"></a>Increasing service quotas requires support tickets</h3><p>To ensure the high quality of our solutions, we rely on detailed integration tests. A high degree of parallelization of the tests requires increasing AWS service quotas. We have learned that many quotas, which can normally be increased automatically at AWS, require a support ticket at EUSC. This has significantly increased the amount of work for us.</p><h3 id="AWS-Support"><a href="#AWS-Support" class="headerlink" title="AWS Support"></a>AWS Support</h3><p>Speaking of support tickets, be prepared for the fact that the plans for AWS support in the EUSC are different.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">Business+ (AWS)</th><th style="text-align:right">Business (AWS EUSC)</th></tr></thead><tbody><tr><td>Minimum spend per month</td><td style="text-align:right">29 USD</td><td style="text-align:right">86 EUR</td></tr><tr><td>Pricing based on total spending</td><td style="text-align:right">3% to 9%</td><td style="text-align:right">3% to 9%</td></tr></tbody></table><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">Enterprise (AWS)</th><th style="text-align:right">Enterprise (AWS EUSC)</th></tr></thead><tbody><tr><td>Minimum spend per month</td><td style="text-align:right">5,000 USD</td><td style="text-align:right">4,300 EUR</td></tr><tr><td>Pricing based on total spending</td><td style="text-align:right">3% to 10%</td><td style="text-align:right">3% to 10%</td></tr></tbody></table><p>To subscribe to the business support plan in the EUSC, you need to open a support ticket. The Enterprise plan is only available through your account manager.</p><p>From my experience I can tell that it takes the support team in EUSC a little longer to respond. The team appears to be quite small; so far I’ve talked to the same two people for all my support issues.</p><p>In summary, bringing our first product to the AWS European Sovereign Cloud was a journey with obstacles. I hope our experiences help you to be better prepared for the EUSC.</p><h2 id="What’s-ahead-for-the-AWS-European-Sovereign-Cloud"><a href="#What’s-ahead-for-the-AWS-European-Sovereign-Cloud" class="headerlink" title="What’s ahead for the AWS European Sovereign Cloud?"></a>What’s ahead for the AWS European Sovereign Cloud?</h2><p>It remains to be seen how customers will respond to the AWS European Sovereign Cloud. Given the current political situation, it is questionable whether the target group of government organizations and regulated industries will embrace the AWS Cloud. We are watching the market closely.</p><p>Another question is how AWS will deal with the organizational divide between AWS in the US and AWS in Europe. How will the teams work together? What happens when troubleshooting becomes complicated? And how will further development succeed?</p><p>What is your experience with the AWS European Cloud? Let me know!</p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. <a href="https://www.aboutamazon.eu/news/aws/aws-european-sovereign-cloud-to-be-operated-by-eu-citizens">AWS European Sovereign Cloud to be operated by EU citizens</a> <a href="#fnref:1" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to Monitor and Auto Adjust AWS Service Quotas?</title>
      <link>https://cloudonaut.io/how-to-monitor-and-auto-adjust-aws-service-quotas/</link>
      <description>Enable Service Quotas Automatic Management to monitor and auto-adjust AWS service quotas to avoid downtimes.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/quota/">quota</category>
      <category domain="https://cloudonaut.io/tag/health/">health</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-monitor-and-auto-adjust-aws-service-quotas/</guid>
      <pubDate>Wed, 28 Jan 2026 06:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Running into AWS service quota limits can cause troubles or even downtimes. Therefore, the announcement of Service Quotas Automatic Management in October 2025 caught my attention. My idea was to add service quota observation to our <a href="https://marbot.io/" target="_blank" rel="noopener">event-driven monitoring solution for AWS called marbot</a>. Here is what I learned from taking a look into the details of Service Quotas Automatic Management.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2026/01/limits@730w.webp 730w, /images/2026/01/limits@730w2x.webp 1460w, /images/2026/01/limits@610w.webp 610w, /images/2026/01/limits@610w2x.webp 1220w, /images/2026/01/limits@450w.webp 450w, /images/2026/01/limits@450w2x.webp 900w, /images/2026/01/limits@330w.webp 330w, /images/2026/01/limits@330w2x.webp 660w, /images/2026/01/limits@545w.webp 545w, /images/2026/01/limits@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2026/01/limits@730w.jpg 730w, /images/2026/01/limits@730w2x.jpg 1460w, /images/2026/01/limits@610w.jpg 610w, /images/2026/01/limits@610w2x.jpg 1220w, /images/2026/01/limits@450w.jpg 450w, /images/2026/01/limits@450w2x.jpg 900w, /images/2026/01/limits@330w.jpg 330w, /images/2026/01/limits@330w2x.jpg 660w, /images/2026/01/limits@545w.jpg 545w, /images/2026/01/limits@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2026/01/limits.jpg" alt="How to Monitor and Auto Adjust AWS Service Quotas?" title="How to Monitor and Auto Adjust AWS Service Quotas?"></picture></p><h2 id="What-is-Service-Quotas-Automatic-Management"><a href="#What-is-Service-Quotas-Automatic-Management" class="headerlink" title="What is Service Quotas Automatic Management?"></a>What is Service Quotas Automatic Management?</h2><p><a href="https://aws.amazon.com/about-aws/whats-new/2025/10/automatic-quota-management-service-quotas/" target="_blank" rel="noopener">AWS released Service Quotas Automatic Management on Oct 7,  2025</a> to provide a solution for monitoring and automatically adjusting service quotas.</p><p><strong>Service Quotas Automatic Management</strong> comes with the following features.</p><ul><li>Send notifications about service quotas<ul><li>80% utilization</li><li>95% utilization</li></ul></li><li>Auto-adjust service quota</li></ul><h2 id="How-to-enable-Service-Quotas-Automatic-Management"><a href="#How-to-enable-Service-Quotas-Automatic-Management" class="headerlink" title="How to enable Service Quotas Automatic Management?"></a>How to enable Service Quotas Automatic Management?</h2><p>Unfortunately, neither CloudFormation&#x2F;CDK nor Terraform supports enabling automatic management for service quotas.</p><p>The following command shows how to enable notifications when reaching service quotas with the help of the AWS CLI. Alternatively, you could use the AWS Management Console as well as the API.</p><figure class="highlight scss"><table><tr><td class="code"><pre><span class="line">aws service-quotas start-auto-management  <span class="attr">--opt-in-level</span> ACCOUNT <span class="attr">--opt-in-type</span> NotifyOnly</span><br></pre></td></tr></table></figure><p>In case you want to automatically increase service quotas, use the opt-in type <code>NotifyAndAdjust</code>.</p><figure class="highlight scss"><table><tr><td class="code"><pre><span class="line">aws service-quotas start-auto-management  <span class="attr">--opt-in-level</span> ACCOUNT <span class="attr">--opt-in-type</span> NotifyAndAdjust</span><br></pre></td></tr></table></figure><h2 id="How-to-receive-notifications-when-service-quotas-are-close"><a href="#How-to-receive-notifications-when-service-quotas-are-close" class="headerlink" title="How to receive notifications when service quotas are close?"></a>How to receive notifications when service quotas are close?</h2><p>Service Quotas Automatic Management supports the following services to deliver notifications.</p><ul><li>AWS Health (Default)</li><li>AWS User Notifications (Default)</li><li>Email</li><li>AWS Console Mobile App</li><li>Amazon Q Developer chat</li></ul><p>The following screenshot shows a service quota notification delivered by the AWS User Notifications service.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2026/01/user-notifications@730w.webp 730w, /images/2026/01/user-notifications@730w2x.webp 1460w, /images/2026/01/user-notifications@610w.webp 610w, /images/2026/01/user-notifications@610w2x.webp 1220w, /images/2026/01/user-notifications@450w.webp 450w, /images/2026/01/user-notifications@450w2x.webp 900w, /images/2026/01/user-notifications@330w.webp 330w, /images/2026/01/user-notifications@330w2x.webp 660w, /images/2026/01/user-notifications@545w.webp 545w, /images/2026/01/user-notifications@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2026/01/user-notifications@730w.png 730w, /images/2026/01/user-notifications@730w2x.png 1460w, /images/2026/01/user-notifications@610w.png 610w, /images/2026/01/user-notifications@610w2x.png 1220w, /images/2026/01/user-notifications@450w.png 450w, /images/2026/01/user-notifications@450w2x.png 900w, /images/2026/01/user-notifications@330w.png 330w, /images/2026/01/user-notifications@330w2x.png 660w, /images/2026/01/user-notifications@545w.png 545w, /images/2026/01/user-notifications@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2026/01/user-notifications.png" alt="Service quota notification delivered by the AWS User Notifications service" title="Service quota notification delivered by the AWS User Notifications service"></picture></p><p>Also, it is possible to subscribe to events from Service Quotas Automatic Management via AWS EventBridge. The source of the events is <code>aws.health</code>. Use the <code>eventTypeCode</code> for more detailed filtering.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;source&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;aws.health&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;detail-type&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;AWS Health Event&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;detail&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;service&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;SERVICEQUOTAS&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eventTypeCode&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;AWS_SERVICEQUOTAS_THRESHOLD_BREACH&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;AWS_SERVICEQUOTAS_INCREASE_REQUEST_FAILED&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;AWS_SERVICEQUOTAS_APPROACHING_THRESHOLD&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eventTypeCategory&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;accountNotification&quot;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>The following snippet shows an example of a Service Quotas Automatic Management event.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;account&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1122334455566&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;detail&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;actionability&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ACTION_REQUIRED&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;affectedAccount&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1122334455566&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;affectedEntities&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;entityValue&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Service: dynamodb | Quota Name: Maximum number of tables | Utilization: 82.56%&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;lastUpdatedTime&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Tue, 13 Jan 2026 10:10:34 GMT&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;backupEvent&quot;</span><span class="punctuation">:</span> <span class="string">&quot;false&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;communicationId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;c7f878c0ef502ffb41c58f8c0de6c733ddbd62fcf82d3891a296512bbccae440-1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;endTime&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Tue, 20 Jan 2026 10:10:33 GMT&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eventArn&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:health:eu-west-1::event/SERVICEQUOTAS/AWS_SERVICEQUOTAS_APPROACHING_THRESHOLD/AWS_SERVICEQUOTAS_APPROACHING_THRESHOLD-DUB-1768299033803&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eventDescription&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;language&quot;</span><span class="punctuation">:</span> <span class="string">&quot;en_US&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;latestDescription&quot;</span><span class="punctuation">:</span> <span class="string">&quot;You are receiving this message because you opted to receive notifications from Service Quotas when utilization exceeds (80/95)% threshold.\\n\\nAt Tue, 13 Jan 2026 10:10:33 GMT, we detected that your AWS account is approaching the utilization threshold for one or more service quotas.\\n\\nA list of your affected service quota(s) can be found in the \\\&quot;Affected resources\\\&quot; tab of your AWS Health Dashboard under the format \\\&quot;Service | Quota Name | Utilization Percentage\\\&quot;.\\n\\nTo proactively prevent any potential service disruption, we recommend:\\n1. Reviewing your current resource usage and growth patterns\\n2. Requesting a quota increase if needed via the Service Quotas console\\n\\nYou can manage your service quotas through the Service Quotas console (1) or APIs (2).\\n\\nIf you have any questions or concerns, please contact the AWS Support Team (3).\\n\\n(1) https://console.aws.amazon.com/servicequotas\\n(2) https://docs.aws.amazon.com/servicequotas/2019-06-24/apireference/API_Operations.html\\n(3) https://aws.amazon.com/support&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eventRegion&quot;</span><span class="punctuation">:</span> <span class="string">&quot;eu-west-1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eventScopeCode&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ACCOUNT_SPECIFIC&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eventTypeCategory&quot;</span><span class="punctuation">:</span> <span class="string">&quot;accountNotification&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eventTypeCode&quot;</span><span class="punctuation">:</span> <span class="string">&quot;AWS_SERVICEQUOTAS_APPROACHING_THRESHOLD&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lastUpdatedTime&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Tue, 13 Jan 2026 10:10:34 GMT&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;page&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;personas&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;OPERATIONS&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;service&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SERVICEQUOTAS&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;startTime&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Tue, 13 Jan 2026 10:10:33 GMT&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;statusCode&quot;</span><span class="punctuation">:</span> <span class="string">&quot;open&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;totalPages&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;detail-type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;AWS Health Event&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;929e5b05-48f3-8150-59ee-a98f2555dc90&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;region&quot;</span><span class="punctuation">:</span> <span class="string">&quot;eu-west-1&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;resources&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;Service: dynamodb | Quota Name: Maximum number of tables | Utilization: 82.56%&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;source&quot;</span><span class="punctuation">:</span> <span class="string">&quot;aws.health&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;time&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2026-01-13T10:11:16Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>It’s a pity that the event is not very well designed. For example, it should include the quote code and service code, as this would allow you to call the <a href="https://docs.aws.amazon.com/servicequotas/2019-06-24/apireference/API_RequestServiceQuotaIncrease.html" target="_blank" rel="noopener"><code>RequestServiceQuotaIncrease</code> API action</a> automatically. Instead, all we get from the event details is the affected resources—for example <code>Service: dynamodb | Quota Name: Maximum number of tables | Utilization: 82.56%</code> - and an ID&#x2F;link to the AWS Health notification.</p><p>Also, AWS not only sends a notification once for getting close to or breaching a service quota. Instead, AWS repeats sending a notification every 24 hours. So you need to implement deduplication to avoid notifications piling up.</p><h2 id="How-to-increase-AWS-service-quotas-automatically"><a href="#How-to-increase-AWS-service-quotas-automatically" class="headerlink" title="How to increase AWS service quotas automatically?"></a>How to increase AWS service quotas automatically?</h2><p>Service Quotas Automatic Management comes with the ability to auto-adjust service quotas. Unlike manual quota increases, auto-adjust requests are handled through a specialized, automated workflow that does not require an AWS Support case.</p><p>In case a quota increase fails, you will receive a notification, but specific rejection details aren’t available.</p><p>Unfortunately, the feature is not working correctly. For testing, I created 50 AppSync GraphQL APIs to reach the service quota. However, the auto-adjustment did not work. I’m still waiting for a response from the AWS service team.</p><h2 id="Limitations-of-Service-Quotas-Automatic-Management"><a href="#Limitations-of-Service-Quotas-Automatic-Management" class="headerlink" title="Limitations of Service Quotas Automatic Management"></a>Limitations of Service Quotas Automatic Management</h2><p>Even though you are using Service Quotas Automatic Management, you might run into issues caused by reaching service quotas.</p><p>First, Service Quotas Automatic Management does not support all quotas. Monitoring and notifications are only available for a subset of all service quotas. And only a subset of that supports auto-adjust.</p><p>To get a list of all supported quotas, go to the tab <strong>Automatic Management</strong> in the Service Quota management console and press the <strong>View supported quotas</strong> button.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2026/01/supported-quotas-button@730w.webp 730w, /images/2026/01/supported-quotas-button@730w2x.webp 1460w, /images/2026/01/supported-quotas-button@610w.webp 610w, /images/2026/01/supported-quotas-button@610w2x.webp 1220w, /images/2026/01/supported-quotas-button@450w.webp 450w, /images/2026/01/supported-quotas-button@450w2x.webp 900w, /images/2026/01/supported-quotas-button@330w.webp 330w, /images/2026/01/supported-quotas-button@330w2x.webp 660w, /images/2026/01/supported-quotas-button@545w.webp 545w, /images/2026/01/supported-quotas-button@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2026/01/supported-quotas-button@730w.png 730w, /images/2026/01/supported-quotas-button@730w2x.png 1460w, /images/2026/01/supported-quotas-button@610w.png 610w, /images/2026/01/supported-quotas-button@610w2x.png 1220w, /images/2026/01/supported-quotas-button@450w.png 450w, /images/2026/01/supported-quotas-button@450w2x.png 900w, /images/2026/01/supported-quotas-button@330w.png 330w, /images/2026/01/supported-quotas-button@330w2x.png 660w, /images/2026/01/supported-quotas-button@545w.png 545w, /images/2026/01/supported-quotas-button@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2026/01/supported-quotas-button.png" alt="Show supported quotas" title="Show supported quotas"></picture></p><p>For example, Automatic Management supports only three of the DynamoDB quotas. And auto-adjusting is not available at all for DynamoDB quotas.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2026/01/supported-quotas-dynamodb@730w.webp 730w, /images/2026/01/supported-quotas-dynamodb@730w2x.webp 1460w, /images/2026/01/supported-quotas-dynamodb@610w.webp 610w, /images/2026/01/supported-quotas-dynamodb@610w2x.webp 1220w, /images/2026/01/supported-quotas-dynamodb@450w.webp 450w, /images/2026/01/supported-quotas-dynamodb@450w2x.webp 900w, /images/2026/01/supported-quotas-dynamodb@330w.webp 330w, /images/2026/01/supported-quotas-dynamodb@330w2x.webp 660w, /images/2026/01/supported-quotas-dynamodb@545w.webp 545w, /images/2026/01/supported-quotas-dynamodb@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2026/01/supported-quotas-dynamodb@730w.png 730w, /images/2026/01/supported-quotas-dynamodb@730w2x.png 1460w, /images/2026/01/supported-quotas-dynamodb@610w.png 610w, /images/2026/01/supported-quotas-dynamodb@610w2x.png 1220w, /images/2026/01/supported-quotas-dynamodb@450w.png 450w, /images/2026/01/supported-quotas-dynamodb@450w2x.png 900w, /images/2026/01/supported-quotas-dynamodb@330w.png 330w, /images/2026/01/supported-quotas-dynamodb@330w2x.png 660w, /images/2026/01/supported-quotas-dynamodb@545w.png 545w, /images/2026/01/supported-quotas-dynamodb@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2026/01/supported-quotas-dynamodb.png" alt="Automatic Management does not support all DynamoDB quotas, for example." title="Automatic Management does not support all DynamoDB quotas, for example."></picture></p><p>Second, it takes between one and three hours until Automatic Management detects that a service quota is reached. The relatively long response time can lead to problems occurring during operation even before this point.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>The monitoring of service quotes has become a lot easier with <strong>Service Quotas Automatic Management</strong>. Notifications allow you to increase service quotas before they affect workloads. The ability to auto-adjust quotas is even more convenient. Unfortunately, the usefulness of both the notifications and the auto-adjustment is limited, as not all quotas are supported. So you cannot rely on the solution.</p><p>By the way, <a href="https://marbot.io/" target="_blank" rel="noopener">marbot our solution for AWS monitoring, already supports service quota notifications</a>, as shown in the following screenshot. Did you know that marbot event dedouplicate events? Quite helpful, as Service Quotas Automatic Management sends the same notifications multiple times.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2026/01/service-quotas-marbot@730w.webp 730w, /images/2026/01/service-quotas-marbot@730w2x.webp 1460w, /images/2026/01/service-quotas-marbot@610w.webp 610w, /images/2026/01/service-quotas-marbot@610w2x.webp 1220w, /images/2026/01/service-quotas-marbot@450w.webp 450w, /images/2026/01/service-quotas-marbot@450w2x.webp 900w, /images/2026/01/service-quotas-marbot@330w.webp 330w, /images/2026/01/service-quotas-marbot@330w2x.webp 660w, /images/2026/01/service-quotas-marbot@545w.webp 545w, /images/2026/01/service-quotas-marbot@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2026/01/service-quotas-marbot@730w.png 730w, /images/2026/01/service-quotas-marbot@730w2x.png 1460w, /images/2026/01/service-quotas-marbot@610w.png 610w, /images/2026/01/service-quotas-marbot@610w2x.png 1220w, /images/2026/01/service-quotas-marbot@450w.png 450w, /images/2026/01/service-quotas-marbot@450w2x.png 900w, /images/2026/01/service-quotas-marbot@330w.png 330w, /images/2026/01/service-quotas-marbot@330w2x.png 660w, /images/2026/01/service-quotas-marbot@545w.png 545w, /images/2026/01/service-quotas-marbot@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2026/01/service-quotas-marbot.png" alt="marbot supports Service Quotas Automatic Management" title="marbot supports Service Quotas Automatic Management"></picture></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS CloudWatch Dashboard Cost Efficiency: Logs vs. Custom Metrics</title>
      <link>https://cloudonaut.io/aws-cloudwatch-dashboard-cost-efficiency-logs-vs-custom-metrics/</link>
      <description>Stop paying $1.19 per metric. We discovered that switching our CloudWatch dashboards from custom metrics to logs dropped costs to just $0.07 per metric. Here is how we did it.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-cloudwatch-dashboard-cost-efficiency-logs-vs-custom-metrics/</guid>
      <pubDate>Wed, 26 Nov 2025 06:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Setting up a CloudWatch dashboard is a great way to share insights into the state of a system. Recently, I was working on improving the dashboard for <a href="https://hyperenv.com/" target="_blank" rel="noopener">HyperEnv, our solution to deploy and manage self-hosted runners for GitHub Actions on AWS</a>. Up until now, we used custom metrics to collect data from the system and display it on the dashboard. However, I was somewhat annoyed by the high costs of this approach. Therefore, I was looking for a cost-effective alternative.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/11/dashboard@730w.webp 730w, /images/2025/11/dashboard@730w2x.webp 1460w, /images/2025/11/dashboard@610w.webp 610w, /images/2025/11/dashboard@610w2x.webp 1220w, /images/2025/11/dashboard@450w.webp 450w, /images/2025/11/dashboard@450w2x.webp 900w, /images/2025/11/dashboard@330w.webp 330w, /images/2025/11/dashboard@330w2x.webp 660w, /images/2025/11/dashboard@545w.webp 545w, /images/2025/11/dashboard@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/11/dashboard@730w.jpg 730w, /images/2025/11/dashboard@730w2x.jpg 1460w, /images/2025/11/dashboard@610w.jpg 610w, /images/2025/11/dashboard@610w2x.jpg 1220w, /images/2025/11/dashboard@450w.jpg 450w, /images/2025/11/dashboard@450w2x.jpg 900w, /images/2025/11/dashboard@330w.jpg 330w, /images/2025/11/dashboard@330w2x.jpg 660w, /images/2025/11/dashboard@545w.jpg 545w, /images/2025/11/dashboard@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/11/dashboard.jpg" alt="AWS CloudWatch Dashboard Cost Efficiency: Logs vs. Custom Metrics" title="AWS CloudWatch Dashboard Cost Efficiency: Logs vs. Custom Metrics"></picture></p><p>Important to know, a CloudWatch dashboard supports the following internal data sources out of the box:</p><ul><li>CloudWatch metrics</li><li>CloudWatch logs</li><li>CloudWatch alarms</li></ul><p>The possibility to add charts not based on metrics but on logs to a dashboard caught my attention.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/11/cloudwatch-dashboard-example@730w.webp 730w, /images/2025/11/cloudwatch-dashboard-example@730w2x.webp 1460w, /images/2025/11/cloudwatch-dashboard-example@610w.webp 610w, /images/2025/11/cloudwatch-dashboard-example@610w2x.webp 1220w, /images/2025/11/cloudwatch-dashboard-example@450w.webp 450w, /images/2025/11/cloudwatch-dashboard-example@450w2x.webp 900w, /images/2025/11/cloudwatch-dashboard-example@330w.webp 330w, /images/2025/11/cloudwatch-dashboard-example@330w2x.webp 660w, /images/2025/11/cloudwatch-dashboard-example@545w.webp 545w, /images/2025/11/cloudwatch-dashboard-example@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/11/cloudwatch-dashboard-example@730w.png 730w, /images/2025/11/cloudwatch-dashboard-example@730w2x.png 1460w, /images/2025/11/cloudwatch-dashboard-example@610w.png 610w, /images/2025/11/cloudwatch-dashboard-example@610w2x.png 1220w, /images/2025/11/cloudwatch-dashboard-example@450w.png 450w, /images/2025/11/cloudwatch-dashboard-example@450w2x.png 900w, /images/2025/11/cloudwatch-dashboard-example@330w.png 330w, /images/2025/11/cloudwatch-dashboard-example@330w2x.png 660w, /images/2025/11/cloudwatch-dashboard-example@545w.png 545w, /images/2025/11/cloudwatch-dashboard-example@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/11/cloudwatch-dashboard-example.png" alt="CloudWatch dashboard with widgets based on metrics, logs, and alarms" title="CloudWatch dashboard with widgets based on metrics, logs, and alarms"></picture></p><p>First, I did the math to compare the costs between custom metrics and logs.</p><h2 id="CloudWatch-custom-metrics-pricing-example"><a href="#CloudWatch-custom-metrics-pricing-example" class="headerlink" title="CloudWatch custom metrics pricing example"></a>CloudWatch custom metrics pricing example</h2><p>AWS charges $0.30 per custom metric per month.</p><p>Moreover, AWS charges $0.01 per 1,000 <code>PutMetricData</code> requests. Let’s assume we want to record a metric value every 30 seconds.</p><figure class="highlight basic"><table><tr><td class="code"><pre><span class="line"><span class="symbol">2 </span>* <span class="number">60</span> * <span class="number">24</span> * <span class="number">30</span> = <span class="number">86</span>,<span class="number">400</span> PutMetricData requests per month = $<span class="number">0.86</span> per month</span><br></pre></td></tr></table></figure><p>That’s $0.86 for ingesting metric data per month.</p><p>When displaying custom metrics on a dashboard, AWS charges for the <code>GetMetricData</code> or <code>GetMetricStatistics</code> requests. We assume 100 requests per day.</p><figure class="highlight basic"><table><tr><td class="code"><pre><span class="line"><span class="symbol">100 </span>* <span class="number">30</span> = <span class="number">3000</span> requests per month = $<span class="number">0.03</span> per month</span><br></pre></td></tr></table></figure><p>So, we pay an additional $0.03 per month to display the metric per month.</p><p>In total, my estimation is a charge of <strong>$1.19 per custom metric per month</strong>.</p><p>Let’s compare the costs of custom metrics with logs next.</p><h2 id="CloudWatch-logs-pricing-example"><a href="#CloudWatch-logs-pricing-example" class="headerlink" title="CloudWatch logs pricing example"></a>CloudWatch logs pricing example</h2><p>Instead of using custom metrics, we send structured log messages to CloudWatch. For example, the following log message includes an event type and a value.</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line">&#123;<span class="keyword">event</span>:<span class="string">&#x27;CUSTOM_METRIC_1&#x27;</span>,<span class="keyword">value</span>:<span class="number">10</span>&#125;</span><br></pre></td></tr></table></figure><p>For the following pricing example, let’s assume each metric data message consumes about 60 bytes of storage. AWS charges $0.50 per GB (us-east-1) for ingesting data.</p><figure class="highlight basic"><table><tr><td class="code"><pre><span class="line"><span class="symbol">2 </span>per minute * <span class="number">60</span> minutes * <span class="number">24</span> hours * <span class="number">30</span> days * <span class="number">60</span> bytes = <span class="number">0.005</span> GB per month = $<span class="number">0.0025</span> per month</span><br></pre></td></tr></table></figure><p>In summary, data ingestion costs $0.0025 per metric and month.</p><p>Additionally, AWS charges $0.005 per GB of data scanned when querying the data. Let’s assume that the CloudWatch dashboard displaying information about the metric executes 100 CloudWatch Logs Insights queries per day and fetches data from the past 30 days.</p><figure class="highlight basic"><table><tr><td class="code"><pre><span class="line"><span class="symbol">30 </span>days * <span class="number">100</span> queries * <span class="number">0.005</span> GB = <span class="number">15</span> GB scanned <span class="keyword">data</span> per month = $<span class="number">0.075</span> per month</span><br></pre></td></tr></table></figure><blockquote><p>Use <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CloudWatchLogs-Field-Indexing.html" target="_blank" rel="noopener">field indexes</a> to ensure each query is only scanning relevant data.</p></blockquote><p>So the cost for querying the data is $0.075 per month.</p><p>In total, my estimation is a charge of <strong>$0.0775 per metric and month</strong>.</p><p>Note that using logs to display metrics on a dashboard is <strong>93% cheaper</strong> than using custom metrics.</p><h2 id="Pros-and-Cons"><a href="#Pros-and-Cons" class="headerlink" title="Pros and Cons"></a>Pros and Cons</h2><p><strong>First,</strong> defining CloudWatch alarms to trigger automated actions like scaling out or in or to notify operators requires CloudWatch metrics. Using CloudWatch logs to collect and query metrics is a possible option when the data is needed to be analyzed on-demand, for example, to build a dashboard.</p><p><strong>Second,</strong> dashboard widgets based on CloudWatch metrics load much faster than those relying on a CloudWatch logs query. It takes about 1 second to load a widget based on a custom metric and 5 to 10 seconds to load a widget based on a logs query.</p><p><strong>Third,</strong> the available widget types for CloudWatch dashboards differ based on the data type.</p><p>The following screenshot shows the available widgets for CloudWatch metrics.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/11/cloudwatch-dashboard-widgets-metrics@730w.webp 730w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@730w2x.webp 1460w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@610w.webp 610w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@610w2x.webp 1220w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@450w.webp 450w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@450w2x.webp 900w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@330w.webp 330w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@330w2x.webp 660w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@545w.webp 545w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/11/cloudwatch-dashboard-widgets-metrics@730w.png 730w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@730w2x.png 1460w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@610w.png 610w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@610w2x.png 1220w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@450w.png 450w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@450w2x.png 900w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@330w.png 330w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@330w2x.png 660w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@545w.png 545w, /images/2025/11/cloudwatch-dashboard-widgets-metrics@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/11/cloudwatch-dashboard-widgets-metrics.png" alt="Available CloudWatch dashboard widgets for metrics" title="Available CloudWatch dashboard widgets for metrics"></picture></p><p>In contrast, the next screenshot shows the available widgets for CloudWatch logs.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/11/cloudwatch-dashboard-widgets-logs@730w.webp 730w, /images/2025/11/cloudwatch-dashboard-widgets-logs@730w2x.webp 1460w, /images/2025/11/cloudwatch-dashboard-widgets-logs@610w.webp 610w, /images/2025/11/cloudwatch-dashboard-widgets-logs@610w2x.webp 1220w, /images/2025/11/cloudwatch-dashboard-widgets-logs@450w.webp 450w, /images/2025/11/cloudwatch-dashboard-widgets-logs@450w2x.webp 900w, /images/2025/11/cloudwatch-dashboard-widgets-logs@330w.webp 330w, /images/2025/11/cloudwatch-dashboard-widgets-logs@330w2x.webp 660w, /images/2025/11/cloudwatch-dashboard-widgets-logs@545w.webp 545w, /images/2025/11/cloudwatch-dashboard-widgets-logs@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/11/cloudwatch-dashboard-widgets-logs@730w.png 730w, /images/2025/11/cloudwatch-dashboard-widgets-logs@730w2x.png 1460w, /images/2025/11/cloudwatch-dashboard-widgets-logs@610w.png 610w, /images/2025/11/cloudwatch-dashboard-widgets-logs@610w2x.png 1220w, /images/2025/11/cloudwatch-dashboard-widgets-logs@450w.png 450w, /images/2025/11/cloudwatch-dashboard-widgets-logs@450w2x.png 900w, /images/2025/11/cloudwatch-dashboard-widgets-logs@330w.png 330w, /images/2025/11/cloudwatch-dashboard-widgets-logs@330w2x.png 660w, /images/2025/11/cloudwatch-dashboard-widgets-logs@545w.png 545w, /images/2025/11/cloudwatch-dashboard-widgets-logs@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/11/cloudwatch-dashboard-widgets-logs.png" alt="Available CloudWatch dashboard widgets for logs" title="Available CloudWatch dashboard widgets for logs"></picture></p><p><strong>Fourth,</strong> the query language to evaluate data differs between metrics and logs. A <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/using-metric-math.html" target="_blank" rel="noopener">math expression</a> allows you to combine multiple metrics, for example.</p><p>The following snippet shows a math expression to calculate the total network throughput of a NAT gateway.</p><figure class="highlight clojure"><table><tr><td class="code"><pre><span class="line">(<span class="name">BytesInFromDestination+BytesInFromSource+BytesOutToDestination+BytesOutToSource</span>)</span><br></pre></td></tr></table></figure><p>In contrast, CloudWatch Logs Insights allows you to analyze metric data stored in log groups. Three different query languages are supported.</p><ul><li>Logs Insights QL</li><li>PPL</li><li>SQL</li></ul><p>The following example shows a query, that fetches the median, p90, and p95 from log messages grouped by day.</p><figure class="highlight n1ql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> window.<span class="keyword">start</span>, approx_percentile(<span class="symbol">`message.diffStartInProgress`</span>, <span class="number">0.50</span>) <span class="keyword">AS</span> <span class="symbol">`Median`</span>, approx_percentile(<span class="symbol">`message.diffStartInProgress`</span>, <span class="number">0.90</span>) <span class="keyword">AS</span> <span class="symbol">`p90`</span>, approx_percentile(<span class="symbol">`message.diffStartInProgress`</span>, <span class="number">0.95</span>) <span class="keyword">AS</span> <span class="symbol">`p95`</span> <span class="keyword">FROM</span> <span class="symbol">`logGroups(logGroupIdentifier:[&#x27;/aws/lambda/***&#x27;])`</span> <span class="keyword">WHERE</span> <span class="symbol">`message.eventId`</span> = <span class="string">&#x27;JOB_IN_PROGRESS&#x27;</span> <span class="keyword">GROUP</span> <span class="keyword">BY</span> window(<span class="symbol">`@timestamp`</span>, <span class="string">&#x27;24 hours&#x27;</span>)</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Collecting and analyzing metric data with CloudWatch logs is a cost-effective alternative to using custom metrics. The approach comes with a few drawbacks. For example, it is not possible to automate actions or send notifications based on logs out-of-the-box. However, building a dashboard to provide insights into key metrics is definitely possible with data stored in log groups instead of custom metrics.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Step Functions: How to Orchestrate Workflows Waiting for 3rd Parties</title>
      <link>https://cloudonaut.io/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties/</link>
      <description>Introducing a serverless architecture using AWS Step Functions to track GitHub actions jobs. The architecture leverages Lambda functions to process webhooks and Dynamodb to store task tokens, allowing the state machine to pause and wait for external status updates.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/dynamodb/">dynamodb</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties/</guid>
      <pubDate>Wed, 22 Oct 2025 06:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>My go-to service for automating workflows in serverless applications is <strong>AWS Step Functions</strong>. Recently, I was working on an enhancement for <a href="https://hyperenv.com/" target="_blank" rel="noopener">HyperEnv, our solution to deploy self-hosted GitHub runners on AWS with ease</a>. The challenge was to track the status of GitHub jobs with the serverless backend of HyperEnv. In the following, I will share the architecture that you can use to orchestrate workflows that need to wait for 3rd parties.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@730w.webp 730w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@730w2x.webp 1460w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@610w.webp 610w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@610w2x.webp 1220w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@450w.webp 450w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@450w2x.webp 900w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@330w.webp 330w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@330w2x.webp 660w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@545w.webp 545w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@730w.jpg 730w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@730w2x.jpg 1460w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@610w.jpg 610w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@610w2x.jpg 1220w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@450w.jpg 450w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@450w2x.jpg 900w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@330w.jpg 330w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@330w2x.jpg 660w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@545w.jpg 545w, /images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/10/aws-step-functions-how-to-orchestrate-workflows-waiting-for-3rd-parties.jpg" alt="How to Orchestrate Workflows Waiting for 3rd Parties: Tracking GitHub Jobs with AWS Step Functions" title="How to Orchestrate Workflows Waiting for 3rd Parties: Tracking GitHub Jobs with AWS Step Functions"></picture></p><h2 id="The-challenge"><a href="#The-challenge" class="headerlink" title="The challenge"></a>The challenge</h2><p>GitHub webhooks enable applications to subscribe to events, like the <code>workflow_job</code> event informing about jobs getting created, starting to run, or completing. To ensure HyperEnv provides a self-hosted runner for every job, even in case of failures, HyperEnv needs to keep track of every GitHub job. Doing so allows HyperEnv to retry launching a self-hosted runner in case a job does not start running within 5 minutes, for example.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/10/step-functions-challenge@730w.webp 730w, /images/2025/10/step-functions-challenge@730w2x.webp 1460w, /images/2025/10/step-functions-challenge@610w.webp 610w, /images/2025/10/step-functions-challenge@610w2x.webp 1220w, /images/2025/10/step-functions-challenge@450w.webp 450w, /images/2025/10/step-functions-challenge@450w2x.webp 900w, /images/2025/10/step-functions-challenge@330w.webp 330w, /images/2025/10/step-functions-challenge@330w2x.webp 660w, /images/2025/10/step-functions-challenge@545w.webp 545w, /images/2025/10/step-functions-challenge@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/10/step-functions-challenge@730w.png 730w, /images/2025/10/step-functions-challenge@730w2x.png 1460w, /images/2025/10/step-functions-challenge@610w.png 610w, /images/2025/10/step-functions-challenge@610w2x.png 1220w, /images/2025/10/step-functions-challenge@450w.png 450w, /images/2025/10/step-functions-challenge@450w2x.png 900w, /images/2025/10/step-functions-challenge@330w.png 330w, /images/2025/10/step-functions-challenge@330w2x.png 660w, /images/2025/10/step-functions-challenge@545w.png 545w, /images/2025/10/step-functions-challenge@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/10/step-functions-challenge.png" alt="The challenge: keep track of the state of a GitHub job by using webhook events" title="The challenge: keep track of the state of a GitHub job by using webhook events"></picture></p><h2 id="The-solution"><a href="#The-solution" class="headerlink" title="The solution"></a>The solution</h2><p>The following diagram illustrates the solution. Let me walk you through the architecture diagram from left to right.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/10/step-functions-solution@730w.webp 730w, /images/2025/10/step-functions-solution@730w2x.webp 1460w, /images/2025/10/step-functions-solution@610w.webp 610w, /images/2025/10/step-functions-solution@610w2x.webp 1220w, /images/2025/10/step-functions-solution@450w.webp 450w, /images/2025/10/step-functions-solution@450w2x.webp 900w, /images/2025/10/step-functions-solution@330w.webp 330w, /images/2025/10/step-functions-solution@330w2x.webp 660w, /images/2025/10/step-functions-solution@545w.webp 545w, /images/2025/10/step-functions-solution@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/10/step-functions-solution@730w.png 730w, /images/2025/10/step-functions-solution@730w2x.png 1460w, /images/2025/10/step-functions-solution@610w.png 610w, /images/2025/10/step-functions-solution@610w2x.png 1220w, /images/2025/10/step-functions-solution@450w.png 450w, /images/2025/10/step-functions-solution@450w2x.png 900w, /images/2025/10/step-functions-solution@330w.png 330w, /images/2025/10/step-functions-solution@330w2x.png 660w, /images/2025/10/step-functions-solution@545w.png 545w, /images/2025/10/step-functions-solution@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/10/step-functions-solution.png" alt="The solution: API Gateway, Lambda, Step Functions, DynamoDB" title="The solution: API Gateway, Lambda, Step Functions, DynamoDB"></picture></p><p>First, an API Gateway receives the GitHub webhook events via HTTP and invokes the Lambda function <code>Webhook</code>.</p><p>Next, the Lambda function <code>Webhook</code> starts the execution of the state machine <code>Job State Machine</code>.</p><p>The following screenshot shows the state machine in more detail.</p><ul><li><code>Start</code> is where the state machine starts.</li><li><code>Queued</code> is the initial state of a GitHub job.</li><li><code>InProgress</code> indicates that the GitHub job is running.</li><li><code>Completed</code> means that the GitHub job finished.</li><li><code>End</code> is the end of the state machine.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/10/jobstatemachine@730w.webp 730w, /images/2025/10/jobstatemachine@730w2x.webp 1460w, /images/2025/10/jobstatemachine@610w.webp 610w, /images/2025/10/jobstatemachine@610w2x.webp 1220w, /images/2025/10/jobstatemachine@450w.webp 450w, /images/2025/10/jobstatemachine@450w2x.webp 900w, /images/2025/10/jobstatemachine@330w.webp 330w, /images/2025/10/jobstatemachine@330w2x.webp 660w, /images/2025/10/jobstatemachine@545w.webp 545w, /images/2025/10/jobstatemachine@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/10/jobstatemachine@730w.png 730w, /images/2025/10/jobstatemachine@730w2x.png 1460w, /images/2025/10/jobstatemachine@610w.png 610w, /images/2025/10/jobstatemachine@610w2x.png 1220w, /images/2025/10/jobstatemachine@450w.png 450w, /images/2025/10/jobstatemachine@450w2x.png 900w, /images/2025/10/jobstatemachine@330w.png 330w, /images/2025/10/jobstatemachine@330w2x.png 660w, /images/2025/10/jobstatemachine@545w.png 545w, /images/2025/10/jobstatemachine@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/10/jobstatemachine.png" alt="Insights into Job State Machine" title="Insights into Job State Machine"></picture></p><p>Then, the state machine <code>Job State Machine</code> invokes the Lambda function <code>Job State</code> by using the <strong>Wait for Callback</strong> integration (see <a href="https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html#connect-wait-token" target="_blank" rel="noopener">Wait for a Callback with Task Token</a>).</p><p>The Lambda function <code>Job State</code> creates an item in the DynamoDB table <code>Job State</code> and persists the task token needed to resolve the callback.</p><p>If next, GitHub sends a webhook event with a status update, the process continues.</p><p>The API Gateway receives the event. Then, the Lambda function <code>Webhook</code> queries the DynamoDB table <code>Job State</code> and fetches the task token. With the task token, the Lambda function <code>Webhook</code> sends a task success signal to the state machine <code>Job State Machine</code>. Which will resolve the wait for the <code>Queued</code> task and continue with the next one <code>InProgress</code>.</p><p>In the next section, I will share implementation details of the architecture.</p><h2 id="Deep-Dive-Step-Functions-and-Lambda-with-Wait-for-Callback"><a href="#Deep-Dive-Step-Functions-and-Lambda-with-Wait-for-Callback" class="headerlink" title="Deep Dive: Step Functions and Lambda with Wait for Callback"></a>Deep Dive: Step Functions and Lambda with Wait for Callback</h2><p>The following listing shows an excerpt of the state machine’s definition.</p><p>The Lambda function is called with <strong>Wait for Callback</strong> (see <code>arn:aws:states:::lambda:invoke.waitForTaskToken</code>).</p><p>The task token is needed to send a success signal as soon as the state is done. Therefore, <code>$states.context.Task.Token</code> is added to the payload of the Lambda function invocation.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Comment&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Job State Machine&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;QueryLanguage&quot;</span><span class="punctuation">:</span> <span class="string">&quot;JSONata&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;StartAt&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Queued&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;States&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;Queued&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Arguments&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;FunctionName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:lambda:eu-west-1:xxxxxxxxxxxx:function:JobStateFunctionFunction&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;Payload&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;% $merge([$states.context.Execution.Input, &#123;\&quot;task\&quot;: \&quot;Queued\&quot;, \&quot;taskToken\&quot;: $states.context.Task.Token&#125;]) %&#125;&quot;</span> <span class="comment">// &lt;= Adds the task token to the payload when invoking the Lambda function</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Next&quot;</span><span class="punctuation">:</span> <span class="string">&quot;InProgress&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:states:::lambda:invoke.waitForTaskToken&quot;</span><span class="punctuation">,</span> <span class="comment">// &lt;= Calls Lambda function in Wait for Callback mode</span></span><br><span class="line">      <span class="attr">&quot;TimeoutSeconds&quot;</span><span class="punctuation">:</span> <span class="number">300</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Task&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;InProgress&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Arguments&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;FunctionName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:lambda:eu-west-1:xxxxxxxxxxxx:function:JobStateFunctionFunction&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;Payload&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;% $merge([$states.context.Execution.Input, &#123;\&quot;task\&quot;: \&quot;InProgress\&quot;, \&quot;taskToken\&quot;: $states.context.Task.Token&#125;]) %&#125;&quot;</span> <span class="comment">// &lt;= Adds the task token to the payload when invoking the Lambda function</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Next&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Completed&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:states:::lambda:invoke.waitForTaskToken&quot;</span><span class="punctuation">,</span> <span class="comment">// &lt;= Calls Lambda function in Wait for Callback mode</span></span><br><span class="line">      <span class="attr">&quot;TimeoutSeconds&quot;</span><span class="punctuation">:</span> <span class="number">21600</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Task&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    ...</span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;TimeoutSeconds&quot;</span><span class="punctuation">:</span> <span class="number">3600</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Next, let’s take a look into a possible implementation of the Lambda function <code>Job State</code>.</p><p>Depending on the <code>event.task</code> property, the function updates an item in the DynamoDB table.</p><p>The <code>expire_at</code> attribute is used to define a time-to-live for the DynamoDB item to ensure the stored state gets deleted from the table automatically.</p><p>The task token is stored in the item’s attribute <code>task_token_queued</code> or <code>task_token_in_progress</code>.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">DynamoDBClient</span>, <span class="title class_">UpdateItemCommand</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-sdk/client-dynamodb&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> ddbClient = <span class="keyword">new</span> <span class="title class_">DynamoDBClient</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">handler</span>(<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="keyword">switch</span>(event.<span class="property">task</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;Queued&#x27;</span>: &#123;</span><br><span class="line">      <span class="keyword">await</span> ddbClient.<span class="title function_">send</span>(<span class="keyword">new</span> <span class="title class_">UpdateItemCommand</span>(&#123;</span><br><span class="line">        <span class="title class_">ExpressionAttributeValues</span>: &#123;</span><br><span class="line">          <span class="string">&#x27;:state&#x27;</span>: &#123;</span><br><span class="line">            <span class="attr">S</span>: <span class="string">&#x27;queued&#x27;</span></span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="string">&#x27;:task_token&#x27;</span>: &#123;</span><br><span class="line">            <span class="attr">S</span>: event.<span class="property">taskToken</span></span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="string">&#x27;:expire_at&#x27;</span>: &#123;</span><br><span class="line">            <span class="attr">N</span>: <span class="title class_">Math</span>.<span class="title function_">floor</span>((<span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>() + <span class="number">1</span> * <span class="number">24</span> * <span class="number">60</span> * <span class="number">60</span> * <span class="number">1000</span>) / <span class="number">1000</span>).<span class="title function_">toString</span>(), <span class="comment">// expire in 24 hours</span></span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="string">&#x27;:created_at&#x27;</span>: &#123;</span><br><span class="line">            <span class="attr">N</span>: <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>() / <span class="number">1000</span>).<span class="title function_">toString</span>()</span><br><span class="line">          &#125;,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="title class_">ExpressionAttributeNames</span>: &#123;</span><br><span class="line">          <span class="string">&#x27;#s&#x27;</span>: <span class="string">&#x27;state&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="title class_">Key</span>: &#123;</span><br><span class="line">          <span class="attr">run_job_id</span>: &#123;</span><br><span class="line">            <span class="attr">S</span>: <span class="string">`run-<span class="subst">$&#123;event.runId&#125;</span>-job-<span class="subst">$&#123;event.jobId&#125;</span>`</span></span><br><span class="line">          &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="title class_">ReturnValues</span>: <span class="string">&#x27;ALL_NEW&#x27;</span>,</span><br><span class="line">        <span class="title class_">TableName</span>: <span class="variable constant_">JOB_STATE_TABLE_NAME</span>,</span><br><span class="line">        <span class="title class_">UpdateExpression</span>: <span class="string">&#x27;SET #s = :state, task_token_queued = :task_token, expire_at = if_not_exists(expire_at, :expire_at),  created_at = if_not_exists(created_at, :created_at)&#x27;</span></span><br><span class="line">      &#125;));</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;InProgress&#x27;</span>:</span><br><span class="line">      <span class="keyword">await</span> ddbClient.<span class="title function_">send</span>(<span class="keyword">new</span> <span class="title class_">UpdateItemCommand</span>(&#123;</span><br><span class="line">        <span class="title class_">ExpressionAttributeValues</span>: &#123;</span><br><span class="line">          <span class="string">&#x27;:state&#x27;</span>: &#123;</span><br><span class="line">            <span class="attr">S</span>: <span class="string">&#x27;in_progress&#x27;</span></span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="string">&#x27;:task_token&#x27;</span>: &#123;</span><br><span class="line">            <span class="attr">S</span>: event.<span class="property">taskToken</span></span><br><span class="line">          &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="title class_">ExpressionAttributeNames</span>: &#123;</span><br><span class="line">          <span class="string">&#x27;#s&#x27;</span>: <span class="string">&#x27;state&#x27;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="title class_">Key</span>: &#123;</span><br><span class="line">          <span class="attr">run_job_id</span>: &#123;</span><br><span class="line">            <span class="attr">S</span>: <span class="string">`run-<span class="subst">$&#123;event.runId&#125;</span>-job-<span class="subst">$&#123;event.jobId&#125;</span>`</span></span><br><span class="line">          &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="title class_">ReturnValues</span>: <span class="string">&#x27;ALL_NEW&#x27;</span>,</span><br><span class="line">        <span class="title class_">TableName</span>: <span class="variable constant_">JOB_STATE_TABLE_NAME</span>,</span><br><span class="line">        <span class="title class_">UpdateExpression</span>: <span class="string">&#x27;SET #s = :state, task_token_in_progress = :task_token&#x27;</span></span><br><span class="line">      &#125;));</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;Completed&#x27;</span>:</span><br><span class="line">      <span class="comment">// ...</span></span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Finally, let’s take a look into the implementation of the Lambda function <code>Webhook</code>.</p><p>The following snippet shows how the Lambda function <code>Webhook</code> starts the execution of the state machine <code>Job State Machine</code> for each new GitHub job.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (body.<span class="property">action</span> === <span class="string">&#x27;queued&#x27;</span>) &#123;</span><br><span class="line">  <span class="keyword">await</span> sfnClient.<span class="title function_">send</span>(<span class="keyword">new</span> <span class="title class_">StartExecutionCommand</span>(&#123;</span><br><span class="line">    <span class="attr">stateMachineArn</span>: <span class="variable constant_">JOB_STATE_MACHINE_ARN</span>,</span><br><span class="line">    <span class="attr">input</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;</span><br><span class="line">      <span class="attr">organizationName</span>: body.<span class="property">repository</span>.<span class="property">owner</span>.<span class="property">login</span>,</span><br><span class="line">      <span class="attr">jobLabels</span>: body.<span class="property">workflow_job</span>.<span class="property">labels</span>,</span><br><span class="line">      <span class="attr">runId</span>: body.<span class="property">workflow_job</span>.<span class="property">run_id</span>,</span><br><span class="line">      <span class="attr">jobId</span>: body.<span class="property">workflow_job</span>.<span class="property">id</span>,</span><br><span class="line">      <span class="attr">installationId</span>: body.<span class="property">installation</span>.<span class="property">id</span>,</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="attr">name</span>: <span class="string">`run-<span class="subst">$&#123;body.workflow_job.run_id&#125;</span>-job-<span class="subst">$&#123;body.workflow_job.id&#125;</span>`</span></span><br><span class="line">  &#125;));</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">statusCode</span>: <span class="number">200</span>,</span><br><span class="line">    <span class="attr">body</span>: <span class="string">`created state machine execution run-<span class="subst">$&#123;body.workflow_job.run_id&#125;</span>-job-<span class="subst">$&#123;body.workflow_job.id&#125;</span>`</span></span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Next, when GitHub sends a webhook indicating that the job changed its status from <code>queued</code> to in <code>in_progress</code> the Lambda function executes the following code.</p><ol><li>Fetch the state of the GitHub job from the DynamoDB table <code>Job State</code>.</li><li>Use the task token <code>task_token_queued</code> to send a task success notification to the state machine.</li></ol><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (body.<span class="property">action</span> === <span class="string">&#x27;in_progress&#x27;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> queryResult = <span class="keyword">await</span> ddbClient.<span class="title function_">send</span>(<span class="keyword">new</span> <span class="title class_">QueryCommand</span>(&#123;</span><br><span class="line">    <span class="title class_">ExpressionAttributeValues</span>: &#123;</span><br><span class="line">      <span class="string">&#x27;:run_job_id&#x27;</span>: &#123;</span><br><span class="line">        <span class="attr">S</span>: <span class="string">`run-<span class="subst">$&#123;body.workflow_job.run_id&#125;</span>-job-<span class="subst">$&#123;body.workflow_job.id&#125;</span>`</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="title class_">KeyConditionExpression</span>: <span class="string">&#x27;run_job_id = :run_job_id&#x27;</span>,</span><br><span class="line">    <span class="title class_">TableName</span>: <span class="variable constant_">JOB_STATE_TABLE_NAME</span></span><br><span class="line">  &#125;));</span><br><span class="line">  <span class="keyword">await</span> sfnClient.<span class="title function_">send</span>(<span class="keyword">new</span> <span class="title class_">SendTaskSuccessCommand</span>(&#123;</span><br><span class="line">    <span class="attr">taskToken</span>: queryResult.<span class="property">Items</span>[<span class="number">0</span>].<span class="property">task_token_queued</span>.<span class="property">S</span>,</span><br><span class="line">    <span class="attr">output</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;&#125;)</span><br><span class="line">  &#125;));</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">statusCode</span>: <span class="number">200</span>,</span><br><span class="line">    <span class="attr">body</span>: <span class="string">&#x27;state updated&#x27;</span></span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The process is similar for the transition from <code>in_progress</code> to <code>completed</code>.</p><h2 id="Summary-Feedback"><a href="#Summary-Feedback" class="headerlink" title="Summary &amp; Feedback"></a>Summary &amp; Feedback</h2><p>To track the progress of a workflow depending on 3rd parties, the following patterns are helpful when using AWS Step Functions.</p><ul><li>Use <strong>Wait for Callback</strong> mode to invoke Lambda functions. Store the task token in DynamoDB. Optionally call the 3rd party. Then wait for a response from the 3rd party.</li><li>As AWS Step Functions does not provide a way to get information about the current task (aka. state), store information about the current state in DynamoDB.</li></ul><p>I hope following along with my solution helps you to come up with a suitable approach for your use case. In case you found another approach, please share it with me. I’m happy to learn from you!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Optimizing Amazon Linux 2023 for tiny EC2 instances: t3.nano, t3a.nano, or t4g.nano</title>
      <link>https://cloudonaut.io/optimizing-amazon-linux-2023-for-tiny-ec2-instances/</link>
      <description>Amazon Linux 2023 slow on tiny EC2 instances? Learn how to optimize t3.nano, t3a.nano, and t4g.nano performance by reconfiguring /tmp and swap defaults.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <guid isPermaLink="true">https://cloudonaut.io/optimizing-amazon-linux-2023-for-tiny-ec2-instances/</guid>
      <pubDate>Mon, 18 Aug 2025 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>I recently observed severe performance degradation when porting our <a href="https://bucketav.com/" target="_blank" rel="noopener">S3 antivirus solution, bucketAV,</a> from Amazon Linux 2 to Amazon Linux 2023 on tiny instance types such as t3.nano, t3a.nano, or t4g.nano. It took minutes to run commands that usually complete in a second. In this blog post, I share two tricks to get back to performance levels provided by Amazon Linux 2.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/08/memory@730w.webp 730w, /images/2025/08/memory@730w2x.webp 1460w, /images/2025/08/memory@610w.webp 610w, /images/2025/08/memory@610w2x.webp 1220w, /images/2025/08/memory@450w.webp 450w, /images/2025/08/memory@450w2x.webp 900w, /images/2025/08/memory@330w.webp 330w, /images/2025/08/memory@330w2x.webp 660w, /images/2025/08/memory@545w.webp 545w, /images/2025/08/memory@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/08/memory@730w.jpg 730w, /images/2025/08/memory@730w2x.jpg 1460w, /images/2025/08/memory@610w.jpg 610w, /images/2025/08/memory@610w2x.jpg 1220w, /images/2025/08/memory@450w.jpg 450w, /images/2025/08/memory@450w2x.jpg 900w, /images/2025/08/memory@330w.jpg 330w, /images/2025/08/memory@330w2x.jpg 660w, /images/2025/08/memory@545w.jpg 545w, /images/2025/08/memory@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/08/memory.jpg" alt="Memory is valuable" title="Memory is valuable"></picture></p><p>Running workloads on t3.nano, t3a.nano, or t4g.nano is a challenge. The 512 MB of RAM are mostly used entirely by the operating system Amazon Linux 2023. As soon as you (or your application) try to allocate memory, things get very slow. There are two tricks to address this problem by moving stuff from RAM to disk:</p><ol><li>Move &#x2F;tmp from RAM to disk.</li><li>Move SWAP from RAM to disk.</li></ol><h2 id="Move-tmp-from-RAM-to-disk"><a href="#Move-tmp-from-RAM-to-disk" class="headerlink" title="Move &#x2F;tmp from RAM to disk"></a>Move &#x2F;tmp from RAM to disk</h2><p><code>/tmp</code> is typically where files that a program doesn’t need in the long-term go. In <a href="https://docs.aws.amazon.com/linux/al2023/ug/compare-al2-al2023-tmp.html" target="_blank" rel="noopener">Amazon Linux 2023</a>, <code>/tmp</code> is backed by <code>tmpfs</code> which stored the file in memory.</p><p>The problem: Memory is scarce on t3.nano.</p><p>To return to <code>/tmp</code> stored on disk, run:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl mask tmp.mount </span><br><span class="line"><span class="built_in">sudo</span> systemctl stop tmp.mount</span><br></pre></td></tr></table></figure><h2 id="Move-SWAP-from-RAM-to-disk"><a href="#Move-SWAP-from-RAM-to-disk" class="headerlink" title="Move SWAP from RAM to disk"></a>Move SWAP from RAM to disk</h2><p>If the OS is running out of memory, it tries to move memory from RAM to another medium, aka swap. Often, swap is backed by disk storage, which is slow but works. In Amazon Linux 2023, <a href="https://docs.aws.amazon.com/linux/al2023/release-notes/relnotes-2023.2.20230920.html" target="_blank" rel="noopener">swap is provided by zram</a> on instances with less than 1 GB of memory. Zram creates a compressed block device in RAM for swap.</p><p>Two problems:</p><ol><li>Compression&#x2F;decompression is CPU-bound, and you don’t have a lot of CPU power on a nano instance.</li><li>RAM is already scarce. There simply is not a lot of RAM available.</li></ol><p>To disable zram and return to swap backed by disk, run:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> dnf -y remove zram-generator-defaults zram-generator</span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> fallocate -l 2G /swapfile</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chmod</span> 600 /swapfile</span><br><span class="line"><span class="built_in">sudo</span> mkswap /swapfile</span><br><span class="line"><span class="built_in">sudo</span> swapon /swapfile</span><br><span class="line"><span class="built_in">cat</span> &lt;&lt;<span class="string">&quot;EOF&quot;</span> | <span class="built_in">sudo</span> <span class="built_in">tee</span> -a /etc/fstab &gt; /dev/null</span><br><span class="line">/swapfile none swap defaults 0 0</span><br><span class="line">EOF</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>The most scarce resource on EC2 instance types like t3.nano, t3a.nano, or t4g.nano is memory. Unfortunately, Amazon Linux 2023 is much more memory-hungry than Amazon Linux 2. Therefore, you might need to tweak AL2023 to continue running your applications. The most important trick is to move swap away from zram to disk.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to reduce your AWS Config bill for volatile workloads</title>
      <link>https://cloudonaut.io/how-to-reduce-your-aws-config-bill/</link>
      <description>Learn how to reduce AWS Config costs for volatile workloads like AWS Fargate tasks</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/config/">config</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-reduce-your-aws-config-bill/</guid>
      <pubDate>Fri, 01 Aug 2025 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Your AWS bill is mostly based on usage, which is great. The more you use it, the more you pay. When usage increases by 50%, the AWS bill grows by 50% as well. But lately, I realized that something odd was happening. In this blog post, I share my story about an ever-growing AWS bill caused by AWS Config and a trick to reduce your AWS Config bill.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/08/config-costs@730w.webp 730w, /images/2025/08/config-costs@730w2x.webp 1460w, /images/2025/08/config-costs@610w.webp 610w, /images/2025/08/config-costs@610w2x.webp 1220w, /images/2025/08/config-costs@450w.webp 450w, /images/2025/08/config-costs@450w2x.webp 900w, /images/2025/08/config-costs@330w.webp 330w, /images/2025/08/config-costs@330w2x.webp 660w, /images/2025/08/config-costs@545w.webp 545w, /images/2025/08/config-costs@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/08/config-costs@730w.jpg 730w, /images/2025/08/config-costs@730w2x.jpg 1460w, /images/2025/08/config-costs@610w.jpg 610w, /images/2025/08/config-costs@610w2x.jpg 1220w, /images/2025/08/config-costs@450w.jpg 450w, /images/2025/08/config-costs@450w2x.jpg 900w, /images/2025/08/config-costs@330w.jpg 330w, /images/2025/08/config-costs@330w2x.jpg 660w, /images/2025/08/config-costs@545w.jpg 545w, /images/2025/08/config-costs@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/08/config-costs.jpg" alt="How to reduce your AWS Config bill" title="How to reduce your AWS Config bill"></picture></p><p>For more than a year, each month, I receive a budget alert for the AWS account that hosts our <a href="https://marbot.io/" target="_blank" rel="noopener">event-driven AWS Monitoring solution called marbot</a>. The following figure shows the latest alert from July 2025.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/08/marbot-budget-alert@730w.webp 730w, /images/2025/08/marbot-budget-alert@730w2x.webp 1460w, /images/2025/08/marbot-budget-alert@610w.webp 610w, /images/2025/08/marbot-budget-alert@610w2x.webp 1220w, /images/2025/08/marbot-budget-alert@450w.webp 450w, /images/2025/08/marbot-budget-alert@450w2x.webp 900w, /images/2025/08/marbot-budget-alert@330w.webp 330w, /images/2025/08/marbot-budget-alert@330w2x.webp 660w, /images/2025/08/marbot-budget-alert@545w.webp 545w, /images/2025/08/marbot-budget-alert@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/08/marbot-budget-alert@730w.png 730w, /images/2025/08/marbot-budget-alert@730w2x.png 1460w, /images/2025/08/marbot-budget-alert@610w.png 610w, /images/2025/08/marbot-budget-alert@610w2x.png 1220w, /images/2025/08/marbot-budget-alert@450w.png 450w, /images/2025/08/marbot-budget-alert@450w2x.png 900w, /images/2025/08/marbot-budget-alert@330w.png 330w, /images/2025/08/marbot-budget-alert@330w2x.png 660w, /images/2025/08/marbot-budget-alert@545w.png 545w, /images/2025/08/marbot-budget-alert@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/08/marbot-budget-alert.png" alt="marbot Budget alert" title="marbot Budget alert"></picture></p><p>For a long time, I accepted that I was spending more than what I defined in my budget years ago. This month, I investigated the bill increase and discovered something interesting. </p><h2 id="Checking-my-AWS-bill"><a href="#Checking-my-AWS-bill" class="headerlink" title="Checking my AWS bill"></a>Checking my AWS bill</h2><p>I use AWS Cost Explorer to understand my AWS costs. The following figure shows the AWS costs for the last 12 months:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/08/marbot-aws-costs-by-service@730w.webp 730w, /images/2025/08/marbot-aws-costs-by-service@730w2x.webp 1460w, /images/2025/08/marbot-aws-costs-by-service@610w.webp 610w, /images/2025/08/marbot-aws-costs-by-service@610w2x.webp 1220w, /images/2025/08/marbot-aws-costs-by-service@450w.webp 450w, /images/2025/08/marbot-aws-costs-by-service@450w2x.webp 900w, /images/2025/08/marbot-aws-costs-by-service@330w.webp 330w, /images/2025/08/marbot-aws-costs-by-service@330w2x.webp 660w, /images/2025/08/marbot-aws-costs-by-service@545w.webp 545w, /images/2025/08/marbot-aws-costs-by-service@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/08/marbot-aws-costs-by-service@730w.png 730w, /images/2025/08/marbot-aws-costs-by-service@730w2x.png 1460w, /images/2025/08/marbot-aws-costs-by-service@610w.png 610w, /images/2025/08/marbot-aws-costs-by-service@610w2x.png 1220w, /images/2025/08/marbot-aws-costs-by-service@450w.png 450w, /images/2025/08/marbot-aws-costs-by-service@450w2x.png 900w, /images/2025/08/marbot-aws-costs-by-service@330w.png 330w, /images/2025/08/marbot-aws-costs-by-service@330w2x.png 660w, /images/2025/08/marbot-aws-costs-by-service@545w.png 545w, /images/2025/08/marbot-aws-costs-by-service@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/08/marbot-aws-costs-by-service.png" alt="marbot AWS costs by service" title="marbot AWS costs by service"></picture></p><p>As I said, the AWS bill should somehow reflect the usage pattern of the workload. As an approximation, I used the number of alerts created by marbot to check the usage of our service against the AWS bill.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/08/marbot-alerts@730w.webp 730w, /images/2025/08/marbot-alerts@730w2x.webp 1460w, /images/2025/08/marbot-alerts@610w.webp 610w, /images/2025/08/marbot-alerts@610w2x.webp 1220w, /images/2025/08/marbot-alerts@450w.webp 450w, /images/2025/08/marbot-alerts@450w2x.webp 900w, /images/2025/08/marbot-alerts@330w.webp 330w, /images/2025/08/marbot-alerts@330w2x.webp 660w, /images/2025/08/marbot-alerts@545w.webp 545w, /images/2025/08/marbot-alerts@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/08/marbot-alerts@730w.png 730w, /images/2025/08/marbot-alerts@730w2x.png 1460w, /images/2025/08/marbot-alerts@610w.png 610w, /images/2025/08/marbot-alerts@610w2x.png 1220w, /images/2025/08/marbot-alerts@450w.png 450w, /images/2025/08/marbot-alerts@450w2x.png 900w, /images/2025/08/marbot-alerts@330w.png 330w, /images/2025/08/marbot-alerts@330w2x.png 660w, /images/2025/08/marbot-alerts@545w.png 545w, /images/2025/08/marbot-alerts@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/08/marbot-alerts.png" alt="marbot alerts" title="marbot alerts"></picture></p><p>The number of alerts grew by 33%, from 120k to 160k, in the last 12 months. The AWS bill increased by 20% from $350 to $419. At the first glance, that looks great.</p><h2 id="Investigating-the-Cost-Spike"><a href="#Investigating-the-Cost-Spike" class="headerlink" title="Investigating the Cost Spike"></a>Investigating the Cost Spike</h2><p>But by looking closer, one service has a significant increase in costs: AWS Config.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/08/marbot-aws-config-costs@730w.webp 730w, /images/2025/08/marbot-aws-config-costs@730w2x.webp 1460w, /images/2025/08/marbot-aws-config-costs@610w.webp 610w, /images/2025/08/marbot-aws-config-costs@610w2x.webp 1220w, /images/2025/08/marbot-aws-config-costs@450w.webp 450w, /images/2025/08/marbot-aws-config-costs@450w2x.webp 900w, /images/2025/08/marbot-aws-config-costs@330w.webp 330w, /images/2025/08/marbot-aws-config-costs@330w2x.webp 660w, /images/2025/08/marbot-aws-config-costs@545w.webp 545w, /images/2025/08/marbot-aws-config-costs@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/08/marbot-aws-config-costs@730w.png 730w, /images/2025/08/marbot-aws-config-costs@730w2x.png 1460w, /images/2025/08/marbot-aws-config-costs@610w.png 610w, /images/2025/08/marbot-aws-config-costs@610w2x.png 1220w, /images/2025/08/marbot-aws-config-costs@450w.png 450w, /images/2025/08/marbot-aws-config-costs@450w2x.png 900w, /images/2025/08/marbot-aws-config-costs@330w.png 330w, /images/2025/08/marbot-aws-config-costs@330w2x.png 660w, /images/2025/08/marbot-aws-config-costs@545w.png 545w, /images/2025/08/marbot-aws-config-costs@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/08/marbot-aws-config-costs.png" alt="marbot AWS Config costs" title="marbot AWS Config costs"></picture></p><p>AWS Config costs increased by 1400% from $6 to $90.</p><h2 id="Root-Cause-Analysis"><a href="#Root-Cause-Analysis" class="headerlink" title="Root Cause Analysis"></a>Root Cause Analysis</h2><p>Luckily, AWS Cost Explorer allows me to drill down into the AWS Config costs by grouping by <strong>Usage Type</strong> which quickly revealed that most of the costs are associated with usage type <code>ConfigurationItemRecorded</code>.</p><p>I had two questions:</p><ol><li>Why are there so many more configuration changes recorded?</li><li>How can I reduce the recorded configuration changes?</li></ol><p>We ship AWS config data to a centralized S3 bucket so I executed a bunch of <a href="https://repost.aws/knowledge-center/retrieve-aws-config-items-per-month" target="_blank" rel="noopener">Athena queries shared by AWS</a> to learn that most of the changes are caused by AWS Fargate. When you launch a task, an ENI is created, which causes 4 changes to be recorded (ENI, VPC, subnet, security group). 4 AWS Config changes cost $0.012. Running a Fargate task for 1 minute costs $0.0008 (1 vCPU with 2 GB memory). In other words, I pay more for AWS Config than for Fargate which is crazy :)</p><p>You might ask, why use AWS Config at all? The answer is simple: Our ISO 27001 audit depends on AWS Security Hub, which depends on AWS Config.</p><h2 id="AWS-Config-cost-optimization"><a href="#AWS-Config-cost-optimization" class="headerlink" title="AWS Config cost optimization"></a>AWS Config cost optimization</h2><p>So the final question remains: How to reduce the changes recorded by AWS Config while still keeping Security Hub happy? The answer is simple: Switch from <a href="https://docs.aws.amazon.com/config/latest/developerguide/managing-recorder_console-change-recording-frequency.html" target="_blank" rel="noopener">recording frequency continuous to daily</a>.</p><p>The following CloudFormation snippet changes the frequency:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">ConfigurationRecorder:</span></span><br><span class="line">   <span class="attr">Type:</span> <span class="string">&#x27;AWS::Config::ConfigurationRecorder&#x27;</span></span><br><span class="line">   <span class="attr">Properties:</span></span><br><span class="line">     [<span class="string">...</span>]</span><br><span class="line">     <span class="attr">RecordingMode:</span></span><br><span class="line">       <span class="attr">RecordingFrequency:</span> <span class="string">DAILY</span></span><br></pre></td></tr></table></figure><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/08/marbot-aws-config-costs-reduction@730w.webp 730w, /images/2025/08/marbot-aws-config-costs-reduction@730w2x.webp 1460w, /images/2025/08/marbot-aws-config-costs-reduction@610w.webp 610w, /images/2025/08/marbot-aws-config-costs-reduction@610w2x.webp 1220w, /images/2025/08/marbot-aws-config-costs-reduction@450w.webp 450w, /images/2025/08/marbot-aws-config-costs-reduction@450w2x.webp 900w, /images/2025/08/marbot-aws-config-costs-reduction@330w.webp 330w, /images/2025/08/marbot-aws-config-costs-reduction@330w2x.webp 660w, /images/2025/08/marbot-aws-config-costs-reduction@545w.webp 545w, /images/2025/08/marbot-aws-config-costs-reduction@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/08/marbot-aws-config-costs-reduction@730w.png 730w, /images/2025/08/marbot-aws-config-costs-reduction@730w2x.png 1460w, /images/2025/08/marbot-aws-config-costs-reduction@610w.png 610w, /images/2025/08/marbot-aws-config-costs-reduction@610w2x.png 1220w, /images/2025/08/marbot-aws-config-costs-reduction@450w.png 450w, /images/2025/08/marbot-aws-config-costs-reduction@450w2x.png 900w, /images/2025/08/marbot-aws-config-costs-reduction@330w.png 330w, /images/2025/08/marbot-aws-config-costs-reduction@330w2x.png 660w, /images/2025/08/marbot-aws-config-costs-reduction@545w.png 545w, /images/2025/08/marbot-aws-config-costs-reduction@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/08/marbot-aws-config-costs-reduction.png" alt="marbot AWS Config costs reduction" title="marbot AWS Config costs reduction"></picture></p><p>As you can see, my AWS Config costs are close to zero after applying the change on July 23rd. This will save me around $90 per month or $1080 per year. A fun and worthwhile time investment.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>AWS Config is adding more and more resource types that it records. Therefore, your AWS Config bill is likely increasing over time as well. Besides that, volatile workloads create and delete resources that are recorded by AWS Config. For example, an Auto Scaling Group to manage a fleet of Spot EC2 instances or the Fargate tasks example from above. </p><p>The daily recording frequency approach I shared in this blog post will help you to reduce your AWS Config bill in volatile AWS environments. The downsides:</p><ul><li>Not all changes will be captured by AWS Config. Lucky me, I also have AWS CloudTrail enabled, which captures all API activity.</li><li>Changes recorded in daily mode are 300% more expensive, $0.012 instead of $0.003. If your workloads are not volatile you might spend more!</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to generate SDKs for a REST API powered by Amazon API Gateway</title>
      <link>https://cloudonaut.io/generate-sdk-for-api-gateway-rest-api/</link>
      <description>Lessons learned from generating SDKs for a virus and malware scan API with Amazon API Gateway, Smithy, and OpenAPI Generator.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/apigateway/">apigateway</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/generate-sdk-for-api-gateway-rest-api/</guid>
      <pubDate>Tue, 22 Jul 2025 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Part of our <a href="https://attachmentav.com/" target="_blank" rel="noopener">attachmentAV</a> offering is an API, powered by Amazon API Gateway (REST APIs), allowing developers to integrate virus and malware scanning into their applications. To increase discoverability and simplify integration we decided to build software develpment kits (SDKs) for popular programming languages.</p><p>Learn from my journey of finding the best way to generate SDKs for JavaScript, Java, Python, and TypeScript for a REST API.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/07/sdk@730w.webp 730w, /images/2025/07/sdk@730w2x.webp 1460w, /images/2025/07/sdk@610w.webp 610w, /images/2025/07/sdk@610w2x.webp 1220w, /images/2025/07/sdk@450w.webp 450w, /images/2025/07/sdk@450w2x.webp 900w, /images/2025/07/sdk@330w.webp 330w, /images/2025/07/sdk@330w2x.webp 660w, /images/2025/07/sdk@545w.webp 545w, /images/2025/07/sdk@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/07/sdk@730w.jpg 730w, /images/2025/07/sdk@730w2x.jpg 1460w, /images/2025/07/sdk@610w.jpg 610w, /images/2025/07/sdk@610w2x.jpg 1220w, /images/2025/07/sdk@450w.jpg 450w, /images/2025/07/sdk@450w2x.jpg 900w, /images/2025/07/sdk@330w.jpg 330w, /images/2025/07/sdk@330w2x.jpg 660w, /images/2025/07/sdk@545w.jpg 545w, /images/2025/07/sdk@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/07/sdk.jpg" alt="How to generate SDKs for a REST API powered by Amazon API Gateway" title="How to generate SDKs for a REST API powered by Amazon API Gateway"></picture></p><h2 id="Built-in-function-Generate-SDKs-for-REST-APIs"><a href="#Built-in-function-Generate-SDKs-for-REST-APIs" class="headerlink" title="Built-in function: Generate SDKs for REST APIs"></a>Built-in function: Generate SDKs for REST APIs</h2><p>First, I tried the built-in functionality <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-generate-sdk.html" target="_blank" rel="noopener">Generate SDKs for REST APIs in API Gateway</a>.</p><p>Generating the SDK was simple and fast. However, the result was mediocre. From a developer point of view, using the SDK did not provide significant convience compared to sending plain HTTP request. I was missing an layer of abstraction to simplifiy API usage.</p><h2 id="Smithy-Interface-Definition-Language-IDL"><a href="#Smithy-Interface-Definition-Language-IDL" class="headerlink" title="Smithy: Interface Definition Language (IDL)"></a>Smithy: Interface Definition Language (IDL)</h2><p>Second, I looked into <a href="https://smithy.io/" target="_blank" rel="noopener">Smithy</a>, an Interface Defintion Language developed by AWS to generate their SDKs. Who, if not AWS, must know how to generate SDKs for APIs.</p><p>The concept of an extensible, typesafe, protocol agnostic IDL in combination with tools to build client SDKs for various programming languages resonated with me. I found the layer of abstraction, that I was missing while generating SDKs with Amazon API Gateway.</p><p>The following snippet shows the model, that I used to build clients for the attachmentAV API.</p><figure class="highlight perl"><table><tr><td class="code"><pre><span class="line"><span class="variable">$version</span>: <span class="string">&quot;2.0&quot;</span></span><br><span class="line"></span><br><span class="line">namespace com.attachmentav.developer</span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> aws.protocols<span class="comment">#restJson1</span></span><br><span class="line"><span class="keyword">use</span> aws.api<span class="comment">#service</span></span><br><span class="line"></span><br><span class="line"><span class="variable">@title</span>(<span class="string">&quot;attachmentAV - Virus and Malware Scan API (SaaS)&quot;</span>)</span><br><span class="line"><span class="variable">@restJson1</span></span><br><span class="line"><span class="variable">@httpApiKeyAuth</span>(name: <span class="string">&quot;x-api-key&quot;</span>, in: <span class="string">&quot;header&quot;</span>)</span><br><span class="line"><span class="variable">@service</span>(sdkId: <span class="string">&quot;VirusMalwareScanSaaS&quot;</span>)</span><br><span class="line">service VirusMalwareScanSaaS &#123;</span><br><span class="line">  version: <span class="string">&quot;2006-03-01&quot;</span></span><br><span class="line">  operations: [CreateSyncDownloadScan, CreateSyncBinaryScan]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">structure ScanResult &#123;</span><br><span class="line">    </span><br><span class="line">  <span class="variable">@required</span></span><br><span class="line">  <span class="variable">@notProperty</span></span><br><span class="line">  status: String,</span><br><span class="line"></span><br><span class="line">  <span class="variable">@notProperty</span></span><br><span class="line">  finding: String,</span><br><span class="line"></span><br><span class="line">  <span class="variable">@required</span></span><br><span class="line">  <span class="variable">@notProperty</span></span><br><span class="line">  size: Long,</span><br><span class="line"></span><br><span class="line">  <span class="variable">@notProperty</span></span><br><span class="line">  realfiletype: String</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">map</span> DownloadHeaders &#123;</span><br><span class="line">  key: String,</span><br><span class="line">  value: String</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">structure CreateSyncDownloadScanInput &#123;</span><br><span class="line">  <span class="variable">@required</span></span><br><span class="line">  download_url: String,</span><br><span class="line">  download_headers: DownloadHeaders</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable">@idempotent</span></span><br><span class="line"><span class="variable">@http</span>(<span class="function"><span class="keyword">method</span>: &quot;<span class="title">POST</span>&quot;, <span class="title">uri</span>: &quot;/<span class="title">v1</span>/<span class="title">scan</span>/<span class="title">sync</span>/<span class="title">download</span>&quot;)</span></span><br><span class="line"><span class="function"><span class="title">operation</span> <span class="title">CreateSyncDownloadScan</span> </span>&#123;</span><br><span class="line">  input: CreateSyncDownloadScanInput,</span><br><span class="line">  output: ScanResult</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">structure CreateSyncBinaryScanInput &#123;</span><br><span class="line">  <span class="variable">@httpPayload</span></span><br><span class="line">  content: Blob</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable">@idempotent</span></span><br><span class="line"><span class="variable">@http</span>(<span class="function"><span class="keyword">method</span>: &quot;<span class="title">POST</span>&quot;, <span class="title">uri</span>: &quot;/<span class="title">v1</span>/<span class="title">scan</span>/<span class="title">sync</span>/<span class="title">binary</span>&quot;)</span></span><br><span class="line"><span class="function"><span class="title">operation</span> <span class="title">CreateSyncBinaryScan</span> </span>&#123;</span><br><span class="line">  input: CreateSyncBinaryScanInput,</span><br><span class="line">  output: ScanResult</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>However, I ran into two problems with Smithy. On the one hand, Smithy is clearly focused on generating SDKs for AWS services and therefore makes assumptions about endpoint URLs, authentication mechanisms, and more by default. Using Smithy outside these defaults was tricky for me. On the other hand, most client generators are not production-ready yet and are marked as <em>developer preview</em>. That led to generated code that was not compiling or did not work as intended.</p><h2 id="OpenAPI-Generator-Generate-from-OpenAPI-documents"><a href="#OpenAPI-Generator-Generate-from-OpenAPI-documents" class="headerlink" title="OpenAPI Generator: Generate from OpenAPI documents"></a>OpenAPI Generator: Generate from OpenAPI documents</h2><p>Third, after getting <a href="https://www.linkedin.com/posts/andreaswittig_i-am-faced-with-the-challenge-of-creating-activity-7348973941396230145-yJrv" target="_blank" rel="noopener">advice from Sebastian, Anton, Luciano on LinkedIn</a> I looked into the popular OpenAPI Generator project.</p><p>As we use an OpenAPI document to deploy the API Gateway on AWS, giving OpenAPI Generator a try was pretty simple. All I needed to do was to hand over the OpenAPI document to the generator.</p><p>But, the result was not very promising. The usability of the generated SDK was pretty bad.</p><p>The following idea brought the turnaround: define a seperate OpenAPI document with the aim of generating an SDK that is easy to use. I removed some resources and methods, modified the resource definitions, and more.</p><p>The following snippet shows the OpenAPI document optimized to generate SDKs.</p><figure class="highlight nix"><table><tr><td class="code"><pre><span class="line"><span class="params">openapi:</span> <span class="number">3.0</span>.<span class="number">0</span></span><br><span class="line"><span class="params">info:</span></span><br><span class="line">  <span class="params">description:</span> &#x27;Scan files for viruses, trojans, and other kinds of malware.&#x27;</span><br><span class="line">  <span class="params">title:</span> attachmentAV</span><br><span class="line">  <span class="params">version:</span> <span class="number">1.0</span>.<span class="number">0</span></span><br><span class="line"><span class="params">servers:</span></span><br><span class="line"><span class="operator">-</span> <span class="params">url:</span> https:<span class="symbol">//eu.developer.attachmentav.com/v1</span></span><br><span class="line"><span class="params">security:</span></span><br><span class="line"><span class="operator">-</span> <span class="params">apiKeyAuth:</span> []</span><br><span class="line"><span class="operator">-</span> <span class="params">bearerAuth:</span> []</span><br><span class="line"><span class="params">tags:</span></span><br><span class="line"><span class="operator">-</span> <span class="params">name:</span> attachmentAV</span><br><span class="line"><span class="params">paths:</span></span><br><span class="line">  <span class="operator">/</span>scan<span class="operator">/</span>sync<span class="operator">/</span><span class="params">binary:</span></span><br><span class="line">    <span class="params">post:</span></span><br><span class="line">      <span class="params">description:</span> &#x27;Upload a file, scan the file, and return the scan result.&#x27;</span><br><span class="line">      <span class="params">tags:</span> [attachmentAV]</span><br><span class="line">      <span class="params">requestBody:</span></span><br><span class="line">        <span class="params">content:</span></span><br><span class="line">          application<span class="operator">/</span><span class="params">octet-stream:</span></span><br><span class="line">            <span class="params">schema:</span></span><br><span class="line">              <span class="params">format:</span> binary</span><br><span class="line">              <span class="params">type:</span> string</span><br><span class="line">        <span class="params">required:</span> <span class="literal">true</span></span><br><span class="line">      <span class="params">responses:</span></span><br><span class="line">        &#x27;<span class="number">200</span>&#x27;:</span><br><span class="line">          <span class="params">content:</span></span><br><span class="line">            application<span class="operator">/</span><span class="params">json:</span></span><br><span class="line">              <span class="params">schema:</span></span><br><span class="line">                $<span class="params">ref:</span> &#x27;<span class="comment">#/components/schemas/ScanResult&#x27;</span></span><br><span class="line">          <span class="params">description:</span> Success</span><br><span class="line">  <span class="operator">/</span>scan<span class="operator">/</span>sync<span class="operator">/</span><span class="params">download:</span></span><br><span class="line">    <span class="params">post:</span></span><br><span class="line">      <span class="params">description:</span> &#x27;Download a file from a remote location (HTTP<span class="operator">/</span>HTTPS), scan the file, and return the scan result.&#x27;</span><br><span class="line">      <span class="params">tags:</span> [ attachmentAV ]</span><br><span class="line">      <span class="params">requestBody:</span></span><br><span class="line">        <span class="params">content:</span></span><br><span class="line">          application<span class="operator">/</span><span class="params">json:</span></span><br><span class="line">            <span class="params">schema:</span></span><br><span class="line">              $<span class="params">ref:</span> &#x27;<span class="comment">#/components/schemas/SyncDownloadScanRequest&#x27;</span></span><br><span class="line">        <span class="params">required:</span> <span class="literal">true</span></span><br><span class="line">      <span class="params">responses:</span></span><br><span class="line">        &#x27;<span class="number">200</span>&#x27;:</span><br><span class="line">          <span class="params">content:</span></span><br><span class="line">            application<span class="operator">/</span><span class="params">json:</span></span><br><span class="line">              <span class="params">schema:</span></span><br><span class="line">                $<span class="params">ref:</span> &#x27;<span class="comment">#/components/schemas/ScanResult&#x27;</span></span><br><span class="line">          <span class="params">description:</span> Success</span><br><span class="line">  <span class="operator">/</span>scan<span class="operator">/</span>sync<span class="operator">/</span><span class="params">s3:</span></span><br><span class="line">    <span class="params">post:</span></span><br><span class="line">      <span class="params">description:</span> &#x27;Download a file from S3, scan the file, and return the scan result. A bucket policy is required to grant attachmentAV access to the S3 objects.&#x27;</span><br><span class="line">      <span class="params">tags:</span> [attachmentAV]</span><br><span class="line">      <span class="params">requestBody:</span></span><br><span class="line">        <span class="params">content:</span></span><br><span class="line">          application<span class="operator">/</span><span class="params">json:</span></span><br><span class="line">            <span class="params">schema:</span></span><br><span class="line">              $<span class="params">ref:</span> &#x27;<span class="comment">#/components/schemas/SyncS3ScanRequest&#x27;</span></span><br><span class="line">        <span class="params">required:</span> <span class="literal">true</span></span><br><span class="line">      <span class="params">responses:</span></span><br><span class="line">        &#x27;<span class="number">200</span>&#x27;:</span><br><span class="line">          <span class="params">content:</span></span><br><span class="line">            application<span class="operator">/</span><span class="params">json:</span></span><br><span class="line">              <span class="params">schema:</span></span><br><span class="line">                $<span class="params">ref:</span> &#x27;<span class="comment">#/components/schemas/ScanResult&#x27;</span></span><br><span class="line">          <span class="params">description:</span> Success</span><br><span class="line"><span class="params">components:</span></span><br><span class="line">  <span class="params">schemas:</span></span><br><span class="line">    <span class="params">ScanResult:</span></span><br><span class="line">      <span class="params">example:</span></span><br><span class="line">        <span class="params">size:</span> size</span><br><span class="line">        <span class="params">realfiletype:</span> realfiletype</span><br><span class="line">        <span class="params">finding:</span> finding</span><br><span class="line">        <span class="params">status:</span> status</span><br><span class="line">      <span class="params">properties:</span></span><br><span class="line">        <span class="params">status:</span></span><br><span class="line">          <span class="params">type:</span> string</span><br><span class="line">        <span class="params">finding:</span></span><br><span class="line">          <span class="params">type:</span> string</span><br><span class="line">        <span class="params">size:</span></span><br><span class="line">          <span class="params">type:</span> string</span><br><span class="line">        <span class="params">realfiletype:</span></span><br><span class="line">          <span class="params">type:</span> string</span><br><span class="line">      <span class="params">type:</span> object</span><br><span class="line">    <span class="params">SyncDownloadScanRequest:</span></span><br><span class="line">      <span class="params">properties:</span></span><br><span class="line">        <span class="params">download_url:</span></span><br><span class="line">          <span class="params">type:</span> string</span><br><span class="line">        <span class="params">download_headers:</span></span><br><span class="line">          <span class="params">additionalProperties:</span></span><br><span class="line">            <span class="params">type:</span> string</span><br><span class="line">          <span class="params">type:</span> object</span><br><span class="line">      <span class="params">required:</span></span><br><span class="line">      <span class="operator">-</span> download_url</span><br><span class="line">      <span class="params">type:</span> object</span><br><span class="line">    <span class="params">SyncS3ScanRequest:</span></span><br><span class="line">      <span class="params">properties:</span></span><br><span class="line">        <span class="params">bucket:</span></span><br><span class="line">          <span class="params">type:</span> string</span><br><span class="line">        <span class="params">key:</span></span><br><span class="line">          <span class="params">type:</span> string</span><br><span class="line">        <span class="params">version:</span></span><br><span class="line">          <span class="params">type:</span> string</span><br><span class="line">      <span class="params">required:</span></span><br><span class="line">      <span class="operator">-</span> bucket</span><br><span class="line">      <span class="operator">-</span> key</span><br><span class="line">      <span class="params">type:</span> object</span><br><span class="line">  <span class="params">securitySchemes:</span></span><br><span class="line">    <span class="params">apiKeyAuth:</span></span><br><span class="line">      <span class="params">in:</span> header</span><br><span class="line">      <span class="params">name:</span> x-api-key</span><br><span class="line">      <span class="params">type:</span> apiKey</span><br><span class="line">    <span class="params">bearerAuth:</span></span><br><span class="line">      <span class="params">type:</span> http</span><br><span class="line">      <span class="params">scheme:</span> bearer</span><br></pre></td></tr></table></figure><p>I’m pleased with the results (see <a href="https://github.com/widdix/attachmentav-sdk-js" target="_blank" rel="noopener">attachmentav-sdk-js</a>, <a href="https://github.com/widdix/attachmentav-sdk-java" target="_blank" rel="noopener">attachmentav-sdk-java</a>, <a href="https://github.com/widdix/attachmentav-sdk-python" target="_blank" rel="noopener">attachmentav-sdk-python</a>, and <a href="https://github.com/widdix/attachmentav-sdk-ts" target="_blank" rel="noopener">attachmentav-sdk-ts</a>).</p><p>The following snippet shows how to scan a file for viruses and malware.</p><figure class="highlight ts"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">AttachmentAVApi</span>, <span class="title class_">Configuration</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@attachmentav/virus-scan-sdk-ts&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> fs <span class="keyword">from</span> <span class="string">&#x27;fs&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> config = <span class="keyword">new</span> <span class="title class_">Configuration</span>(&#123;</span><br><span class="line">  <span class="attr">apiKey</span>: <span class="string">&#x27;&lt;API_KEY_PLACEHOLDER&gt;&#x27;</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> api = <span class="keyword">new</span> <span class="title class_">AttachmentAVApi</span>(config);</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">scanFile</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> fileBuffer = fs.<span class="title function_">readFileSync</span>(<span class="string">&#x27;./demo.txt&#x27;</span>);</span><br><span class="line">  <span class="keyword">const</span> blob = <span class="keyword">new</span> <span class="title class_">Blob</span>([fileBuffer]);</span><br><span class="line">  <span class="keyword">const</span> scanResult = <span class="keyword">await</span> api.<span class="title function_">scanSyncBinaryPost</span>(&#123;</span><br><span class="line">    <span class="attr">body</span>: blob</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Sync binary scan result:&#x27;</span>, scanResult);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">scanFile</span>();</span><br></pre></td></tr></table></figure><blockquote><p>Looking for a way to scan files for viruses, trojans and other kinds of malware. <a href="https://attachmentav.com/solution/virus-malware-scan-api/" target="_blank" rel="noopener">Try the Virus and Malware Scan API by attachmentAV!</a></p></blockquote><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Building SDKs for an API increases discoverability and simplifies integration. Implementing and maintaining SDKs by writing code manually is a huge effort. For us, using the OpenAPI Generator with an optimized OpenAPI document brought the best results.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Simplified AMI deletion: new feature streamlines cleanup</title>
      <link>https://cloudonaut.io/simplified-ami-deletion/</link>
      <description>Learn how AWS's new DeleteAssociatedSnapshots feature simplifies AMI cleanup by automatically deleting EBS snapshots during AMI deregistration, reducing API calls and preventing orphaned resources that inflate costs.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/ebs/">ebs</category>
      <guid isPermaLink="true">https://cloudonaut.io/simplified-ami-deletion/</guid>
      <pubDate>Thu, 03 Jul 2025 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>For years, deleting an Amazon Machine Image (AMI) required a cumbersome two-step process: first deregistering the AMI, then manually deleting the underlying EBS snapshots. Forgetting that second step was costly—orphaned snapshots would accumulate, causing AWS bills to grow steadily over time.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/07/step-by-step@730w.webp 730w, /images/2025/07/step-by-step@730w2x.webp 1460w, /images/2025/07/step-by-step@610w.webp 610w, /images/2025/07/step-by-step@610w2x.webp 1220w, /images/2025/07/step-by-step@450w.webp 450w, /images/2025/07/step-by-step@450w2x.webp 900w, /images/2025/07/step-by-step@330w.webp 330w, /images/2025/07/step-by-step@330w2x.webp 660w, /images/2025/07/step-by-step@545w.webp 545w, /images/2025/07/step-by-step@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/07/step-by-step@730w.jpg 730w, /images/2025/07/step-by-step@730w2x.jpg 1460w, /images/2025/07/step-by-step@610w.jpg 610w, /images/2025/07/step-by-step@610w2x.jpg 1220w, /images/2025/07/step-by-step@450w.jpg 450w, /images/2025/07/step-by-step@450w2x.jpg 900w, /images/2025/07/step-by-step@330w.jpg 330w, /images/2025/07/step-by-step@330w2x.jpg 660w, /images/2025/07/step-by-step@545w.jpg 545w, /images/2025/07/step-by-step@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/07/step-by-step.jpg" alt="Simplified AMI deletion" title="Simplified AMI deletion"></picture></p><p>A recent AWS feature release caught my attention: <a href="https://aws.amazon.com/about-aws/whats-new/2025/06/amazon-ec2-delete-underlying-ebs-snapshots-deregistering-amis/" target="_blank" rel="noopener">Amazon EC2 now enables you to delete underlying EBS snapshots when deregistering AMIs</a>. As the author of <a href="https://github.com/widdix/aws-amicleaner" target="_blank" rel="noopener">aws-amicleaner</a>, a tool designed to clean up AMIs based on configurable rules, I’m excited about the operational improvements this brings.</p><h2 id="Streamlined-implementation"><a href="#Streamlined-implementation" class="headerlink" title="Streamlined implementation"></a>Streamlined implementation</h2><p>With this new feature, you can now delete an AMI along with its associated snapshots in a single API call. Here’s a JavaScript implementation using the AWS SDK v3:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">EC2Client</span>, <span class="title class_">DeregisterImageCommand</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-sdk/client-ec2&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">deleteAMI</span>(<span class="params">amiId</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> ec2 = <span class="keyword">new</span> <span class="title class_">EC2Client</span>(&#123;&#125;);</span><br><span class="line">  <span class="keyword">return</span> ec2.<span class="title function_">send</span>(<span class="keyword">new</span> <span class="title class_">DeregisterImageCommand</span>(&#123;</span><br><span class="line">    <span class="title class_">ImageId</span>: amiId,</span><br><span class="line">    <span class="title class_">DeleteAssociatedSnapshots</span>: <span class="literal">true</span></span><br><span class="line">  &#125;));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Benefits-of-the-new-approach"><a href="#Benefits-of-the-new-approach" class="headerlink" title="Benefits of the new approach"></a>Benefits of the new approach</h2><p>This enhancement eliminates the need for at least two additional API calls that were previously required to:</p><ol><li>Retrieve the AMI’s block device mappings</li><li>Delete each associated snapshot individually</li></ol><p>The result is a more efficient, reliable AMI cleanup process that reduces both API overhead and the risk of orphaned resources.</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>This AWS feature represents a significant improvement for infrastructure management. AMI deletion is now faster, more reliable, and less prone to human error—a welcome change for anyone managing EC2 images at scale.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Getting ISO 27001 certified as a 2-person company</title>
      <link>https://cloudonaut.io/getting-iso-27001-certified-as-a-2-person-company/</link>
      <description>
        <![CDATA[<p>For more than five years, we have been selling software to customers worldwide. But so far, we mainly sold software solutions that custom]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/getting-iso-27001-certified-as-a-2-person-company/</guid>
      <pubDate>Thu, 10 Apr 2025 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>For more than five years, we have been selling software to customers worldwide. But so far, we mainly sold software solutions that customers run on their own cloud infrastructure. Last year, we started attachmentAV, a Software-as-a-Service (SaaS) solution to protect Jira, Confluence, Salesforce, and other applications from viruses and malware. To scan files for malware, attachmentAV sends those files to our backend, where we run an antivirus engine powered by Sophos. So we are facing the challenge of building trust with potential customers and proof that we take information security seriously and protect their data. Many prospects asked about an ISO 27001 certification. That’s why we decided to prepare for an ISO 27001 audit in February 2025.</p><p>We had doubts about getting ISO 27001 certified. First, we imagined that getting ISO 27001 certified is not for 2-person companies, as we are. Second, we expected the process to involve a lot of paperwork and bureaucracy without adding any real value. But things turned out differently.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/04/secure-pexels-padrinan-2882659@730w.webp 730w, /images/2025/04/secure-pexels-padrinan-2882659@730w2x.webp 1460w, /images/2025/04/secure-pexels-padrinan-2882659@610w.webp 610w, /images/2025/04/secure-pexels-padrinan-2882659@610w2x.webp 1220w, /images/2025/04/secure-pexels-padrinan-2882659@450w.webp 450w, /images/2025/04/secure-pexels-padrinan-2882659@450w2x.webp 900w, /images/2025/04/secure-pexels-padrinan-2882659@330w.webp 330w, /images/2025/04/secure-pexels-padrinan-2882659@330w2x.webp 660w, /images/2025/04/secure-pexels-padrinan-2882659@545w.webp 545w, /images/2025/04/secure-pexels-padrinan-2882659@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/04/secure-pexels-padrinan-2882659@730w.jpg 730w, /images/2025/04/secure-pexels-padrinan-2882659@730w2x.jpg 1460w, /images/2025/04/secure-pexels-padrinan-2882659@610w.jpg 610w, /images/2025/04/secure-pexels-padrinan-2882659@610w2x.jpg 1220w, /images/2025/04/secure-pexels-padrinan-2882659@450w.jpg 450w, /images/2025/04/secure-pexels-padrinan-2882659@450w2x.jpg 900w, /images/2025/04/secure-pexels-padrinan-2882659@330w.jpg 330w, /images/2025/04/secure-pexels-padrinan-2882659@330w2x.jpg 660w, /images/2025/04/secure-pexels-padrinan-2882659@545w.jpg 545w, /images/2025/04/secure-pexels-padrinan-2882659@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/04/secure-pexels-padrinan-2882659.jpg" alt="Getting ISO 27001 certified as a 2-person company" title="Getting ISO 27001 certified as a 2-person company"></picture></p><h2 id="What-is-ISO-27001"><a href="#What-is-ISO-27001" class="headerlink" title="What is ISO 27001?"></a>What is ISO 27001?</h2><p>ISO 27001 is an international standard for putting an information security management system (ISMS) in place to meet the following requirements:</p><ul><li>Identify and assess information security risks, including threats and weaknesses.</li><li>Put in place effective security measures or other ways to handle unacceptable risks.</li><li>Maintain a process to regularly review and update security controls as needed.</li></ul><h2 id="Our-journey-to-ISO-27001"><a href="#Our-journey-to-ISO-27001" class="headerlink" title="Our journey to ISO 27001"></a>Our journey to ISO 27001</h2><p>Our research showed that there are two main ways to prepare for an ISO 27001 audit:</p><ul><li>Work with a consultancy helping you to implement an ISMS and the necessary procedures.</li><li>Buy a tool that guides you through the process in an automated way.</li></ul><p>We decided to go with the second option. We looked at a few tools and chose Sprinto, which describes itself as a continuous security and compliance platform.</p><p>First, we set up policies and controls that define who is responsible for which part of information security in our company with the help of the Sprinto team. Second, we started implementing the controls. Luckily, Sprinto comes with integrations for most of the tools and platforms we use: Amazon Web Services, Google Workspaces, GitHub, and many more. So implementing controls means configuring the integrations and going through the checks in Sprinto. More on those checks in the following.</p><p>It took us eight weeks to implement the controls. Then, we handed over the collected evidence to an external auditor. And nine weeks after we started the project, we hold the ISO 27001 certificate in our hands.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/04/iso-27001@730w.webp 730w, /images/2025/04/iso-27001@730w2x.webp 1460w, /images/2025/04/iso-27001@610w.webp 610w, /images/2025/04/iso-27001@610w2x.webp 1220w, /images/2025/04/iso-27001@450w.webp 450w, /images/2025/04/iso-27001@450w2x.webp 900w, /images/2025/04/iso-27001@330w.webp 330w, /images/2025/04/iso-27001@330w2x.webp 660w, /images/2025/04/iso-27001@545w.webp 545w, /images/2025/04/iso-27001@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/04/iso-27001@730w.jpg 730w, /images/2025/04/iso-27001@730w2x.jpg 1460w, /images/2025/04/iso-27001@610w.jpg 610w, /images/2025/04/iso-27001@610w2x.jpg 1220w, /images/2025/04/iso-27001@450w.jpg 450w, /images/2025/04/iso-27001@450w2x.jpg 900w, /images/2025/04/iso-27001@330w.jpg 330w, /images/2025/04/iso-27001@330w2x.jpg 660w, /images/2025/04/iso-27001@545w.jpg 545w, /images/2025/04/iso-27001@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/04/iso-27001.jpg" alt="Andreas, holding the ISO 27001 certificate in his hands" title="Andreas, holding the ISO 27001 certificate in his hands"></picture></p><h2 id="Infrastructure"><a href="#Infrastructure" class="headerlink" title="Infrastructure"></a>Infrastructure</h2><p>100% of our infrastructure is hosted on Amazon Web Services. Sprinto provides an integration that connects with the AWS API and creates a list of all assets. Those assets need to get classified as non-production or production. Then, Sprinto runs checks against the production assets to ensure, they comply with the specified controls.</p><p>It took us a while to roll out the Sprinto integration to all our AWS accounts because there was no way to automate the process. Luckily, Sprinto released a new feature allowing us to benefit from AWS Organizations and CloudFormation StackSets to roll out the integration to all AWS accounts.</p><p>Even though, our AWS infrastructure follows security best practices, Sprinto created a list of 100+ checks that needed to be addressed. At the beginning, we were strictly implementing any change raised by Sprinto. But we soon realized that some of the changes were not applicable to our infrastructure. And we started to document exceptions for those cases. For example, the checks raised concerns about missing backups for some DynamoDB tables. However, we are using DynamoDB tables to store sessions and cached data that is deleted after a short period of time (TTL) and therefore does not need to be backed up. Even worse, enabling backups for those tables where a lot of data gets created and deleted would have increased costs significantly.</p><h2 id="Vulnerabilities"><a href="#Vulnerabilities" class="headerlink" title="Vulnerabilities"></a>Vulnerabilities</h2><p>Another important part is vulnerability management. And that’s an area, where we did not have any established processes before. We enabled the vulnerability management in Sprinto and connected it with GitHub’s Dependabot and AWS Inspector. Now, when a vulnerability is detected, we are getting notified, and Sprinto tracks that we roll out an update to fix the vulnerability.</p><ul><li><em>Critical Vulnerabilities</em> should be resolved in 3 days</li><li><em>High Vulnerabilities</em> should be resolved in 30 days</li><li><em>Moderate Vulnerabilities</em> should be resolved in 60 days</li><li><em>Low Vulnerabilities</em> should be resolved in 100 days</li></ul><p>Meeting those deadlines is a challenge for a 2-person company, but we are confident that we can do it. And we are happy to have a process in place that ensures that we are doing our best to keep our customers’ data secure.</p><h2 id="Staff-Access"><a href="#Staff-Access" class="headerlink" title="Staff &amp; Access"></a>Staff &amp; Access</h2><p>While we are a 2-person company, we also hire freelancers to bring in expertise for specific tasks. Those freelancers need access to GitHub or some AWS resources. So far, we were going through the access management of our systems and removed freelancers who are currently not working for us. IS0 27001 requires a process to onboard and offboard employees and freelancers. And we are glad that we can automate those tasks with Sprinto. Now, we ensure that our freelancers have the same information security standards. For example, by ensuring encryption of data-at-rest is enabled on their devices. Also, when freelancers leave, the automated checks ensure that access to all systems is removed.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Thanks to a high level of automation, it took us only nine weeks to get ISO 27001 certified. We invested about 100 hours and had a four-digit budget. We learned a lot and improved vulnerability management as well as access management significantly. Furthermore, we hope the ISO 27001 certificate will help us to build trust with prospects and customers.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>S3 Virus Scan and Malware Protection Step-by-Step Guide</title>
      <link>https://cloudonaut.io/s3-virus-scan-and-malware-protection-step-by-step-guide/</link>
      <description>Protect your AWS S3 buckets from viruses and malware with an antivirus scanning solution. This step-by-step guide explains how to implement S3 virus scanning, covering architecture and use cases for protecting object storage from malware threats.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <guid isPermaLink="true">https://cloudonaut.io/s3-virus-scan-and-malware-protection-step-by-step-guide/</guid>
      <pubDate>Mon, 03 Mar 2025 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Do users or 3rd party systems upload data to your S3 buckets? How do you ensure that viruses, trojans, ransomware and other kinds of malware are detected before causing harm?</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/02/virus@730w.webp 730w, /images/2025/02/virus@730w2x.webp 1460w, /images/2025/02/virus@610w.webp 610w, /images/2025/02/virus@610w2x.webp 1220w, /images/2025/02/virus@450w.webp 450w, /images/2025/02/virus@450w2x.webp 900w, /images/2025/02/virus@330w.webp 330w, /images/2025/02/virus@330w2x.webp 660w, /images/2025/02/virus@545w.webp 545w, /images/2025/02/virus@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/02/virus@730w.jpg 730w, /images/2025/02/virus@730w2x.jpg 1460w, /images/2025/02/virus@610w.jpg 610w, /images/2025/02/virus@610w2x.jpg 1220w, /images/2025/02/virus@450w.jpg 450w, /images/2025/02/virus@450w2x.jpg 900w, /images/2025/02/virus@330w.jpg 330w, /images/2025/02/virus@330w2x.jpg 660w, /images/2025/02/virus@545w.jpg 545w, /images/2025/02/virus@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/02/virus.jpg" alt="S3 Virus Scan and Malware Protection Step-by-Step Guide" title="S3 Virus Scan and Malware Protection Step-by-Step Guide"></picture></p><p>Besides sharing our learnings about all things AWS at cloudonaut, we’re building bucketAV, a solution to protect Amazon S3 and Cloudflare R2 from viruses and malware.</p><blockquote><p>This is the first part of our series <strong>Made by cloudonaut</strong> where we present the solutions we are building: <a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV</a>, <a href="https://attachmentav.com/" target="_blank" rel="noopener">attachmentAV</a>, <a href="https://hyperenv.com/" target="_blank" rel="noopener">HyperEnv</a>, and <a href="https://marbot.io/" target="_blank" rel="noopener">marbot</a>.</p></blockquote><h2 id="Video-S3-Virus-Scan-Step-by-Step-Guide"><a href="#Video-S3-Virus-Scan-Step-by-Step-Guide" class="headerlink" title="Video: S3 Virus Scan Step-by-Step Guide"></a>Video: S3 Virus Scan Step-by-Step Guide</h2><p>Check out our video, where we walk you through the steps required to protect your S3 buckets from viruses and malware.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=ck7GnlUjnSY">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/ck7GnlUjnSY" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="Why-protect-S3-with-an-antivirus-solution"><a href="#Why-protect-S3-with-an-antivirus-solution" class="headerlink" title="Why protect S3 with an antivirus solution?"></a>Why protect S3 with an antivirus solution?</h2><p>We started bucketAV back in 2015 because one of our consulting clients had the need to scan files uploaded by users for viruses and malware, before publishing them on their website. Most traditional antivirus solutions scan file systems but don’t come with the ability to scan an object store, like bucketAV.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/02/bucketav-motivation@730w.webp 730w, /images/2025/02/bucketav-motivation@730w2x.webp 1460w, /images/2025/02/bucketav-motivation@610w.webp 610w, /images/2025/02/bucketav-motivation@610w2x.webp 1220w, /images/2025/02/bucketav-motivation@450w.webp 450w, /images/2025/02/bucketav-motivation@450w2x.webp 900w, /images/2025/02/bucketav-motivation@330w.webp 330w, /images/2025/02/bucketav-motivation@330w2x.webp 660w, /images/2025/02/bucketav-motivation@545w.webp 545w, /images/2025/02/bucketav-motivation@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/02/bucketav-motivation@730w.png 730w, /images/2025/02/bucketav-motivation@730w2x.png 1460w, /images/2025/02/bucketav-motivation@610w.png 610w, /images/2025/02/bucketav-motivation@610w2x.png 1220w, /images/2025/02/bucketav-motivation@450w.png 450w, /images/2025/02/bucketav-motivation@450w2x.png 900w, /images/2025/02/bucketav-motivation@330w.png 330w, /images/2025/02/bucketav-motivation@330w2x.png 660w, /images/2025/02/bucketav-motivation@545w.png 545w, /images/2025/02/bucketav-motivation@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/02/bucketav-motivation.png" alt="Protect S3 from viruses and malware uploaded by users or 3rd parties" title="Protect S3 from viruses and malware uploaded by users or 3rd parties"></picture></p><p>Besides that, here is a list with the main use cases where you should consider protecting your S3 buckets with an antivirus solution:</p><ul><li>User-generated content</li><li>Comply with industry standards</li><li>Incoming files from 3rd party data providers</li><li>File storage of users and customers</li><li>Content and software distribution</li></ul><p>Next, let me explain the architecture of our antivirus solution.</p><h2 id="Architecture-Fault-Tolerant-Cost-Effective-Scalable"><a href="#Architecture-Fault-Tolerant-Cost-Effective-Scalable" class="headerlink" title="Architecture: Fault-Tolerant, Cost-Effective, Scalable"></a>Architecture: Fault-Tolerant, Cost-Effective, Scalable</h2><p>When designing the architecture for bucketAV, we had the following goals in mind:</p><ul><li>Fault Tolerance</li><li>Cost Effectiveness</li><li>Scalability</li><li>Security</li></ul><p>The architecture consists of the following building blocks:</p><ul><li><strong>S3 buckets</strong> stores files that need to be scanned.</li><li><strong>S3 Event Notifications, or EventBridge</strong> is used to trigger real-time scan jobs.</li><li><strong>SQS queue</strong> to receive and store scan jobs.</li><li><strong>Auto-scaling group</strong> to manage a fleet of EC2 instances.</li><li><strong>EC2 instance</strong> runs bucketAV and the antivirus engine (ClamAV&#x2F;Sophos).</li><li><strong>SNS topic</strong> to notify add-ons and humans about the scan results.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/02/bucketav-architecture@730w.webp 730w, /images/2025/02/bucketav-architecture@730w2x.webp 1460w, /images/2025/02/bucketav-architecture@610w.webp 610w, /images/2025/02/bucketav-architecture@610w2x.webp 1220w, /images/2025/02/bucketav-architecture@450w.webp 450w, /images/2025/02/bucketav-architecture@450w2x.webp 900w, /images/2025/02/bucketav-architecture@330w.webp 330w, /images/2025/02/bucketav-architecture@330w2x.webp 660w, /images/2025/02/bucketav-architecture@545w.webp 545w, /images/2025/02/bucketav-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/02/bucketav-architecture@730w.png 730w, /images/2025/02/bucketav-architecture@730w2x.png 1460w, /images/2025/02/bucketav-architecture@610w.png 610w, /images/2025/02/bucketav-architecture@610w2x.png 1220w, /images/2025/02/bucketav-architecture@450w.png 450w, /images/2025/02/bucketav-architecture@450w2x.png 900w, /images/2025/02/bucketav-architecture@330w.png 330w, /images/2025/02/bucketav-architecture@330w2x.png 660w, /images/2025/02/bucketav-architecture@545w.png 545w, /images/2025/02/bucketav-architecture@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/02/bucketav-architecture.png" alt="The architecture of bucketAV consists of S3, SQS, and EC2" title="The architecture of bucketAV consists of S3, SQS, and EC2"></picture></p><p>You might wonder why we use EC2 instead of Lambda or Fargate. The short answer: EC2 is cost-efficient and provides the best performance. The long answer is: <a href="https://bucketav.com/blog/why-does-bucketav-use-ec2-to-protect-s3-from-malware/" target="_blank" rel="noopener">Why does bucketAV use EC2 to protect S3 from malware?</a>.</p><h2 id="Try-bucketAV"><a href="#Try-bucketAV" class="headerlink" title="Try bucketAV"></a>Try bucketAV</h2><p>Protect Amazon S3 from viruses, trojans, ransomware, and other kinds of malware. <a href="https://bucketav.com/" target="_blank" rel="noopener">Get started with a free trial today!</a></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Reduce GitHub runner costs by leveraging EC2 spot instances</title>
      <link>https://cloudonaut.io/reduce-github-runner-costs-by-leveraging-ec2-spot-instances/</link>
      <description>Reduce GitHub Actions costs by using self-hosted runners on EC2 Spot instances. Learn how to optimize CI/CD workflows with cost-efficient AWS infrastructure.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/github/">github</category>
      <guid isPermaLink="true">https://cloudonaut.io/reduce-github-runner-costs-by-leveraging-ec2-spot-instances/</guid>
      <pubDate>Fri, 31 Jan 2025 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>We learned the hard way, that GitHub Actions is getting expensive when using GitHub-hosted runners. Back in 2023, we decided to build a solution for self-hosted runners on AWS to reduce costs. A few months later, we released <a href="https://hyperenv.com/" target="_blank" rel="noopener">HyperEnv</a> to the public. Over time, we improved our solution step by step. With the <a href="https://hyperenv.com/blog/release-2-9-0-improced-architecture-spot-instances-workflow-configuration/" target="_blank" rel="noopener">release of version 2.9.0</a> we achieved another important milestone: leverage EC2 spot instances to take advantage of unused EC2 capacity in the AWS cloud. In the following, I will share our learnings along the way.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/01/money@730w.webp 730w, /images/2025/01/money@730w2x.webp 1460w, /images/2025/01/money@610w.webp 610w, /images/2025/01/money@610w2x.webp 1220w, /images/2025/01/money@450w.webp 450w, /images/2025/01/money@450w2x.webp 900w, /images/2025/01/money@330w.webp 330w, /images/2025/01/money@330w2x.webp 660w, /images/2025/01/money@545w.webp 545w, /images/2025/01/money@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/01/money@730w.jpg 730w, /images/2025/01/money@730w2x.jpg 1460w, /images/2025/01/money@610w.jpg 610w, /images/2025/01/money@610w2x.jpg 1220w, /images/2025/01/money@450w.jpg 450w, /images/2025/01/money@450w2x.jpg 900w, /images/2025/01/money@330w.jpg 330w, /images/2025/01/money@330w2x.jpg 660w, /images/2025/01/money@545w.jpg 545w, /images/2025/01/money@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/01/money.jpg" alt="Reduce GitHub runner costs by leveraging EC2 spot instances" title="Reduce GitHub runner costs by leveraging EC2 spot instances"></picture></p><h2 id="EC2-Spot-vs-On-Demand"><a href="#EC2-Spot-vs-On-Demand" class="headerlink" title="EC2 Spot vs. On-Demand"></a>EC2 Spot vs. On-Demand</h2><p>There are three different pricing models for virtual machines on AWS.</p><ul><li><strong>On-Demand</strong> is the default, depending on the instance type, you pay an hourly fee, which is usually charged by the second. For example, an <code>m5.large</code> instance (2 vCPUs and 8 GiB of memory) costs you $0.0960 per hour in region <code>us-east-1</code>.</li><li><strong>Spot</strong> grants discounts on unused capacity in AWS’s data centers. The price changes depending on the utilization of the data centers. While writing this, an <code>m5.large</code> instance is about $0.0348 in <code>us-east-1</code> which is a 63.78% saving compared with on-demand. Here is the catch: AWS reserves the right to terminate a spot instance after 2 minutes notice, which AWS calls a spot interruption.</li><li><strong>Savings Plans</strong> are a simple deal: you commit to a specific use of EC2 instances and get a discount from AWS. The approach works best for static workloads that can be planned for 1 or even 3 years in advance.</li></ul><p>So, running GitHub runners on spot instead of on-demand instances cuts down infrastructure costs by about 60%. However, there are a few caveats to consider.</p><h2 id="Consideration-Ephemeral-Runners"><a href="#Consideration-Ephemeral-Runners" class="headerlink" title="Consideration: Ephemeral Runners"></a>Consideration: Ephemeral Runners</h2><p>There are three approaches for hosting GitHub Runners on AWS:</p><ul><li><strong>Long-running</strong>: Launch an EC2 instance, install and start GitHub runner. Keep the instance running 24&#x2F;7.</li><li><strong>Auto-scaled</strong>: Launch and terminate EC2 instances depending on the number of waiting jobs.</li><li><strong>Ephemeral</strong>: Launch an EC2 instance for every job. Terminate the machine after completing the job.</li></ul><p>A typical build job takes 5 to 15 minutes to complete. Therefore, ephemeral runners require a spot instance for a short time only, which reduces the risk of getting interrupted. That’s because AWS takes the runtime of a spot instance into consideration, when selecting a spot instance to interrupt to free up capacity.</p><p>So, ephemeral runners are a perfect fit for EC2 spot instances.</p><h2 id="Consideration-Fallback-to-On-Demand"><a href="#Consideration-Fallback-to-On-Demand" class="headerlink" title="Consideration: Fallback to On-Demand"></a>Consideration: Fallback to On-Demand</h2><p>Depending on the utilization of the data center, the availability of spot instances might be limited. It is not unlikely, that AWS rejects a request to launch a spot instance due to no capacity.</p><p>When utilizing spot instances, it is essential to be aware that the availability of these instances can fluctuate based on demand. In some cases, AWS might not have enough capacity available, resulting in your request to launch a spot instance being rejected due to no available capacity.<br>To mitigate this risk, consider implementing a fallback strategy where you automatically switch to launching on-demand instances when spot instances are not available.</p><p>One way to achieve this is by using a combination of AWS Auto Scaling and CloudWatch metrics to monitor the availability of spot instances. Based on these metrics, your Auto Scaling group can scale up or down accordingly, ensuring that your GitHub runners have access to the necessary resources.<br>By implementing such a strategy, you can minimize the impact of spot instance unavailability and maintain a stable and reliable infrastructure for your GitHub runners.</p><p>This approach ensures high availability and allows you to take advantage of the cost savings offered by spot instances while minimizing the risk associated with their availability.</p><p>Here is what to do in such a situation:</p><ol><li>Try to launch the spot instance in another availability zone, which means in another data center that might provide spot capacity.</li><li>Fallback to launching an on-demand instance.</li></ol><h2 id="Consideration-Does-the-GitHub-workflow-withstand-an-interruption"><a href="#Consideration-Does-the-GitHub-workflow-withstand-an-interruption" class="headerlink" title="Consideration: Does the GitHub workflow withstand an interruption?"></a>Consideration: Does the GitHub workflow withstand an interruption?</h2><p>While chances are low that an ephemeral runner that runs on an EC2 spot instance for a few minutes gets interrupted, it is not zero. For many GitHub workflows, it is not an issue when a job gets stuck and cancelled due to a spot interruption. A job like running unit tests, linting code, or building artifacts can typically be restarted without any side effects. However, there are jobs where an interruption might cause in corrupt state or unwanted side effects. For example, interrupting the run of <code>terraform apply</code> might cause the following job to fail because the Terraform state is still locked.</p><p>Therefore, being able to configure whether a spot instance should be used at the job level is necessary.</p><h2 id="Architecture-for-GitHub-Actions-running-on-EC2-spot-instances"><a href="#Architecture-for-GitHub-Actions-running-on-EC2-spot-instances" class="headerlink" title="Architecture for GitHub Actions running on EC2 spot instances"></a>Architecture for GitHub Actions running on EC2 spot instances</h2><p>So what would an AWS architecture for launching ephemeral GitHub runners on EC2 spot instances with a fallback to on-demand instances look like? The following diagram shows the solution that we ended up with for HyperEnv.</p><ol><li>API Gateway receives an HTTP request from GitHub.</li><li>API Gateway invokes the Lambda function named <code>webhook</code>.</li><li>The Lambda function <code>webhook</code> verifies the incoming webhook event.</li><li>The Lambda function <code>webhook</code> starts an execution of the Step Function <code>runner-orchestrator</code>.</li><li>The Step Function invokes the Lambda function <code>consumer</code> which tries to launch a spot instance.</li><li>In case a spot instance is not available in the selected availability zone, the Step Function retries launching a spot instance in another availability zone by calling the Lambda function <code>consumer</code> a second time.</li><li>In case it is not possible to launch a spot instance, the Step Function continues and calls the Lambda function <code>consumer</code> again to launch an on-demand instance.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/01/hyperenv-architecture@730w.webp 730w, /images/2025/01/hyperenv-architecture@730w2x.webp 1460w, /images/2025/01/hyperenv-architecture@610w.webp 610w, /images/2025/01/hyperenv-architecture@610w2x.webp 1220w, /images/2025/01/hyperenv-architecture@450w.webp 450w, /images/2025/01/hyperenv-architecture@450w2x.webp 900w, /images/2025/01/hyperenv-architecture@330w.webp 330w, /images/2025/01/hyperenv-architecture@330w2x.webp 660w, /images/2025/01/hyperenv-architecture@545w.webp 545w, /images/2025/01/hyperenv-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/01/hyperenv-architecture@730w.png 730w, /images/2025/01/hyperenv-architecture@730w2x.png 1460w, /images/2025/01/hyperenv-architecture@610w.png 610w, /images/2025/01/hyperenv-architecture@610w2x.png 1220w, /images/2025/01/hyperenv-architecture@450w.png 450w, /images/2025/01/hyperenv-architecture@450w2x.png 900w, /images/2025/01/hyperenv-architecture@330w.png 330w, /images/2025/01/hyperenv-architecture@330w2x.png 660w, /images/2025/01/hyperenv-architecture@545w.png 545w, /images/2025/01/hyperenv-architecture@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/01/hyperenv-architecture.png" alt="Running self-hosted GitHub runners on EC2 spot instances" title="Running self-hosted GitHub runners on EC2 spot instances"></picture></p><h2 id="Give-HyperEnv-a-try"><a href="#Give-HyperEnv-a-try" class="headerlink" title="Give HyperEnv a try!"></a>Give HyperEnv a try!</h2><p>Do you prefer a production-ready and well-maintained solution instead of building this on your own? Check out our solution for <a href="https://hyperenv.com/" target="_blank" rel="noopener">self-hosted GitHub Actions runners for AWS: HyperEnv</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>10th anniversary - our story</title>
      <link>https://cloudonaut.io/10th-anniversary-our-story/</link>
      <description>We are celebrating the 10th anniversary of our company and want to share our story.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/10th-anniversary-our-story/</guid>
      <pubDate>Wed, 22 Jan 2025 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Today, we -the Wittig brothers- are celebrating the 10th anniversary of our company. Here we want to tell our story and say thank you!</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/01/party@730w.webp 730w, /images/2025/01/party@730w2x.webp 1460w, /images/2025/01/party@610w.webp 610w, /images/2025/01/party@610w2x.webp 1220w, /images/2025/01/party@450w.webp 450w, /images/2025/01/party@450w2x.webp 900w, /images/2025/01/party@330w.webp 330w, /images/2025/01/party@330w2x.webp 660w, /images/2025/01/party@545w.webp 545w, /images/2025/01/party@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/01/party@730w.jpg 730w, /images/2025/01/party@730w2x.jpg 1460w, /images/2025/01/party@610w.jpg 610w, /images/2025/01/party@610w2x.jpg 1220w, /images/2025/01/party@450w.jpg 450w, /images/2025/01/party@450w2x.jpg 900w, /images/2025/01/party@330w.jpg 330w, /images/2025/01/party@330w2x.jpg 660w, /images/2025/01/party@545w.jpg 545w, /images/2025/01/party@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/01/party.jpg" alt="10th anniversary - our story" title="10th anniversary - our story"></picture></p><h2 id="Prehistory"><a href="#Prehistory" class="headerlink" title="Prehistory"></a>Prehistory</h2><p>In 2012, Michael and I joined the same team at Tullius Walden Bank with the mission to build a trading platform for retail customers. We set ourselves the goal of shipping new features every week. Soon, we realized that we needed full control over the infrastructure and deployment process to release a new version weekly. Besides that, we had to fulfill the requirement of hosting the web application in two data centers to achieve high availability. Our research led us to Amazon Web Services. Ten years ago, it was almost inconceivable in Germany that a bank would move important business processes to the cloud. But we were excited about the possibilities offered by AWS and simply went for it. Step by step, we learned how to build highly available architectures on AWS and how to reduce the risk of shipping improvements and features weekly or even daily. Until 2014, we helped to migrate the whole IT infrastructure to the cloud, which ended with us emptying the racks in the colocating data center.</p><p>Unfortunately, the business model behind the trading platform did not work. And therefore, Tullius Walden Bank shut down their retail business, and we were given notice.</p><h2 id="Founding-a-company"><a href="#Founding-a-company" class="headerlink" title="Founding a company"></a>Founding a company</h2><p>In 2015, We decided to found our own company, <a href="http://widdix.net/" target="_blank" rel="noopener">widdix</a>. The name “widdix” is a variation of our surname and a domain name that we had already registered. At the beginning, we didn’t really know what to focus on. As we had built web and mobile applications and had some experience with AWS, we decided to start offering three services: web development, mobile app development, and cloud computing. After a few months, we noticed a high demand for experts in Amazon Web Services as organizations in Europe started to take a first look into cloud computing. Also, Manning asked us to write a book about Amazon Web Services, which we did to fill in the free slots between projects. Moreover, we started to work as freelancers for an early AWS consulting partner in Germany, <a href="https://www.tecracer.com/" target="_blank" rel="noopener">tecRacer</a>.</p><h2 id="Freelancing-and-Consulting"><a href="#Freelancing-and-Consulting" class="headerlink" title="Freelancing and Consulting"></a>Freelancing and Consulting</h2><p>The story of how we moved the first German bank to the cloud and our book Amazon Web Services in Action were a perfect door opener for winning consulting projects. Over the years, we consulted countless small and large organizations on their way to the cloud. And if you know us, then you know that we don’t just talk the talk, we also walk the walk. We helped small startups to automate their release process and large enterprises to lay the foundation for a change process. We got to know many people and companies during this exciting time. And we shared what we learned with the community via <a href="https://cloudonaut.io/">cloudonaut</a> and open-source projects (e.g., <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">widdix&#x2F;aws-cf-templates</a> or <a href="https://github.com/widdix/s3-getobject-accelerator" target="_blank" rel="noopener">widdix&#x2F;s3-getobject-accelerator</a>). On top of that, we learned how to negotiate a reasonable daily rate and run a business.</p><p>A serious portrait from 2017.<br><picture class="img-fluid"><source type="image/webp" srcset="../images/2025/01/2017-team@730w.webp 730w, ../images/2025/01/2017-team@730w2x.webp 1460w, ../images/2025/01/2017-team@610w.webp 610w, ../images/2025/01/2017-team@610w2x.webp 1220w, ../images/2025/01/2017-team@450w.webp 450w, ../images/2025/01/2017-team@450w2x.webp 900w, ../images/2025/01/2017-team@330w.webp 330w, ../images/2025/01/2017-team@330w2x.webp 660w, ../images/2025/01/2017-team@545w.webp 545w, ../images/2025/01/2017-team@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="../images/2025/01/2017-team@730w.jpg 730w, ../images/2025/01/2017-team@730w2x.jpg 1460w, ../images/2025/01/2017-team@610w.jpg 610w, ../images/2025/01/2017-team@610w2x.jpg 1220w, ../images/2025/01/2017-team@450w.jpg 450w, ../images/2025/01/2017-team@450w2x.jpg 900w, ../images/2025/01/2017-team@330w.jpg 330w, ../images/2025/01/2017-team@330w2x.jpg 660w, ../images/2025/01/2017-team@545w.jpg 545w, ../images/2025/01/2017-team@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="../images/2025/01/2017-team.jpg" alt="A serious portrait from 2017" title="A serious portrait from 2017"></picture></p><h2 id="Building-products-on-the-side"><a href="#Building-products-on-the-side" class="headerlink" title="Building products on the side"></a>Building products on the side</h2><p>We had a lot of fun working as consultants, but we always had the dream of developing and selling our own software. There were two reasons for this. Firstly, we enjoy developing products based on customer feedback. Secondly, from a business perspective, selling software is much more exciting than selling your working time. That’s why we worked on products on the side. We aimed to spend at least 10% of our time on side projects.</p><p>We’re still doing consulting and in need of a serious portrait in 2019.<br><picture class="img-fluid"><source type="image/webp" srcset="../images/2025/01/2019-team@730w.webp 730w, ../images/2025/01/2019-team@730w2x.webp 1460w, ../images/2025/01/2019-team@610w.webp 610w, ../images/2025/01/2019-team@610w2x.webp 1220w, ../images/2025/01/2019-team@450w.webp 450w, ../images/2025/01/2019-team@450w2x.webp 900w, ../images/2025/01/2019-team@330w.webp 330w, ../images/2025/01/2019-team@330w2x.webp 660w, ../images/2025/01/2019-team@545w.webp 545w, ../images/2025/01/2019-team@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="../images/2025/01/2019-team@730w.jpg 730w, ../images/2025/01/2019-team@730w2x.jpg 1460w, ../images/2025/01/2019-team@610w.jpg 610w, ../images/2025/01/2019-team@610w2x.jpg 1220w, ../images/2025/01/2019-team@450w.jpg 450w, ../images/2025/01/2019-team@450w2x.jpg 900w, ../images/2025/01/2019-team@330w.jpg 330w, ../images/2025/01/2019-team@330w2x.jpg 660w, ../images/2025/01/2019-team@545w.jpg 545w, ../images/2025/01/2019-team@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="../images/2025/01/2019-team.jpg" alt="We're still doing consulting and in need of a serious portrait in 2019." title="We're still doing consulting and in need of a serious portrait in 2019."></picture></p><p>Step by step, we tried plenty of things and failed again and again. Here is an incomplete list of projects in which we invested a lot of time and energy but which never took off and were therefore discontinued.</p><ul><li><strong>PageChimp</strong> a website builder for medical doctors</li><li><strong>Time Series Guru</strong> a time series database as a service</li><li><strong>Education apps</strong> for iPad</li></ul><p>But luckily, some of our side projects were successful and grew over time.</p><ul><li><strong><a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV</a></strong> protects Amazon S3 and Cloudflare R2 from viruses and malware</li><li><strong><a href="https://marbot.io/" target="_blank" rel="noopener">marbot</a></strong> configures and receives alarms and notifications from AWS via Slack or Microsoft Teams</li></ul><p>Besides building software products, we also produced and tried to sell content like self-published e-books and video courses. In 2020, we improved our video setup.<br><picture class="img-fluid"><source type="image/webp" srcset="../images/2025/01/2020-video@730w.webp 730w, ../images/2025/01/2020-video@730w2x.webp 1460w, ../images/2025/01/2020-video@610w.webp 610w, ../images/2025/01/2020-video@610w2x.webp 1220w, ../images/2025/01/2020-video@450w.webp 450w, ../images/2025/01/2020-video@450w2x.webp 900w, ../images/2025/01/2020-video@330w.webp 330w, ../images/2025/01/2020-video@330w2x.webp 660w, ../images/2025/01/2020-video@545w.webp 545w, ../images/2025/01/2020-video@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="../images/2025/01/2020-video@730w.jpg 730w, ../images/2025/01/2020-video@730w2x.jpg 1460w, ../images/2025/01/2020-video@610w.jpg 610w, ../images/2025/01/2020-video@610w2x.jpg 1220w, ../images/2025/01/2020-video@450w.jpg 450w, ../images/2025/01/2020-video@450w2x.jpg 900w, ../images/2025/01/2020-video@330w.jpg 330w, ../images/2025/01/2020-video@330w2x.jpg 660w, ../images/2025/01/2020-video@545w.jpg 545w, ../images/2025/01/2020-video@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="../images/2025/01/2020-video.jpg" alt="Improved video setup from 2020" title="Improved video setup from 2020"></picture></p><h2 id="We-are-a-software-business"><a href="#We-are-a-software-business" class="headerlink" title="We are a software business"></a>We are a software business</h2><p>Since 2023, our dream has come true, we have handed over our consulting customers and are now a software company. This gives us the time and energy to further develop our existing products and, at the same time, build up new products. We have launched a whole range of new products.</p><p>You could say that our company is a lifestyle business. We have a lot of time for deep work and can organize our lives in a self-determined way. So far, we have decided against hiring employees. From time to time, we work with freelancers who are experts in their field. Over time, we have involuntarily learned a lot about marketing, sales, accounting and taxes.</p><p>With <a href="https://attachmentav.com/" target="_blank" rel="noopener">attachmentAV</a> we bring virus and malware protection to more and more platforms.</p><ul><li>Jira</li><li>Confluence</li><li>Salesforce</li><li>WordPress</li><li>Virus and Malware Scan API (SaaS)</li><li>Virus and Malware Scan API (Self-hosted on AWS)</li></ul><p>Besides that, we are expanding into new territories as well. <a href="https://hyperenv.com/" target="_blank" rel="noopener">HyperEnv</a> is our solution to deploy self-hosted GitHub Actions runners on AWS with ease.</p><p>Occasionally, we get sidetracked from building products. For example, by revising our book Amazon Web Services in Action for the third time.<br><picture class="img-fluid"><source type="image/webp" srcset="../images/2025/01/2023-book@730w.webp 730w, ../images/2025/01/2023-book@730w2x.webp 1460w, ../images/2025/01/2023-book@610w.webp 610w, ../images/2025/01/2023-book@610w2x.webp 1220w, ../images/2025/01/2023-book@450w.webp 450w, ../images/2025/01/2023-book@450w2x.webp 900w, ../images/2025/01/2023-book@330w.webp 330w, ../images/2025/01/2023-book@330w2x.webp 660w, ../images/2025/01/2023-book@545w.webp 545w, ../images/2025/01/2023-book@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="../images/2025/01/2023-book@730w.jpg 730w, ../images/2025/01/2023-book@730w2x.jpg 1460w, ../images/2025/01/2023-book@610w.jpg 610w, ../images/2025/01/2023-book@610w2x.jpg 1220w, ../images/2025/01/2023-book@450w.jpg 450w, ../images/2025/01/2023-book@450w2x.jpg 900w, ../images/2025/01/2023-book@330w.jpg 330w, ../images/2025/01/2023-book@330w2x.jpg 660w, ../images/2025/01/2023-book@545w.jpg 545w, ../images/2025/01/2023-book@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="../images/2025/01/2023-book.jpg" alt="Unbelievable, the 3rd edition of our book Amazon Web Services in Action was published." title="Unbelievable, the 3rd edition of our book Amazon Web Services in Action was published."></picture></p><h2 id="Thank-you"><a href="#Thank-you" class="headerlink" title="Thank you!"></a>Thank you!</h2><p>We would like to thank all our business partners and supporters who have accompanied us over the years. We look forward to the next 10 years.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to move a DynamoDB table to another region or account?</title>
      <link>https://cloudonaut.io/dynamodb-move-region-account-backup-restore/</link>
      <description>Need to move your DynamoDB table? Learn about three migration methods: backup and restore, S3 export/import, and DynamoDB CLI tool dynein.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/dynamodb/">dynamodb</category>
      <guid isPermaLink="true">https://cloudonaut.io/dynamodb-move-region-account-backup-restore/</guid>
      <pubDate>Tue, 14 Jan 2025 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>How to move data from one DynamoDB table to another? For example, when it becomes necessary to move a DynamoDB table to another account or region.</p><p>In the following blog post, I will discuss three different options to move DynamoDB tables by backing up and restoring data. All three options work in harmony with Infrastructure as Code tools such as Terraform and CloudFormation. Be aware that a downtime is necessary for both options to avoid data loss.</p><ol><li>Restore table from backup</li><li>S3 Export and Import</li><li>Copy data with DynamoDB CLI tool <code>dynein</code></li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/01/move@730w.webp 730w, /images/2025/01/move@730w2x.webp 1460w, /images/2025/01/move@610w.webp 610w, /images/2025/01/move@610w2x.webp 1220w, /images/2025/01/move@450w.webp 450w, /images/2025/01/move@450w2x.webp 900w, /images/2025/01/move@330w.webp 330w, /images/2025/01/move@330w2x.webp 660w, /images/2025/01/move@545w.webp 545w, /images/2025/01/move@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/01/move@730w.jpg 730w, /images/2025/01/move@730w2x.jpg 1460w, /images/2025/01/move@610w.jpg 610w, /images/2025/01/move@610w2x.jpg 1220w, /images/2025/01/move@450w.jpg 450w, /images/2025/01/move@450w2x.jpg 900w, /images/2025/01/move@330w.jpg 330w, /images/2025/01/move@330w2x.jpg 660w, /images/2025/01/move@545w.jpg 545w, /images/2025/01/move@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/01/move.jpg" alt="How to move a DynamoDB table by backing up and restoring data" title="How to move a DynamoDB table by backing up and restoring data"></picture></p><h2 id="Backup-and-Restore"><a href="#Backup-and-Restore" class="headerlink" title="Backup and Restore"></a>Backup and Restore</h2><p>The first approach is to back up and restore a DynamoDB table. AWS provides two ways to do so:</p><ul><li>DynamoDB’s built-in backup and restore functionality</li><li>AWS Backup service</li></ul><p>Use the AWS CLI to create an on-demand backup of the source table <code>demo-source</code> with DynamoDB’s built-in backup and restore functionality.</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">$ aws dynamodb create-backup --table-name demo-source --backup-name snapshot</span><br><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;BackupDetails&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;BackupArn&quot;</span>: <span class="string">&quot;arn:aws:dynamodb:eu-west-1:111111111111:table/demo-source/backup/01736522438603-834a89d1&quot;</span>,</span><br><span class="line">        <span class="string">&quot;BackupName&quot;</span>: <span class="string">&quot;snapshot&quot;</span>,</span><br><span class="line">        <span class="string">&quot;BackupSizeBytes&quot;</span>: 0,</span><br><span class="line">        <span class="string">&quot;BackupStatus&quot;</span>: <span class="string">&quot;CREATING&quot;</span>,</span><br><span class="line">        <span class="string">&quot;BackupType&quot;</span>: <span class="string">&quot;USER&quot;</span>,</span><br><span class="line">        <span class="string">&quot;BackupCreationDateTime&quot;</span>: <span class="string">&quot;2025-01-10T16:20:38.603000+01:00&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>After the backup is complete, the following command creates a new table based on the backup.</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">aws dynamodb restore-table-from-backup --target-table-name demo-target --backup-arn arn:aws:dynamodb:eu-west-1:111111111111:table/demo-source/backup/01736522438603-834a89d1</span><br></pre></td></tr></table></figure><p>Be aware, that DynamoDB’s built-in backup and restore functionality does not support restoring a table in another AWS account or region. To accomplish that, use the AWS Backup service instead.</p><p>Use the AWS CLI to create a backup with AWS Backup.</p><figure class="highlight elixir"><table><tr><td class="code"><pre><span class="line">aws backup start-backup-job --backup-vault-name <span class="title class_">Default</span> --resource-arn <span class="symbol">arn:</span><span class="symbol">aws:</span><span class="symbol">dynamodb:</span>eu-west<span class="number">-1</span><span class="symbol">:</span><span class="number">111111111111</span><span class="symbol">:table/demo-source</span> --iam-role-arn <span class="symbol">arn:</span><span class="symbol">aws:</span>iam::<span class="number">111111111111</span><span class="symbol">:role/service-role/AWSBackupDefaultServiceRole</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;BackupJobId&quot;</span>: <span class="string">&quot;0a9c3b91-9cb0-48d7-be40-a379f66921cc&quot;</span>,</span><br><span class="line">    <span class="string">&quot;RecoveryPointArn&quot;</span>: <span class="string">&quot;arn:aws:backup:eu-west-1:111111111111:recovery-point:39420416-139b-4c6a-9132-66869794cc82&quot;</span>,</span><br><span class="line">    <span class="string">&quot;CreationDate&quot;</span>: <span class="string">&quot;2025-01-10T16:35:03.365000+01:00&quot;</span>,</span><br><span class="line">    <span class="string">&quot;IsParent&quot;</span>: <span class="literal">false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Copy the backup to another region or account (see <a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/recov-point-create-a-copy.html" target="_blank" rel="noopener">Backup and tag copy</a> for details).</p><p>Then, restore into a new table named <code>demo-target</code>.</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">aws backup start-restore-job --recovery-point-arn arn:aws:backup:eu-west-1:111111111111:recovery-point:39420416-139b-4c6a-9132-66869794cc82 --metadata targetTableName=demo-target --iam-role-arn arn:aws:iam::111111111111:role/service-role/AWSBackupDefaultServiceRole</span><br></pre></td></tr></table></figure><p>Next, import the resource into the Terraform state.</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">% terraform import aws_dynamodb_table.target demo-target</span><br></pre></td></tr></table></figure><p>Importing a DynamoDB table is supported by CloudFormation as well (see <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resource-import.html" target="_blank" rel="noopener">Import AWS resources into a CloudFormation stack with a resource import</a>) for details.</p><p>While this is a rock solid solution that should work with large data sets without any issues, it is a little clumsy to manually import the new table to the CloudFormation stack or Terraform state. Therefore, let’s take a look at the 2nd approach.</p><h2 id="S3-Export-and-Import"><a href="#S3-Export-and-Import" class="headerlink" title="S3 Export and Import"></a>S3 Export and Import</h2><p>The second approach for moving a DynamoDB table uses the <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/S3DataExport.HowItWorks.html" target="_blank" rel="noopener">export to S3 and import from S3 functionality of DynamoDB</a>.</p><p>The following Terraform code snippet illustrates the procedure.</p><ol><li>Prepare the scenario by creating a <code>aws_dynamodb_table.source</code> table and an S3 bucket <code>aws_s3_bucket.export</code> to store the export.</li><li>Use the <code>aws_dynamodb_table_item</code> resource to add some test data to the <code>aws_dynamodb_table.source</code> table.</li><li>Use the <code>aws_dynamodb_table_export</code> resource to export the <code>source</code> table to S3 once.</li><li>Use the <code>import_table</code> attribute when creating the new <code>aws_dynamodb_table.target</code> table.</li></ol><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"># Configure Terraform</span><br><span class="line">terraform &#123;</span><br><span class="line">  required_providers &#123;</span><br><span class="line">    aws = &#123;</span><br><span class="line">      source  = &quot;hashicorp/aws&quot;</span><br><span class="line">      version = &quot;~&gt; 5.0&quot;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"># Configure the AWS provider</span><br><span class="line">provider &quot;aws&quot; &#123;</span><br><span class="line">  region = &quot;eu-west-1&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"># Create the source table</span><br><span class="line">resource &quot;aws_dynamodb_table&quot; &quot;source&quot; &#123;</span><br><span class="line">  name           = &quot;demo-source&quot;</span><br><span class="line">  billing_mode   = &quot;PAY_PER_REQUEST&quot;</span><br><span class="line">  hash_key       = &quot;id&quot;</span><br><span class="line">  point_in_time_recovery &#123;</span><br><span class="line">    enabled = true</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  attribute &#123;</span><br><span class="line">    name = &quot;id&quot;</span><br><span class="line">    type = &quot;S&quot;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"># Add an item to the table</span><br><span class="line">resource &quot;aws_dynamodb_table_item&quot; &quot;source&quot; &#123;</span><br><span class="line">  table_name = aws_dynamodb_table.source.name</span><br><span class="line">  hash_key   = aws_dynamodb_table.source.hash_key</span><br><span class="line"></span><br><span class="line">  item = &lt;&lt;ITEM</span><br><span class="line">&#123;</span><br><span class="line">  &quot;id&quot;: &#123;&quot;S&quot;: &quot;1&quot;&#125;,</span><br><span class="line">  &quot;data&quot;: &#123;&quot;S&quot;: &quot;demo&quot;&#125;</span><br><span class="line">&#125;</span><br><span class="line">ITEM</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"># Create an S3 bucket to store the DynamoDB table export</span><br><span class="line">resource &quot;aws_s3_bucket&quot; &quot;export&quot; &#123;</span><br><span class="line">  bucket_prefix = &quot;dynamodb-export-&quot;</span><br><span class="line">  force_destroy = true</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"># Create an export of the DynamoDB table</span><br><span class="line">resource &quot;aws_dynamodb_table_export&quot; &quot;source&quot; &#123;</span><br><span class="line">  depends_on = [aws_dynamodb_table_item.source]</span><br><span class="line">  table_arn = aws_dynamodb_table.source.arn</span><br><span class="line">  s3_bucket = aws_s3_bucket.export.id</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"># Create a new table based on the data export stored on S3</span><br><span class="line">resource &quot;aws_dynamodb_table&quot; &quot;target&quot; &#123;</span><br><span class="line">  name           = &quot;demo-target&quot;</span><br><span class="line">  billing_mode   = &quot;PAY_PER_REQUEST&quot;</span><br><span class="line">  hash_key       = &quot;id&quot;</span><br><span class="line">  point_in_time_recovery &#123;</span><br><span class="line">    enabled = true</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  attribute &#123;</span><br><span class="line">    name = &quot;id&quot;</span><br><span class="line">    type = &quot;S&quot;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  import_table &#123;</span><br><span class="line">    input_format = aws_dynamodb_table_export.source.export_format</span><br><span class="line">    input_compression_type = &quot;GZIP&quot;</span><br><span class="line">    s3_bucket_source &#123;</span><br><span class="line">        bucket = aws_s3_bucket.export.id</span><br><span class="line">        key_prefix = &quot;$&#123;trimsuffix(aws_dynamodb_table_export.source.manifest_files_s3_key, &quot;/manifest-summary.json&quot;)&#125;/data/&quot;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>There is one thing that bothers me about this solution. The one-time process of data migration is defined in the Terraform code, even though the migration will no longer play a role in the future.</p><h2 id="DynamoDB-CLI-dynein"><a href="#DynamoDB-CLI-dynein" class="headerlink" title="DynamoDB CLI: dynein"></a>DynamoDB CLI: dynein</h2><p>The third approach is my preferred approach, at least for small data sets: <a href="https://github.com/awslabs/dynein" target="_blank" rel="noopener">dynein</a> is a command line interface for Amazon DynamoDB written in Rust.</p><p>Use your preferred Infrastructure as Code tool to create the target table. Then use <code>dynein</code> to export the data from the <code>demo-source</code> table.</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">$ dy <span class="built_in">export</span> --table demo-source --region eu-west-1 --format jsonl --output-file export.jsonl</span><br><span class="line">2 items processed (10948.89 items/sec)</span><br></pre></td></tr></table></figure><p>The export file contains all items exported from the <code>demo-source</code> table.</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">$ <span class="built_in">cat</span> export.jsonl</span><br><span class="line">&#123;<span class="string">&quot;id&quot;</span>:<span class="string">&quot;1&quot;</span>,<span class="string">&quot;data&quot;</span>:<span class="string">&quot;demo&quot;</span>&#125;</span><br><span class="line">&#123;<span class="string">&quot;id&quot;</span>:<span class="string">&quot;2&quot;</span>,<span class="string">&quot;data&quot;</span>:<span class="string">&quot;test&quot;</span>&#125;</span><br></pre></td></tr></table></figure><p>Next, import the data to the <code>demo-target</code> table.</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">$ dy import --table demo-target --region eu-west-1 --format jsonl --input-file export.jsonl</span><br><span class="line">2 items processed (50419.74 items/sec)</span><br></pre></td></tr></table></figure><p>I like the approach because it does not interfere with the infrastructure code. There are two things to watch out for: AWS might stop maintaining the <a href="https://github.com/awslabs/dynein" target="_blank" rel="noopener">dynein</a> project, and the CLI tool might not be a good fit for large data sets.</p><p>By the way, <code>dynein</code> is a powerful tool to interact with DynamoDB. Here is a list of all <code>dy</code> commands.</p><pre><code>admin      Admin operations such as creating/updating table or GSIlist       List tables in the region. [API: ListTables]desc       Show detailed information of a table. [API: DescribeTable]scan       Retrieve items in a table without any condition. [API: Scan]get        Retrieve an item by specifying primary key(s). [API: GetItem]query      Retrieve items that match conditions. Partition key is required. [API: Query]put        Create a new item, or replace an existing item. [API: PutItem]del        Delete an existing item. [API: DeleteItem]upd        Update an existing item. [API: UpdateItem]bwrite     Put or Delete multiple items at one time, up to 25 requests. [API: BatchWriteItem]use        Switch target table context. After you use the command you don&#39;t need to specify table every time, but you may overwrite the target table with --table (-t) option.config     Manage configuration files (config.yml and cache.yml) from command linebootstrap  Create sample tables and load test data for bootstrappingexport     Export items from a DynamoDB table and save them as CSV/JSON file.import     Import items into a DynamoDB table from CSV/JSON file.backup     Take backup of a DynamoDB table using on-demand backuprestore    Restore a DynamoDB table from backup datahelp       Print this message or the help of the given subcommand(s)</code></pre><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>So, you learned about three options to move a DynamoDB table to another region or account: Backup and Restore (with DynamoDB’s built in backup functionality or AWS Backup), S3 Export and Import, or the DynamoDB CLI <code>dynein</code>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2025/01/dynamodb-move-backup-restore@730w.webp 730w, /images/2025/01/dynamodb-move-backup-restore@730w2x.webp 1460w, /images/2025/01/dynamodb-move-backup-restore@610w.webp 610w, /images/2025/01/dynamodb-move-backup-restore@610w2x.webp 1220w, /images/2025/01/dynamodb-move-backup-restore@450w.webp 450w, /images/2025/01/dynamodb-move-backup-restore@450w2x.webp 900w, /images/2025/01/dynamodb-move-backup-restore@330w.webp 330w, /images/2025/01/dynamodb-move-backup-restore@330w2x.webp 660w, /images/2025/01/dynamodb-move-backup-restore@545w.webp 545w, /images/2025/01/dynamodb-move-backup-restore@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2025/01/dynamodb-move-backup-restore@730w.png 730w, /images/2025/01/dynamodb-move-backup-restore@730w2x.png 1460w, /images/2025/01/dynamodb-move-backup-restore@610w.png 610w, /images/2025/01/dynamodb-move-backup-restore@610w2x.png 1220w, /images/2025/01/dynamodb-move-backup-restore@450w.png 450w, /images/2025/01/dynamodb-move-backup-restore@450w2x.png 900w, /images/2025/01/dynamodb-move-backup-restore@330w.png 330w, /images/2025/01/dynamodb-move-backup-restore@330w2x.png 660w, /images/2025/01/dynamodb-move-backup-restore@545w.png 545w, /images/2025/01/dynamodb-move-backup-restore@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2025/01/dynamodb-move-backup-restore.png" alt="How to move a DynamoDB table to another region or account?" title="How to move a DynamoDB table to another region or account?"></picture></p><p>One more thin aspect, that you should consider when moving large amounts of data, that I haven’t covered in this blog post: cost. The three different approaches are charged differently. So do the math before moving large amounts of data.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Optional DependsOn with CloudFormation: Metadata to the rescue</title>
      <link>https://cloudonaut.io/optional-dependson-with-cloudformation-metadata-to-the-rescue/</link>
      <description>Learn how to manage optional dependencies in AWS CloudFormation using a Metadata-based workaround when DependsOn can't handle conditional resources.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <guid isPermaLink="true">https://cloudonaut.io/optional-dependson-with-cloudformation-metadata-to-the-rescue/</guid>
      <pubDate>Tue, 12 Nov 2024 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>When working with AWS CloudFormation, sometimes it’s necessary to incorporate optional dependencies into your template. Recently, I encountered a situation where I had to build a single CloudFormation template to manage both a VPC and an application—though, generally, I recommend separating these into distinct templates for easier management. Using a single template introduces complexity, as you must explicitly manage dependencies using the <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-dependson.html" target="_blank" rel="noopener">DependsOn attribute</a> to ensure proper resource creation order.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/11/dependencies-title@730w.webp 730w, /images/2024/11/dependencies-title@730w2x.webp 1460w, /images/2024/11/dependencies-title@610w.webp 610w, /images/2024/11/dependencies-title@610w2x.webp 1220w, /images/2024/11/dependencies-title@450w.webp 450w, /images/2024/11/dependencies-title@450w2x.webp 900w, /images/2024/11/dependencies-title@330w.webp 330w, /images/2024/11/dependencies-title@330w2x.webp 660w, /images/2024/11/dependencies-title@545w.webp 545w, /images/2024/11/dependencies-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/11/dependencies-title@730w.jpg 730w, /images/2024/11/dependencies-title@730w2x.jpg 1460w, /images/2024/11/dependencies-title@610w.jpg 610w, /images/2024/11/dependencies-title@610w2x.jpg 1220w, /images/2024/11/dependencies-title@450w.jpg 450w, /images/2024/11/dependencies-title@450w2x.jpg 900w, /images/2024/11/dependencies-title@330w.jpg 330w, /images/2024/11/dependencies-title@330w2x.jpg 660w, /images/2024/11/dependencies-title@545w.jpg 545w, /images/2024/11/dependencies-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/11/dependencies-title.jpg" alt="Managing optional Dependencies in AWS CloudFormation" title="Managing optional Dependencies in AWS CloudFormation"></picture></p><p>Here’s a simplified version of the template. This setup involves an Auto Scaling Group (ASG) that depends on VPC resources such as network ACLs, route table associations, and a VPC gateway endpoint for DynamoDB:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">NetworkAclEntryInPrivateAllowEphemeral:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">NetworkAclEntryOutPrivateAllowHTTPS:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">RouteTableAssociationPrivate:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">VPCEndpointForDynamoDB:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">AutoScalingGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS::AutoScaling::AutoScalingGroup</span></span><br><span class="line">    <span class="attr">DependsOn:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">NetworkAclEntryInPrivateAllowEphemeral</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">NetworkAclEntryOutPrivateAllowHTTPS</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">RouteTableAssociationPrivate</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">VPCEndpointForDynamoDB</span></span><br><span class="line">    <span class="attr">Properties:</span> &#123;&#125;</span><br></pre></td></tr></table></figure><p>This approach works well if all resources are mandatory. However, in my case, the <code>VPCEndpointForDynamoDB</code> is an optional resource that can be toggled via a parameter. Here’s how I attempted to handle this initially:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">DynamoDB:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">    <span class="attr">Default:</span> <span class="string">&#x27;true&#x27;</span></span><br><span class="line">    <span class="attr">AllowedValues:</span> [<span class="string">&#x27;true&#x27;</span>, <span class="string">&#x27;false&#x27;</span>]</span><br><span class="line"><span class="attr">Conditions:</span></span><br><span class="line">  <span class="attr">HasDynamoDB:</span> <span class="type">!Equals</span> [<span class="type">!Ref</span> <span class="string">DynamoDB</span>, <span class="string">&#x27;true&#x27;</span>]</span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">NetworkAclEntryInPrivateAllowEphemeral:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">NetworkAclEntryOutPrivateAllowHTTPS:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">RouteTableAssociationPrivate:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">VPCEndpointForDynamoDB:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS::EC2::VPCEndpoint</span></span><br><span class="line">    <span class="attr">Condition:</span> <span class="string">HasDynamoDB</span></span><br><span class="line">    <span class="attr">Properties:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">AutoScalingGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS::AutoScaling::AutoScalingGroup</span></span><br><span class="line">    <span class="attr">DependsOn:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">NetworkAclEntryInPrivateAllowEphemeral</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">NetworkAclEntryOutPrivateAllowHTTPS</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">RouteTableAssociationPrivate</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!If</span> [<span class="string">HasDynamoDB</span>, <span class="string">VPCEndpointForDynamoDB</span>, <span class="type">!Ref</span> <span class="string">AWS::NoValue</span>] <span class="comment"># Template format error: Every DependsOn value must be a string.</span></span><br><span class="line">    <span class="attr">Properties:</span> &#123;&#125;</span><br></pre></td></tr></table></figure><p>In theory, this approach should work by conditionally referencing <code>VPCEndpointForDynamoDB</code> using an <code>!If</code> statement. However, CloudFormation doesn’t support <code>!If</code> conditions within the <code>DependsOn</code> attribute and returns a template format error: Every DependsOn value must be a string. This limitation required a workaround to manage conditional dependencies effectively.</p><h2 id="The-Metadata-Workaround"><a href="#The-Metadata-Workaround" class="headerlink" title="The Metadata Workaround"></a>The Metadata Workaround</h2><p>A reliable workaround is to use the <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-metadata.html" target="_blank" rel="noopener">Metadata attribute</a> that CloudFormation supports for attaching arbitrary data to resources. By leveraging Metadata, you can create an implicit dependency without using <code>DependsOn</code>. Here’s how:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">DynamoDB:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">    <span class="attr">Default:</span> <span class="string">&#x27;true&#x27;</span></span><br><span class="line">    <span class="attr">AllowedValues:</span> [<span class="string">&#x27;true&#x27;</span>, <span class="string">&#x27;false&#x27;</span>]</span><br><span class="line"><span class="attr">Conditions:</span></span><br><span class="line">  <span class="attr">HasDynamoDB:</span> <span class="type">!Equals</span> [<span class="type">!Ref</span> <span class="string">DynamoDB</span>, <span class="string">&#x27;true&#x27;</span>]</span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">NetworkAclEntryInPrivateAllowEphemeral:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">NetworkAclEntryOutPrivateAllowHTTPS:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">RouteTableAssociationPrivate:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">VPCEndpointForDynamoDB:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS::EC2::VPCEndpoint</span></span><br><span class="line">    <span class="attr">Condition:</span> <span class="string">HasDynamoDB</span></span><br><span class="line">    <span class="attr">Properties:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">AutoScalingGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS::AutoScaling::AutoScalingGroup</span></span><br><span class="line">    <span class="attr">Metadata:</span></span><br><span class="line">      <span class="attr">DynamoDB:</span> <span class="type">!If</span> [<span class="string">HasDynamoDB</span>, <span class="type">!Ref</span> <span class="string">VPCEndpointForDynamoDB</span>, <span class="type">!Ref</span> <span class="string">AWS::NoValue</span>] <span class="comment"># This is the workaround</span></span><br><span class="line">    <span class="attr">DependsOn:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">NetworkAclEntryInPrivateAllowEphemeral</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">NetworkAclEntryOutPrivateAllowHTTPS</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">RouteTableAssociationPrivate</span></span><br><span class="line">    <span class="attr">Properties:</span> &#123;&#125;</span><br></pre></td></tr></table></figure><p>In this solution, the <code>Metadata</code> attribute of <code>AutoScalingGroup</code> includes a conditional reference to <code>VPCEndpointForDynamoDB</code>. When <code>HasDynamoDB</code> is true, the <code>!Ref VPCEndpointForDynamoDB</code> effectively creates an implicit dependency on the optional resource. This way, if <code>VPCEndpointForDynamoDB</code> is toggled on, CloudFormation ensures it’s created before <code>AutoScalingGroup</code>.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>When managing optional resources in AWS CloudFormation, the <code>DependsOn</code> attribute doesn’t support conditional dependencies. To work around this limitation, use the <code>Metadata</code> attribute to create implicit dependencies based on conditions. This approach enables you to control optional resources’ deployment order without violating CloudFormation’s syntax rules.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Are you missing an AWS resource in Terraform? Try awscc provider!</title>
      <link>https://cloudonaut.io/are-you-missing-an-aws-resource-in-terraform-try-awscc-provider/</link>
      <description>Discover how the awscc Terraform provider leverages AWS Cloud Control API to address missing resources and streamline AWS infrastructure management. Learn to combine awscc with the standard aws provider for comprehensive coverage and faster access to new AWS features in your Terraform projects.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/terraform/">terraform</category>
      <guid isPermaLink="true">https://cloudonaut.io/are-you-missing-an-aws-resource-in-terraform-try-awscc-provider/</guid>
      <pubDate>Wed, 02 Oct 2024 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>There is nothing more frustrating than running into gaps in resource coverage when working with Infrastructure as Code tools like Terraform or CloudFormation. Not being able to use the latest features to solve a challenge is demotivating. Recently, I discovered a way to workaround missing resources when working with Terraform. In the following I will present the <code>awscc</code> Terraform provider which is based on the Cloud Control API provided by AWS.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/10/awscc-title@730w.webp 730w, /images/2024/10/awscc-title@730w2x.webp 1460w, /images/2024/10/awscc-title@610w.webp 610w, /images/2024/10/awscc-title@610w2x.webp 1220w, /images/2024/10/awscc-title@450w.webp 450w, /images/2024/10/awscc-title@450w2x.webp 900w, /images/2024/10/awscc-title@330w.webp 330w, /images/2024/10/awscc-title@330w2x.webp 660w, /images/2024/10/awscc-title@545w.webp 545w, /images/2024/10/awscc-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/10/awscc-title@730w.jpg 730w, /images/2024/10/awscc-title@730w2x.jpg 1460w, /images/2024/10/awscc-title@610w.jpg 610w, /images/2024/10/awscc-title@610w2x.jpg 1220w, /images/2024/10/awscc-title@450w.jpg 450w, /images/2024/10/awscc-title@450w2x.jpg 900w, /images/2024/10/awscc-title@330w.jpg 330w, /images/2024/10/awscc-title@330w2x.jpg 660w, /images/2024/10/awscc-title@545w.jpg 545w, /images/2024/10/awscc-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/10/awscc-title.jpg" alt="Are you missing an AWS resource in Terraform? Try awscc provider!" title="Are you missing an AWS resource in Terraform? Try awscc provider!"></picture></p><h2 id="What’s-the-Cloud-Control-API"><a href="#What’s-the-Cloud-Control-API" class="headerlink" title="What’s the Cloud Control API?"></a>What’s the Cloud Control API?</h2><p>Each AWS services provides an API to manage its resources. Infrastructure as Code tools like Terraform utilize the APIs to manage resources. But as the teams at AWS operate independently, there has been no standard in how APIs look like, which comes with high maintenance costs. The Cloud Control API aims to simplify maintaining Infrastructure as Code tools by providing a consistent API to create, read, update, delete, and list (CRUDL) resources in a standardized way.</p><p>AWS announced the Cloud Control API in 2021<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup>. I remember being disappointed by the announcement because only 366 resources had been supported back then<sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup>. But things improved within the past three years: the Clod Control API now supports more than 1000 resources. Even better, AWS is adding and extending resources constantly.<sup><a href="#fn:3" id="fnref:3" class="footnote-ref">3</a></sup></p><h2 id="A-Terraform-provider-leveraging-the-Cloud-Control-API-awscc"><a href="#A-Terraform-provider-leveraging-the-Cloud-Control-API-awscc" class="headerlink" title="A Terraform provider leveraging the Cloud Control API: awscc"></a>A Terraform provider leveraging the Cloud Control API: awscc</h2><p>The Terraform provider <a href="https://registry.terraform.io/providers/hashicorp/awscc/latest" target="_blank" rel="noopener">awscc</a> utilizes the Cloud Control API. The provider is automatically generated based on the Cloud Control API specification, which ensures changes are becoming available in Terraform quickly.</p><p>Using the <code>awscc</code> is very similar to using the good old <code>aws</code> provider. The following code snippet illustrates how to create an S3 bucket with the <code>awscc</code> provider.</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">terraform &#123;</span><br><span class="line">  required_version = &quot;~&gt;1.0&quot;</span><br><span class="line">  required_providers &#123;</span><br><span class="line">    awscc = &#123;</span><br><span class="line">      source  = &quot;hashicorp/awscc&quot;</span><br><span class="line">      version = &quot;~&gt;1.0&quot;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">provider &quot;awscc&quot; &#123;</span><br><span class="line">  region = &quot;eu-west-1&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;awscc_s3_bucket&quot; &quot;demo&quot; &#123;</span><br><span class="line">  bucket_name = &quot;awscc-demo&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Comparing-Terraform-providers-aws-and-awscc"><a href="#Comparing-Terraform-providers-aws-and-awscc" class="headerlink" title="Comparing Terraform providers: aws and awscc"></a>Comparing Terraform providers: aws and awscc</h2><p>What are the differences between the <code>aws</code> and <code>awscc</code> Terraform provider?</p><p>As illustrated in the following table, the <code>aws</code> provider is still ahead when it comes to covering resources. But <code>awscc</code> is closing the gap step by step. However, keep in mind that the comparison is counting resources and data resources only. It does not take the depth of attributes of each resource into consideration.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th><code>aws</code></th><th><code>awscc</code></th></tr></thead><tbody><tr><td>Resources</td><td>1424</td><td>1023</td></tr><tr><td>Data Resources</td><td>583</td><td>1813</td></tr></tbody></table><p>The <code>awscc</code> provider comes with a data resource to list and get each resource. However, you need to provide the IDs of the resources. There is no way to filter by any other attributes, as illustrated in the following example.</p><p>For example, the <code>aws</code> provider allows us to fetch information about the default VPC.</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">data &quot;aws_vpc&quot; &quot;demo&quot; &#123;</span><br><span class="line">  default = true # Filter the default VPC</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>In contrast, the <code>awscc</code> provider requires the ID to fetch information about the VPC.</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">data &quot;awscc_ec2_vpc&quot; &quot;demo&quot; &#123;</span><br><span class="line">  id = &quot;vpc-b1123fd5&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>So, the <code>awscc</code> comes with more data resources, but the missing ability to fetch resources by attributes other than the ID limits their usefulness.</p><h2 id="Is-awscc-ahead-of-aws-when-it-comes-to-support-new-AWS-features"><a href="#Is-awscc-ahead-of-aws-when-it-comes-to-support-new-AWS-features" class="headerlink" title="Is awscc ahead of aws when it comes to support new AWS features?"></a>Is awscc ahead of aws when it comes to support new AWS features?</h2><p>When working with Terraform, it is frustrating to run into missing resources, hindering to use new features. So, I tried to answer the question: Is the <code>awscc</code> provider ahead of the <code>aws</code> provider when it comes to supporting new features. I went through the GitHub issues of the <code>aws</code> provider, looking for missing features. In the following cases, the <code>aws</code> provider is missing new resources while the <code>awscc</code> provider already supports them.</p><ul><li><a href="https://github.com/hashicorp/terraform-provider-aws/issues/38583" target="_blank" rel="noopener">aws_bedrockagent_prompt</a></li><li><a href="https://github.com/hashicorp/terraform-provider-aws/issues/38095" target="_blank" rel="noopener">iot_job_template</a></li><li><a href="https://github.com/hashicorp/terraform-provider-aws/issues/38055" target="_blank" rel="noopener">sagemaker_mlflow_tracking_server</a></li></ul><h2 id="Mix-and-match"><a href="#Mix-and-match" class="headerlink" title="Mix and match"></a>Mix and match</h2><p>The good news, you don’t have to decide between using the <code>aws</code> and the <code>awscc</code> providers. Just use both, as illustrated in the following code example, it is simple to use both providers at the same time.</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">terraform &#123;</span><br><span class="line">  required_version = &quot;~&gt;1.0&quot;</span><br><span class="line">  required_providers &#123;</span><br><span class="line">    aws = &#123;</span><br><span class="line">      source  = &quot;hashicorp/aws&quot;</span><br><span class="line">      version = &quot;~&gt;5.0&quot;</span><br><span class="line">    &#125;</span><br><span class="line">    awscc = &#123;</span><br><span class="line">      source  = &quot;hashicorp/awscc&quot;</span><br><span class="line">      version = &quot;~&gt;1.0&quot;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">provider &quot;aws&quot; &#123;</span><br><span class="line">  region = &quot;eu-west-1&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">provider &quot;awscc&quot; &#123;</span><br><span class="line">  region = &quot;eu-west-1&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">data &quot;aws_vpc&quot; &quot;demo&quot; &#123;</span><br><span class="line">  default = true</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">data &quot;aws_subnets&quot; &quot;demo&quot; &#123;</span><br><span class="line">  filter &#123;</span><br><span class="line">    name   = &quot;vpc-id&quot;</span><br><span class="line">    values = [data.aws_vpc.demo.id]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">resource &quot;aws_security_group&quot; &quot;demo&quot; &#123;</span><br><span class="line">  name        = &quot;demo&quot;</span><br><span class="line">  description = &quot;Allow HTTPS.&quot;</span><br><span class="line">  vpc_id      = data.aws_vpc.demo.id</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_vpc_security_group_ingress_rule&quot; &quot;demo&quot; &#123;</span><br><span class="line">  security_group_id = aws_security_group.demo.id</span><br><span class="line">  ip_protocol       = &quot;tcp&quot;</span><br><span class="line">  cidr_ipv4         = &quot;0.0.0.0/0&quot;</span><br><span class="line">  from_port         = 443</span><br><span class="line">  to_port           = 443</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;awscc_ec2_instance&quot; &quot;demo&quot; &#123;</span><br><span class="line">  instance_type      = &quot;m5.large&quot;</span><br><span class="line">  security_group_ids = [aws_security_group.demo.id]</span><br><span class="line">  subnet_id          = data.aws_subnets.demo.ids[0]</span><br><span class="line">  image_id = &quot;ami-0fed63ea358539e44&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Add the <code>awscc</code> provider to your Infrastructure as Code toolbox, as it helps to overcome issues with missing resources in the <code>aws</code> provider. Mixing the <code>awscc</code> and <code>aws</code> provider is not a big deal and worth a try.</p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. <a href="https://aws.amazon.com/blogs/aws/announcing-aws-cloud-control-api/">AWS Cloud Control API, a Uniform API to Access AWS &amp; Third-Party Services</a> <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. <a href="https://web.archive.org/web/20211001000506/https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html">Wayback Machine: Supported Resources in September 2021</a> <a href="#fnref:2" class="footnote-backref">↩</a></p></li><li id="fn:3"><p>3. <a href="https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/doc-history.html">Document history for the Cloud Control API User Guide</a> <a href="#fnref:3" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Review: Amazon GuardDuty Malware Protection for S3</title>
      <link>https://cloudonaut.io/review-amazon-guardduty-malware-protection-for-s3/</link>
      <description>Learn how AWS GuardDuty protects your cloud infrastructure from viruses, malware, and other cyber threats. Discover best practices for implementing GuardDuty to enhance your AWS security posture and detect potential malicious activities across your AWS accounts and workloads.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/review/">review</category>
      <category domain="https://cloudonaut.io/tag/guardduty/">guardduty</category>
      <guid isPermaLink="true">https://cloudonaut.io/review-amazon-guardduty-malware-protection-for-s3/</guid>
      <pubDate>Thu, 27 Jun 2024 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Imagine users uploading attachments directly to S3 to share them with other users. Or partners uploading data to your S3 bucket to trigger business processes that download directly from S3. What could go wrong? A file uploaded to S3 could be infected. Malware, like a virus or ransomware is a cyber security threat first seen in 1971. Since then, the number of different types of malware has exploded. It is common practice to scan all files that enter (and sometimes leave) your security perimeter, usually your corporate network, by inspecting network traffic.<br>Additionally, many corporations install malware scanners on all servers and clients to scan all files that are stored (and sometimes accessed) from disk. But in the Cloud era, files can be uploaded directly to Amazon S3 bypassing your corporate network. You can access S3 objects without persisting them to disk first bypassing traditional malware scanners. We need to scan all uploads to Amazon S3 as well! That’s what Amazon GuardDuty Malware Protection for S3 is all about.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@730w.webp 730w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@730w2x.webp 1460w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@610w.webp 610w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@610w2x.webp 1220w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@450w.webp 450w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@450w2x.webp 900w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@330w.webp 330w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@330w2x.webp 660w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@545w.webp 545w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@730w.jpg 730w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@730w2x.jpg 1460w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@610w.jpg 610w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@610w2x.jpg 1220w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@450w.jpg 450w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@450w2x.jpg 900w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@330w.jpg 330w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@330w2x.jpg 660w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@545w.jpg 545w, /images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/06/review-amazon-guardduty-malware-protection-for-s3-title.jpg" alt="Review: Amazon GuardDuty Malware Protection for S3" title="Review: Amazon GuardDuty Malware Protection for S3"></picture></p><p>In the following post, I will dive deep into Amazon GuardDuty Malware Protection for S3. I have a lot of experience in this field. In 2015, I released an <a href="https://github.com/widdix/aws-s3-virusscan" target="_blank" rel="noopener">open-source project to scan files uploaded to Amazon S3</a>. In 2019, I co-founded <a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV - Antivirus protection for Amazon S3</a>. I might be biased but I have seen a lot of customer use cases, judge yourself.</p><h2 id="Scan-modes"><a href="#Scan-modes" class="headerlink" title="Scan modes"></a>Scan modes</h2><p>Amazon GuardDuty Malware Protection for S3 can scan files in real-time, right after the file is uploaded. Unfortunately, that’s it. Each file is scanned only once. There is no way to trigger a scan programmatically. It is also not possible to scan files just before a download happens.<br>Imagine a file uploaded a year ago. In the meantime, a new security vulnerability is disclosed. Unfortunately, the bad guys knew about the vulnerability long before and actively used it to attack victims. Only after the good guys discover the vulnerability, the malware scanners can detect it. All files uploaded one year ago could be infected as well. We simply don’t know because back then, the malware engine had no idea about the threat. That’s why almost all malware scanners rescan all files from time to time or on access. GuardDuty does not.</p><ul><li>Real-time&#x2F;on-upload file scan: ✅</li><li>Scheduled bucket scan: ❌</li><li>On-demand bucket scan: ❌</li><li>On-demand file scan: ❌</li><li>On-access file scan: ❌</li></ul><h2 id="Mitigation"><a href="#Mitigation" class="headerlink" title="Mitigation"></a>Mitigation</h2><p>Detecting a malicious file is important. Dealing with the malicious files is key. Amazon GuardDuty Malware Protection for S3 can tag S3 objects with the scan result. You can use this tag in S3 bucket policies or IAM policies to restrict access to clean files or block access to infected files. Unfortunately, that’s it. GuardDuty does not delete infected files or quarantine files (move them to a separate S3 bucket for further analysis).</p><ul><li>Tag: ✅</li><li>Delete: ❌</li><li>Quarantine&#x2F;Move: ❌</li></ul><h2 id="Reporting"><a href="#Reporting" class="headerlink" title="Reporting"></a>Reporting</h2><p>New security tools are always great. But someone must deal with all the findings, right? Even if the mitigation is automated (like deleting infected files), you still want to know what the tool is doing. Therefore, reporting is an important aspect. Amazon GuardDuty Malware Protection for S3 is working mostly in the dark. If you subscribe to GuardDuty, you will see findings created for malicious files. If you use Amazon GuardDuty Malware Protection for S3 in standalone mode, the scan results are not stored. You get some high-level CloudWatch metrics and that’s it. No dashboard, no notifications, no reports.</p><ul><li>Reports: ❌</li><li>Notifications (email): ❌</li><li>Notifications (Slack): ❌</li><li>Notifications (Microsoft Teams): ❌</li><li>Dashboard: ❌</li><li>AWS Security Hub finding integration: ⚠️ (only if you subscribe to GuardDuty)</li><li>AWS Systems Manager OpsCenter item integration: ❌</li><li>Amazon GuardDuty finding integration: ⚠️ (only if you subscribe to GuardDuty)</li></ul><h2 id="Developer"><a href="#Developer" class="headerlink" title="Developer"></a>Developer</h2><p>AWS is like Lego bricks. You put many bricks together to build great things. Amazon GuardDuty Malware Protection for S3 publishes events like scan results to EventBridge. EventBridge rules can trigger other AWS services. For example, to implement your quarantine logic, you can trigger a Lambda function if a file is infected. Keep in mind that moving files in S3 is not easy. You first copy the file and then delete it. But you can not copy a file that is larger than 5 GB. You need to copy it in parts which can take a lot of time so you better use Step Functions to orchestrate it to avoid Lambda timeouts.</p><ul><li>Amazon EventBridge integration: ✅</li><li>Amazon SNS integration: ❌</li><li>Amazon CloudWatch metrics integration: ✅</li><li>AWS API to scan files: ❌</li></ul><h2 id="Pricing-model"><a href="#Pricing-model" class="headerlink" title="Pricing model"></a>Pricing model</h2><p>I will use three example workloads to demonstrate the pricing model using us-east-1 prices.</p><ul><li>Tiny (90 GB&#x2F;month): $57.68</li><li>Small (3 TB &#x2F; month): $1,991.07</li><li>Larger (15 TB &#x2F; month): $12,696.81</li></ul><p>In the following, I present detailed cost estimations of all examples. I end with a detailed comparison of the pricing models.</p><h3 id="Tiny-workload"><a href="#Tiny-workload" class="headerlink" title="Tiny workload"></a>Tiny workload</h3><p>The customer scans 300 files per day with an average file size of 10 MB. This results in 9,000 files and 90 GB per month. Objects are tagged with scan results. AWS region is us-east-1.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">Amazon GuardDuty Malware Protection for S3</th></tr></thead><tbody><tr><td>Scanning</td><td style="text-align:right">GB: $54.00<br>files: $1.94<br>$55.94</td></tr><tr><td>Infrastructure</td><td style="text-align:right">S3: $0.05<br>EventBridge: $0.01<br>GuardDuty: optional, AWS usage dependent<br>$0.06</td></tr><tr><td>Support</td><td style="text-align:right">At least $1.68</td></tr><tr><td><strong>Total</strong></td><td style="text-align:right"><strong>$57.68</strong></td></tr></tbody></table><h3 id="Small-workload"><a href="#Small-workload" class="headerlink" title="Small workload"></a>Small workload</h3><p>The customer scans 20,000 files per day with an average file size of 5 MB. This results in 600,000 files and 3,000 GB per month. Objects are tagged with scan results. AWS region is us-east-1.</p><p>|  | Amazon GuardDuty Malware Protection for S3 || --- | ---: | ---: | ---: || Scanning | GB: $1,800.00<br>files: $129.00<br>$1,929.00 || Infrastructure | S3: $3.48<br>EventBridge: $0.60<br>GuardDuty: optional, AWS usage dependent<br>$4.08 || Support | At least $57.99 || <strong>Total</strong> | <strong>$1,991.07</strong> |</p><h3 id="Larger-workload"><a href="#Larger-workload" class="headerlink" title="Larger workload"></a>Larger workload</h3><p>The customer scans 500,000 files per day with an average file size of 1 MB. This results in 15,000,000 files and 15,000 GB per month. Objects are tagged with scan results. AWS region is us-east-1.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">Amazon GuardDuty Malware Protection for S3</th></tr></thead><tbody><tr><td>Scanning</td><td style="text-align:right">GB: $9,000.00<br>files: $3,225.00<br>$12,225.00</td></tr><tr><td>Infrastructure</td><td style="text-align:right">S3: $87.00<br>EventBridge: $15.00<br>GuardDuty: optional, AWS usage dependent<br>$102.00</td></tr><tr><td>Support</td><td style="text-align:right">At least $369.81</td></tr><tr><td><strong>Total</strong></td><td style="text-align:right"><strong>$12696.81</strong></td></tr></tbody></table><h3 id="Detailed-pricing-model-comparison"><a href="#Detailed-pricing-model-comparison" class="headerlink" title="Detailed pricing model comparison"></a>Detailed pricing model comparison</h3><p>The following table shows the various aspects of the pricing models using us-east-1 prices.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th>Amazon GuardDuty Malware Protection for S3</th></tr></thead><tbody><tr><td>Scanning</td><td>$0.60 per GB<br>$0.215 per 1,000 objects</td></tr><tr><td>Infrastructure</td><td>S3, EventBridge, optional GuardDuty</td></tr><tr><td>Support</td><td>Developer: $29 or 3% of monthly AWS charges<br>Business: $100 per month or 3-10% of monthly AWS charges<br>Enterprise: $15,000 per month or 3-7% of monthly AWS charges</td></tr></tbody></table><h2 id="Limitations"><a href="#Limitations" class="headerlink" title="Limitations"></a>Limitations</h2><p>Last but not least, we dive into the technical limitations of Amazon GuardDuty Malware Protection for S3:</p><ul><li>Maximum S3 object size: 5 GB</li><li>Maximum extracted archive size: 5 GB</li><li>Maximum number of files in an archive: 1,000</li><li>Maximum archive depth level: 5 (archive inside archive inside archive…)</li></ul><h2 id="Service-Maturity-Table"><a href="#Service-Maturity-Table" class="headerlink" title="Service Maturity Table"></a>Service Maturity Table</h2><p>Each service review ends with the service maturity table.</p><table class="table table-striped table-responsive"><thead><tr><th>Criteria</th><th>Summary</th><th style="text-align:right">Score</th></tr></thead><tbody><tr><td>Feature Completeness</td><td>🚨</td><td style="text-align:right">2</td></tr><tr><td>Documentation detailedness</td><td>✅</td><td style="text-align:right">8</td></tr><tr><td>Tags (Grouping + Billing)</td><td>✅️</td><td style="text-align:right">10</td></tr><tr><td>CloudFormation + Terraform support</td><td>✅️️</td><td style="text-align:right">10</td></tr><tr><td>Emits CloudWatch Events</td><td>✅️️</td><td style="text-align:right">10</td></tr><tr><td>IAM granularity</td><td>✅️️</td><td style="text-align:right">8</td></tr><tr><td>Integrated with AWS Config</td><td>⚠️</td><td style="text-align:right">0</td></tr><tr><td>Auditing via AWS CloudTrail</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>Available in all commercial regions</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>SLA</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>Compliance (ISO, SOC HIPAA)</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td><strong>Total Maturity Score (0-10)</strong></td><td>✅</td><td style="text-align:right"><strong>8.0</strong></td></tr></tbody></table><p>Our maturity score for Amazon GuardDuty Malware Protection for S3 is 8.0 on a scale from 0 to 10. Amazon GuardDuty Malware Protection for S3 benefits from being part of the GuardDuty service which is very mature. When we look at <strong>Feature Completeness</strong> in isolation, the picture looks less rosy. If you are interested in <a href="https://bucketav.com/blog/amazon-guardduty-malware-protection-for-s3-versus-bucketav/" target="_blank" rel="noopener">how bucketAV compares with Amazon GuardDuty Malware Protection for S3</a> I have you covered.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Deploying Self-Hosted Runners for GitHub Enterprise Server on AWS: A Guide to Efficient CI/CD</title>
      <link>https://cloudonaut.io/deploy-self-hosted-runners-github-enterprise-server-aws/</link>
      <description>Learn how to deploy self-hosted runners for GitHub Enterprise Server on AWS. Overcome CI/CD challenges with scalable, secure, and cost-efficient solutions for regulated industries and sensitive data environments.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/github/">github</category>
      <guid isPermaLink="true">https://cloudonaut.io/deploy-self-hosted-runners-github-enterprise-server-aws/</guid>
      <pubDate>Mon, 24 Jun 2024 20:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>GitHub Actions is a continuous integration and continuous deployment (CI&#x2F;CD) platform provided by GitHub. It allows you to automate your software development workflows by building, testing, and deploying code directly from your GitHub repository. Many companies, especially in regulated industries or dealing with sensitive data, choose GitHub Enterprise Server to host their code repositories and CI&#x2F;CD pipelines on-premises.</p><p>However, it’s important to note that GitHub Enterprise Server does not come with built-in GitHub-hosted runners, so it is necessary to deploy self-hosted runners on your own infrastructure to run GitHub Actions workflows. Learn how to deploy self-hosted runners for GitHub Enterprise Server on AWS in the following.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@730w.webp 730w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@730w2x.webp 1460w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@610w.webp 610w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@610w2x.webp 1220w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@450w.webp 450w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@450w2x.webp 900w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@330w.webp 330w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@330w2x.webp 660w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@545w.webp 545w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@730w.jpg 730w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@730w2x.jpg 1460w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@610w.jpg 610w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@610w2x.jpg 1220w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@450w.jpg 450w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@450w2x.jpg 900w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@330w.jpg 330w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@330w2x.jpg 660w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@545w.jpg 545w, /images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/06/deploy-self-hosted-runners-github-enterprise-server-aws-title.jpg" alt="Deploying Self-Hosted Runners for GitHub Enterprise Server on AWS: A Guide to Efficient CI/CD" title="Deploying Self-Hosted Runners for GitHub Enterprise Server on AWS: A Guide to Efficient CI/CD"></picture></p><h2 id="GitHub-Enterprise-Server-does-not-support-GitHub-managed-runners"><a href="#GitHub-Enterprise-Server-does-not-support-GitHub-managed-runners" class="headerlink" title="GitHub Enterprise Server does not support GitHub-managed runners"></a>GitHub Enterprise Server does not support GitHub-managed runners</h2><p>The GitHub documentation states it clearly:</p><blockquote><p>GitHub-hosted runners are not currently supported on GitHub Enterprise Server. You can see more information about planned future support on the <a href="https://github.com/github/roadmap/issues/72" target="_blank" rel="noopener">GitHub public roadmap</a>.</p></blockquote><p>The linked issue on GitHub’s roadmap was created in July 2020. Up until now, there is no indicator that GitHub is planning to start working on the feature in the near future.</p><p>So we’re on our own. GitHub Enterprise Server supports self-hosted runners only.</p><h2 id="Challenges-of-self-hosted-GitHub-runners"><a href="#Challenges-of-self-hosted-GitHub-runners" class="headerlink" title="Challenges of self-hosted GitHub runners"></a>Challenges of self-hosted GitHub runners</h2><p>Deploying self-hosted GitHub runners for GitHub Enterprise Server presents a few key challenges:</p><ul><li><strong>Security</strong>: Since self-hosted runners operate within the company’s infrastructure, extra care must be taken to secure them and ensure they cannot be misused as an entry point for malicious actors. Proper isolation, access controls, and monitoring are crucial.</li><li><strong>High availability</strong>: Depending on the workload, companies may need to deploy multiple self-hosted runners and implement strategies for high availability to ensure continuous service and efficient job execution.</li><li><strong>Scalability</strong>: As the number of concurrent jobs or workload increases, companies may need to implement auto-scaling mechanisms to dynamically provision and deprovision self-hosted runners to handle the demand efficiently.</li><li><strong>Cost efficiency</strong>: Provisioning self-hosted runners can lead to underutilized resources and higher costs if the workload is not consistent or predictable. Companies need to carefully plan and manage their runner infrastructure to optimize resource utilization and control costs.</li><li><strong>Maintenance</strong>: Self-hosted runners require regular updates and maintenance to keep them compatible with the latest GitHub Actions versions and to apply security patches or bug fixes.</li></ul><p>Over the years, I’ve been implementing different approaches and improved the solution step by step. Here is the architecture that I currently think is the best way to deploy self-hosted runners.</p><ul><li>Configure GitHub webhooks to get notified when a GitHub job is waiting for a runner.</li><li>Launch EC2 instance on-demand and register them as just-in-time runners.</li><li>Terminate EC2 instance after the GitHub job finished.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/06/hyperenv-v2-architecture@730w.webp 730w, /images/2024/06/hyperenv-v2-architecture@730w2x.webp 1460w, /images/2024/06/hyperenv-v2-architecture@610w.webp 610w, /images/2024/06/hyperenv-v2-architecture@610w2x.webp 1220w, /images/2024/06/hyperenv-v2-architecture@450w.webp 450w, /images/2024/06/hyperenv-v2-architecture@450w2x.webp 900w, /images/2024/06/hyperenv-v2-architecture@330w.webp 330w, /images/2024/06/hyperenv-v2-architecture@330w2x.webp 660w, /images/2024/06/hyperenv-v2-architecture@545w.webp 545w, /images/2024/06/hyperenv-v2-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/06/hyperenv-v2-architecture@730w.png 730w, /images/2024/06/hyperenv-v2-architecture@730w2x.png 1460w, /images/2024/06/hyperenv-v2-architecture@610w.png 610w, /images/2024/06/hyperenv-v2-architecture@610w2x.png 1220w, /images/2024/06/hyperenv-v2-architecture@450w.png 450w, /images/2024/06/hyperenv-v2-architecture@450w2x.png 900w, /images/2024/06/hyperenv-v2-architecture@330w.png 330w, /images/2024/06/hyperenv-v2-architecture@330w2x.png 660w, /images/2024/06/hyperenv-v2-architecture@545w.png 545w, /images/2024/06/hyperenv-v2-architecture@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/06/hyperenv-v2-architecture.png" alt="HyperEnv for GitHub Actions Runner" title="HyperEnv for GitHub Actions Runner"></picture></p><p>This approach offloads the scalability challenge to AWS, as we just start EC2 instances when we need them. Also, each GitHub jobs runs on its own virtual machine, which provides a solid isolation boundary and therefore increases security.</p><h2 id="How-to-deploy-self-hosted-GitHub-runners-on-AWS"><a href="#How-to-deploy-self-hosted-GitHub-runners-on-AWS" class="headerlink" title="How to deploy self-hosted GitHub runners on AWS?"></a>How to deploy self-hosted GitHub runners on AWS?</h2><p>Michael and I built a simple to use solution to deploy self-hosted GitHub runners on AWS: <a href="https://hyperenv.com/github-actions/" target="_blank" rel="noopener">HyperEnv for GitHub Actions Runner</a>. With it’s 2.0.0 release HyperEnv supports GitHub Free, Pro, Team, Enterprise Cloud and Enterprise Server. Here is how to deploy HyperEnv to your AWS account.</p><ol><li>Go to the AWS Marketplace and subscribe to <strong>HyperEnv for GitHub Actions Runner</strong>.</li><li>Create a CloudFormation stack based on the provided template.</li><li>Install a private GitHub app to a GitHub organization.</li><li>Configure the GitHub workflows to run on the self-hosted runners.</li></ol><p>For a more detailed explanation, please refer to the <a href="https://hyperenv.com/github-actions/setup-guide-step-01.html" target="_blank" rel="noopener">HyperEnv setup guide</a>.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>GitHub Actions allows you to automate workflows directly from GitHub repositories, but GitHub Enterprise Server requires self-hosted runners which present challenges around security, availability, scalability, cost, and maintenance. A solution like <a href="https://hyperenv.com/github-actions/" target="_blank" rel="noopener">HyperEnv for GitHub Actions Runner</a> can help deploy self-hosted runners on AWS by launching EC2 instances on-demand when jobs are triggered, providing isolation and auto-scaling capabilities.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to write unit tests when using the AWS JavaScript SDK v3?</title>
      <link>https://cloudonaut.io/how-to-unit-test-aws-javascript-sdk-v3/</link>
      <description>Use the aws-sdk-client-mock library to write simple and reliable unit tests by mocking the AWS SDK clients.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/sdk/">sdk</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-unit-test-aws-javascript-sdk-v3/</guid>
      <pubDate>Wed, 05 Jun 2024 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Writing unit tests for code that interacts with the AWS JavaScript SDK v3 comes with two major benefits. Obviously, writing unit tests ensures you catch bugs early and therefore increase the quality of your code. Also, writing unit tests enables you to run your code locally without the need to reach out to the AWS service APIs. But how do you write unit tests for code interacting with the AWS JavaScript SDK v3?</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@730w.webp 730w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@730w2x.webp 1460w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@610w.webp 610w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@610w2x.webp 1220w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@450w.webp 450w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@450w2x.webp 900w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@330w.webp 330w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@330w2x.webp 660w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@545w.webp 545w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@730w.jpg 730w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@730w2x.jpg 1460w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@610w.jpg 610w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@610w2x.jpg 1220w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@450w.jpg 450w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@450w2x.jpg 900w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@330w.jpg 330w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@330w2x.jpg 660w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@545w.jpg 545w, /images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/06/how-to-unit-test-aws-javascript-sdk-v3-title.jpg" alt="How to write unit tests when using the AWS JavaScript SDK v3?" title="How to write unit tests when using the AWS JavaScript SDK v3?"></picture></p><p>In the following, I will share my learnings from writing unit tests by using <a href="https://github.com/m-radzikowski/aws-sdk-client-mock" target="_blank" rel="noopener">aws-sdk-client-mock</a> by <a href="https://betterdev.blog/" target="_blank" rel="noopener">Maciej Radzikowski</a>.</p><h2 id="aws-sdk-client-mock-is-simple-to-use"><a href="#aws-sdk-client-mock-is-simple-to-use" class="headerlink" title="aws-sdk-client-mock is simple to use!"></a>aws-sdk-client-mock is simple to use!</h2><p>Let’s start with a simple example.</p><p>The following code snippet shows the <code>index.js</code> file containing a <code>handler()</code> function, which could be deployed as a Lambda function. The <code>handler()</code> function lists all buckets belonging to an AWS account.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; S3Client, <span class="title class_">ListBucketsCommand</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-sdk/client-s3&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">handler</span>(<span class="params">event, context</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> s3Client = <span class="keyword">new</span> <span class="title function_">S3Client</span>();</span><br><span class="line">  <span class="keyword">const</span> response = <span class="keyword">await</span> s3Client.<span class="title function_">send</span>(<span class="keyword">new</span> <span class="title class_">ListBucketsCommand</span>(&#123;&#125;));</span><br><span class="line">  <span class="keyword">return</span> response.<span class="property">Buckets</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>So, how do I write a unit test? I prefer using the test framework <a href="https://mochajs.org/" target="_blank" rel="noopener">mocha</a> to write JavaScript tests. The following snippet shows the <code>test/test.index.js</code> file containing the skeleton to implement a test.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; deepStrictEqual &#125; <span class="keyword">from</span> <span class="string">&#x27;node:assert&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> &#123; handler &#125; <span class="keyword">from</span> <span class="string">&#x27;../index.js&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;demo&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;handler&#x27;</span>, <span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> now = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">toISOString</span>();</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> <span class="title function_">handler</span>(&#123;&#125;, &#123;&#125;); <span class="comment">// Call the handler() function</span></span><br><span class="line">    <span class="title function_">deepStrictEqual</span>(result, [&#123; <span class="comment">// Verify the response</span></span><br><span class="line">      <span class="title class_">Name</span>: <span class="string">&#x27;bucket-demo-1&#x27;</span>,</span><br><span class="line">      <span class="title class_">CreationDate</span>: now</span><br><span class="line">    &#125;])</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>When executing the test, the <code>handler()</code> function will send a request to the S3 API. But doing so is not feasible for unit testing, as it is very challenging to ensure the response matches the assumptions in the unit test.</p><p>Instead of sending requests to the AWS APIs use a common testing technique called <strong>mocking</strong>. A <strong>mock</strong> simulates a dependency. So let’s mock the AWS Java Script SDK v3 by extending the <code>test/test.index.js</code> file.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; S3Client, <span class="title class_">ListBucketsCommand</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-sdk/client-s3&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; mockClient &#125; <span class="keyword">from</span> <span class="string">&#x27;aws-sdk-client-mock&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; deepStrictEqual &#125; <span class="keyword">from</span> <span class="string">&#x27;node:assert&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> &#123;handler&#125; <span class="keyword">from</span> <span class="string">&#x27;../index.js&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> s3Mock = <span class="title function_">mockClient</span>(S3Client); <span class="comment">// Creates a mock</span></span><br><span class="line"></span><br><span class="line"><span class="title function_">beforeEach</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  s3Mock.<span class="title function_">reset</span>(); <span class="comment">// Reset the mock before each test</span></span><br><span class="line">  s3Mock.<span class="title function_">rejects</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;not mocked&#x27;</span>)); <span class="comment">// by default, all commands are mocked, we change this here!</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;demo&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;handler&#x27;</span>, <span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> now = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">toISOString</span>();</span><br><span class="line">    s3Mock.<span class="title function_">on</span>(<span class="title class_">ListBucketsCommand</span>).<span class="title function_">resolvesOnce</span>(&#123; <span class="comment">// Mock the ListBucketsCommand and return hard-coded result</span></span><br><span class="line">      <span class="title class_">Buckets</span>: [&#123;</span><br><span class="line">        <span class="title class_">Name</span>: <span class="string">&#x27;bucket-demo-1&#x27;</span>,</span><br><span class="line">        <span class="title class_">CreationDate</span>: now</span><br><span class="line">      &#125;]</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> <span class="title function_">handler</span>(&#123;&#125;, &#123;&#125;);</span><br><span class="line">    <span class="title function_">deepStrictEqual</span>(result, [&#123;</span><br><span class="line">      <span class="title class_">Name</span>: <span class="string">&#x27;bucket-demo-1&#x27;</span>,</span><br><span class="line">      <span class="title class_">CreationDate</span>: now</span><br><span class="line">    &#125;]);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>Want to run the example yourself? Here is the <code>package.json</code> that you need to setup the example.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;dependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;@aws-sdk/client-s3&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.583.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;aws-sdk-client-mock&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.0.0&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;aws-mock-demo&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;main&quot;</span><span class="punctuation">:</span> <span class="string">&quot;index.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;devDependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;aws-sdk-client-mock&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;mocha&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^10.2.0&quot;</span></span><br><span class="line">    </span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mocha&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;module&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;author&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;license&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ISC&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Finally, run the test.</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">npm i</span><br><span class="line">npm <span class="built_in">test</span></span><br></pre></td></tr></table></figure><p>The testing framework outputs the following results.</p><figure class="highlight node-repl"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">&gt;</span> <span class="language-javascript">aws-mock-demo@<span class="number">1.0</span><span class="number">.0</span> test</span></span><br><span class="line"><span class="meta prompt_">&gt;</span> <span class="language-javascript">mocha</span></span><br><span class="line">  demo</span><br><span class="line">    ✔ handler</span><br><span class="line"></span><br><span class="line">  1 passing (5ms)</span><br></pre></td></tr></table></figure><p>Next, let me share a some lessons learned.</p><h2 id="Creating-a-mock-with-aws-sdk-client-mock"><a href="#Creating-a-mock-with-aws-sdk-client-mock" class="headerlink" title="Creating a mock with aws-sdk-client-mock"></a>Creating a mock with aws-sdk-client-mock</h2><p>It took me a little bit to understand that there are two ways to create a mock.</p><p>The following code creates a mock for a given client instance.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> s3Client = <span class="keyword">new</span> <span class="title function_">S3Client</span>(&#123;&#125;);</span><br><span class="line"><span class="keyword">const</span> s3Mock = <span class="title function_">mockClient</span>(s3Client);</span><br></pre></td></tr></table></figure><p>However, in many scenarios, you don’t have access to the AWS SDK client instances. In those scenarios, here is how you globally mock a client.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> s3Mock = <span class="title function_">mockClient</span>(S3Client);</span><br></pre></td></tr></table></figure><h2 id="Testing-pagination"><a href="#Testing-pagination" class="headerlink" title="Testing pagination"></a>Testing pagination</h2><p>The AWS JavaScript SDK v3 comes with built-in paginators. The following snippet shows how to page through all items stored in a DynamoDB table.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">DynamoDBClient</span>, paginateScan &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-sdk/client-dynamodb&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> dynamodbClient = <span class="keyword">new</span> <span class="title class_">DynamoDBClient</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">handler</span>(<span class="params">event, context</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> result = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  <span class="keyword">const</span> paginator = <span class="title function_">paginateScan</span>(&#123; <span class="comment">// pageinateScan calls ScanCommand multiple times to iterate over all result pages</span></span><br><span class="line">    <span class="attr">client</span>: dynamodbClient</span><br><span class="line">  &#125;, &#123;</span><br><span class="line">    <span class="title class_">TableName</span>: <span class="string">&#x27;demo&#x27;</span></span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="keyword">for</span> <span class="title function_">await</span> (<span class="keyword">const</span> page <span class="keyword">of</span> paginator) &#123;</span><br><span class="line">    result = result + page.<span class="property">Items</span>[<span class="number">0</span>].<span class="property">Data</span>.<span class="property">S</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>To write a unit test override the underlying command, <code>ScanCommand</code> in this example.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">DynamoDBClient</span>, <span class="title class_">ScanCommand</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-sdk/client-dynamodb&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; mockClient &#125; <span class="keyword">from</span> <span class="string">&#x27;aws-sdk-client-mock&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; deepStrictEqual &#125; <span class="keyword">from</span> <span class="string">&#x27;node:assert&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> dynamodbMock = <span class="title function_">mockClient</span>(<span class="title class_">DynamoDBClient</span>);</span><br><span class="line"><span class="keyword">import</span> &#123; handler &#125; <span class="keyword">from</span> <span class="string">&#x27;../index.js&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">beforeEach</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  dynamodbMock.<span class="title function_">reset</span>();</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;demo&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;handler&#x27;</span>, <span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">    dynamodbMock.<span class="title function_">on</span>(<span class="title class_">ScanCommand</span>, &#123;<span class="title class_">ExclusiveStartKey</span>: <span class="literal">undefined</span>&#125;).<span class="title function_">resolvesOnce</span>(&#123;</span><br><span class="line">      <span class="title class_">Items</span>: [&#123;<span class="title class_">Key</span>: &#123;<span class="attr">S</span>: <span class="string">&#x27;1&#x27;</span>&#125;, <span class="title class_">Data</span>: &#123;<span class="attr">S</span>: <span class="string">&#x27;Hello &#x27;</span>&#125;&#125;],</span><br><span class="line">      <span class="title class_">Count</span>: <span class="number">1</span>,</span><br><span class="line">      <span class="title class_">LastEvaluatedKey</span>: &#123;<span class="title class_">Key</span>: &#123;<span class="attr">S</span>: <span class="string">&#x27;1&#x27;</span>&#125;&#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    dynamodbMock.<span class="title function_">on</span>(<span class="title class_">ScanCommand</span>, &#123;<span class="title class_">ExclusiveStartKey</span>: &#123;<span class="title class_">Key</span>: &#123;<span class="attr">S</span>: <span class="string">&#x27;1&#x27;</span>&#125;&#125;&#125;).<span class="title function_">resolvesOnce</span>(&#123;</span><br><span class="line">      <span class="title class_">Items</span>: [&#123;<span class="title class_">Key</span>: &#123;<span class="attr">S</span>: <span class="string">&#x27;2&#x27;</span>&#125;, <span class="title class_">Data</span>: &#123;<span class="attr">S</span>: <span class="string">&#x27;World&#x27;</span>&#125;&#125;],</span><br><span class="line">      <span class="title class_">Count</span>: <span class="number">1</span>,</span><br><span class="line">      <span class="title class_">LastEvaluatedKey</span>: &#123;<span class="title class_">Key</span>: &#123;<span class="attr">S</span>: <span class="string">&#x27;2&#x27;</span>&#125;&#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    dynamodbMock.<span class="title function_">on</span>(<span class="title class_">ScanCommand</span>, &#123;<span class="title class_">ExclusiveStartKey</span>: &#123;<span class="title class_">Key</span>: &#123;<span class="attr">S</span>: <span class="string">&#x27;2&#x27;</span>&#125;&#125;&#125;).<span class="title function_">resolvesOnce</span>(&#123;</span><br><span class="line">      <span class="title class_">Items</span>: [&#123;<span class="title class_">Key</span>: &#123;<span class="attr">S</span>: <span class="string">&#x27;3&#x27;</span>&#125;, <span class="title class_">Data</span>: &#123;<span class="attr">S</span>: <span class="string">&#x27;!&#x27;</span>&#125;&#125;],</span><br><span class="line">      <span class="title class_">Count</span>: <span class="number">1</span></span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> <span class="title function_">handler</span>(&#123;&#125;, &#123;&#125;);</span><br><span class="line">    <span class="title function_">deepStrictEqual</span>(result, <span class="string">&#x27;Hello World!&#x27;</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="Mocks-vs-real-world"><a href="#Mocks-vs-real-world" class="headerlink" title="Mocks vs. real-world"></a>Mocks vs. real-world</h2><p>The tricky part when writing mocks for the AWS SDK is to ensure comatiblity with the real-world. That’s why I do not rely on unit testing. On top of that, integration testing against the AWS APIs is necessary.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p><a href="https://github.com/m-radzikowski/aws-sdk-client-mock" target="_blank" rel="noopener">aws-sdk-client-mock</a> is a handy tool when it comes to writing unit tests for code that interacts with the AWS JavaScript SDK v3. It has never been easier to write unit tests!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Tidying up after failed Terraform tests</title>
      <link>https://cloudonaut.io/tidying-up-after-failed-terraform-tests/</link>
      <description>Watch out: Terraform tests might leave AWS resources instead of deleting them at the end of executing the test. So how do you clean up leftover resources after running terraform test?</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/terraform/">terraform</category>
      <guid isPermaLink="true">https://cloudonaut.io/tidying-up-after-failed-terraform-tests/</guid>
      <pubDate>Wed, 22 May 2024 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Automated tests are making their way into Infrastructure as Code projects. Recently, I’ve implemented tests with <a href="https://developer.hashicorp.com/terraform/language/tests" target="_blank" rel="noopener">Terraform’s test framework</a> which was <a href="https://www.hashicorp.com/blog/terraform-1-6-adds-a-test-framework-for-enhanced-code-validation" target="_blank" rel="noopener">released in October 2023</a>. However, I ran into the issue that Terraform could not remove all AWS resources at the end of the test under rare circumstances.</p><blockquote><p>The problem and solution discussed in the following also apply to OpenTofu.</p></blockquote><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/05/tidying-up-after-failed-terraform-tests-title@730w.webp 730w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@730w2x.webp 1460w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@610w.webp 610w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@610w2x.webp 1220w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@450w.webp 450w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@450w2x.webp 900w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@330w.webp 330w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@330w2x.webp 660w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@545w.webp 545w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/05/tidying-up-after-failed-terraform-tests-title@730w.jpg 730w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@730w2x.jpg 1460w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@610w.jpg 610w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@610w2x.jpg 1220w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@450w.jpg 450w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@450w2x.jpg 900w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@330w.jpg 330w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@330w2x.jpg 660w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@545w.jpg 545w, /images/2024/05/tidying-up-after-failed-terraform-tests-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/05/tidying-up-after-failed-terraform-tests-title.jpg" alt="Tidying up after failed Terraform tests" title="Tidying up after failed Terraform tests"></picture></p><p>Here is the typical message that <code>terraform test</code> will print out in case cleaning up all the resources fails.</p><figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">Terraform <span class="attribute">left</span> the following resources in state after executing</span><br><span class="line">tests/default<span class="selector-class">.tftest</span>.hcl/execute, and they need to be cleaned up manually:</span><br><span class="line">  - aws_subnet<span class="selector-class">.private</span><span class="selector-attr">[0]</span></span><br><span class="line">  - aws_subnet<span class="selector-class">.private</span><span class="selector-attr">[1]</span></span><br><span class="line">  - aws_subnet<span class="selector-class">.public</span><span class="selector-attr">[0]</span></span><br><span class="line">  - aws_subnet<span class="selector-class">.public</span><span class="selector-attr">[1]</span></span><br><span class="line">  - aws_vpc<span class="selector-class">.this</span></span><br><span class="line">  - ...</span><br></pre></td></tr></table></figure><p>Leftover AWS resources are an issue, especially when running tests in an automated manner, causing unwanted costs. Therefore, I was looking for a solution to tidy up AWS resources regularly. The tool <a href="https://github.com/rebuy-de/aws-nuke" target="_blank" rel="noopener">aws-nuke</a>, by rebuy, deletes all resources belonging to an AWS account.</p><p>The following snippet shows the configuration file <code>nuke-config.yml</code> for <code>aws-nuke</code>. First, define which regions <code>aws-nuke</code> shall remove resources. <code>global</code> is needed to delete global resources like IAM roles and policies. For safety reasons, defining an <code>account-blocklist</code> with AWS account IDs that you never want to tidy up is necessary. Next, you define the <code>accounts</code> where you want to remove all resources. The <code>filters</code> are required to keep some essential resources, such as the IAM role and policy used by <code>aws-nuke</code> to access the AWS account.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">regions:</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">eu-west-1</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">global</span></span><br><span class="line"><span class="attr">account-blocklist:</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">&#x27;999999999999&#x27;</span></span><br><span class="line"><span class="attr">accounts:</span></span><br><span class="line">  <span class="attr">&#x27;111111111111&#x27;:</span></span><br><span class="line">    <span class="attr">filters:</span></span><br><span class="line">      <span class="attr">IAMRole:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;nuke&#x27;</span></span><br><span class="line">      <span class="attr">IAMRolePolicy:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">type:</span> <span class="string">glob</span></span><br><span class="line">        <span class="attr">value:</span> <span class="string">&quot;nuke -&gt; *&quot;</span></span><br></pre></td></tr></table></figure><p>My recommendation is to run <code>aws-nuke</code> with the dry run option activated - which is the default - and check for resources you want to keep. Then, add a filter for those resources. <a href="https://github.com/rebuy-de/aws-nuke#install" target="_blank" rel="noopener">Learn how to install aws-nuke.</a></p><figure class="highlight arduino"><table><tr><td class="code"><pre><span class="line">aws-nuke -c nuke-config.yml</span><br></pre></td></tr></table></figure><p>For example, <code>aws-nuke</code> deletes the VPC and subnets left over from the Terraform test.</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">&gt; aws-nuke-example</span><br><span class="line"></span><br><span class="line">eu-west-1 - EC2Subnet - <span class="string">&#x27;subnet-154d844e&#x27;</span> - would <span class="built_in">remove</span></span><br><span class="line">eu-west-1 - EC2Subnet - <span class="string">&#x27;subnet-af12a261&#x27;</span> - would <span class="built_in">remove</span></span><br><span class="line">eu-west-1 - EC2Subnet - <span class="string">&#x27;subnet-cd2fa222&#x27;</span> - would <span class="built_in">remove</span></span><br><span class="line">eu-west-1 - EC2Subnet - <span class="string">&#x27;subnet-51223aff&#x27;</span> - would <span class="built_in">remove</span></span><br><span class="line">eu-west-1 - EC2VPC - <span class="string">&#x27;vpc-c6159fa1&#x27;</span> - would <span class="built_in">remove</span></span><br><span class="line">Scan complete: 13 total, 5 nukeable, 10 filtered.</span><br></pre></td></tr></table></figure><p>While it’s possible to run <code>aws-nuke</code> from your machine to ensure leftover AWS resources are regularly cleaned up, a scheduled job is the way to go. As I’m running the command <code>terraform test</code> within a CI&#x2F;CD pipeline on GitHub, I decided to use a scheduled GitHub workflow to run <code>aws-nuke</code> once a day. The following snippet illustrates how to define a GitHub workflow to regularly run <code>aws-nuke</code> to delete resources belonging to an AWS account.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">&#x27;nuke&#x27;</span></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">workflow_dispatch:</span></span><br><span class="line">  <span class="attr">schedule:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">cron:</span>  <span class="string">&#x27;0 0 * * *&#x27;</span></span><br><span class="line"><span class="attr">concurrency:</span></span><br><span class="line">  <span class="attr">group:</span> <span class="string">&#x27;nuke&#x27;</span></span><br><span class="line">  <span class="attr">cancel-in-progress:</span> <span class="literal">false</span></span><br><span class="line"><span class="attr">permissions:</span></span><br><span class="line">  <span class="attr">id-token:</span> <span class="string">write</span></span><br><span class="line">  <span class="attr">contents:</span> <span class="string">read</span></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">nuke:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">&#x27;Assuming IAM role&#x27;</span></span><br><span class="line">      <span class="attr">uses:</span> <span class="string">aws-actions/configure-aws-credentials@v4</span></span><br><span class="line">      <span class="attr">with:</span></span><br><span class="line">        <span class="attr">role-to-assume:</span> <span class="string">arn:aws:iam::111111111111:role/nuke</span></span><br><span class="line">        <span class="attr">role-session-name:</span> <span class="string">nuke</span></span><br><span class="line">        <span class="attr">aws-region:</span> <span class="string">eu-west-1</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">&#x27;Tidying up AWS resources&#x27;</span></span><br><span class="line">      <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line">        <span class="string">docker</span> <span class="string">run</span> <span class="string">-e</span> <span class="string">AWS_ACCESS_KEY_ID=$&#123;AWS_ACCESS_KEY_ID&#125;</span> <span class="string">-e</span> <span class="string">AWS_SECRET_ACCESS_KEY=$&#123;AWS_SECRET_ACCESS_KEY&#125;</span> <span class="string">-e</span> <span class="string">AWS_SESSION_TOKEN=$&#123;AWS_SESSION_TOKEN&#125;</span> <span class="string">--rm</span> <span class="string">-v</span> <span class="string">./nuke-config.yml:/home/aws-nuke/config.yml</span> <span class="string">quay.io/rebuy/aws-nuke:v2.25.0</span> <span class="string">--config</span> <span class="string">/home/aws-nuke/config.yml</span> <span class="string">--force</span> <span class="string">--no-dry-run</span></span><br></pre></td></tr></table></figure><blockquote><p>By the way, have you heard about our solution <a href="https://hyperenv.com/github-actions/" target="_blank" rel="noopener">HyperEnv for GitHub Actions Runner</a> to spin up EC2 instances on-demand for executing GitHub workflow jobs?</p></blockquote><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Watch out for leftover AWS resources after executing Terraform tests. Periodically running <a href="https://github.com/rebuy-de/aws-nuke" target="_blank" rel="noopener">aws-nuke</a> ensures all AWS resources are deleted to avoid unwanted costs.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to monetize an API on AWS?</title>
      <link>https://cloudonaut.io/how-to-monetize-an-api-on-aws/</link>
      <description>Sell access to REST APIs with API Gateway, usage plan, API keys, Lambda, and FastSpring.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/apigateway/">apigateway</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-monetize-an-api-on-aws/</guid>
      <pubDate>Wed, 15 May 2024 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Did you develop an API and want to sell access? Here is how I combined <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html" target="_blank" rel="noopener">Amazon’s API Gateway (REST APIs)</a> and <a href="https://fastspring.com/" target="_blank" rel="noopener">FastSpring</a>, a payment and subscription platform, to monetize our API for malware scanning. Luckily, you can apply the pattern to any REST API.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/05/how-to-monetize-an-api-on-aws-title@730w.webp 730w, /images/2024/05/how-to-monetize-an-api-on-aws-title@730w2x.webp 1460w, /images/2024/05/how-to-monetize-an-api-on-aws-title@610w.webp 610w, /images/2024/05/how-to-monetize-an-api-on-aws-title@610w2x.webp 1220w, /images/2024/05/how-to-monetize-an-api-on-aws-title@450w.webp 450w, /images/2024/05/how-to-monetize-an-api-on-aws-title@450w2x.webp 900w, /images/2024/05/how-to-monetize-an-api-on-aws-title@330w.webp 330w, /images/2024/05/how-to-monetize-an-api-on-aws-title@330w2x.webp 660w, /images/2024/05/how-to-monetize-an-api-on-aws-title@545w.webp 545w, /images/2024/05/how-to-monetize-an-api-on-aws-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/05/how-to-monetize-an-api-on-aws-title@730w.jpg 730w, /images/2024/05/how-to-monetize-an-api-on-aws-title@730w2x.jpg 1460w, /images/2024/05/how-to-monetize-an-api-on-aws-title@610w.jpg 610w, /images/2024/05/how-to-monetize-an-api-on-aws-title@610w2x.jpg 1220w, /images/2024/05/how-to-monetize-an-api-on-aws-title@450w.jpg 450w, /images/2024/05/how-to-monetize-an-api-on-aws-title@450w2x.jpg 900w, /images/2024/05/how-to-monetize-an-api-on-aws-title@330w.jpg 330w, /images/2024/05/how-to-monetize-an-api-on-aws-title@330w2x.jpg 660w, /images/2024/05/how-to-monetize-an-api-on-aws-title@545w.jpg 545w, /images/2024/05/how-to-monetize-an-api-on-aws-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/05/how-to-monetize-an-api-on-aws-title.jpg" alt="How to monetize an API on AWS?" title="How to monetize an API on AWS?"></picture></p><h2 id="The-problem-payments-subscription-and-access-control"><a href="#The-problem-payments-subscription-and-access-control" class="headerlink" title="The problem: payments, subscription, and access control"></a>The problem: payments, subscription, and access control</h2><p>I’m building a <a href="https://attachmentav.com/solution/malware-protection-for-wordpress/" target="_blank" rel="noopener">WordPress plugin to protect blogs from malware</a>. Whenever an editor uploads a new attachment, the plugin sends the file to our API, which scans it for malware. The infrastructure consists of an Application Load Balancer (ALB) and EC2 instances running the malware engine. So, how do we charge customers for accessing the API?</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/05/monetize-api-problem@730w.webp 730w, /images/2024/05/monetize-api-problem@730w2x.webp 1460w, /images/2024/05/monetize-api-problem@610w.webp 610w, /images/2024/05/monetize-api-problem@610w2x.webp 1220w, /images/2024/05/monetize-api-problem@450w.webp 450w, /images/2024/05/monetize-api-problem@450w2x.webp 900w, /images/2024/05/monetize-api-problem@330w.webp 330w, /images/2024/05/monetize-api-problem@330w2x.webp 660w, /images/2024/05/monetize-api-problem@545w.webp 545w, /images/2024/05/monetize-api-problem@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/05/monetize-api-problem@730w.png 730w, /images/2024/05/monetize-api-problem@730w2x.png 1460w, /images/2024/05/monetize-api-problem@610w.png 610w, /images/2024/05/monetize-api-problem@610w2x.png 1220w, /images/2024/05/monetize-api-problem@450w.png 450w, /images/2024/05/monetize-api-problem@450w2x.png 900w, /images/2024/05/monetize-api-problem@330w.png 330w, /images/2024/05/monetize-api-problem@330w2x.png 660w, /images/2024/05/monetize-api-problem@545w.png 545w, /images/2024/05/monetize-api-problem@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/05/monetize-api-problem.png" alt="" title=""></picture></p><p>Let’s break down the problem into requirements.</p><ul><li>Manage a subscription (create, pause, cancel, …)</li><li>Handle payments (different payment methods, worldwide, …)</li><li>Control access to API (API key, throttling, …)</li></ul><h2 id="The-options-API-marketplaces-and-payment-and-subscription-platforms"><a href="#The-options-API-marketplaces-and-payment-and-subscription-platforms" class="headerlink" title="The options: API marketplaces and payment and subscription platforms"></a>The options: API marketplaces and payment and subscription platforms</h2><p>My first idea was to use an API marketplace. The <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/sell-api-as-saas-on-aws-marketplace.html" target="_blank" rel="noopener">AWS Marketplace supports selling API Gateway APIs</a>. We are already selling products through the AWS Marketplace and are pretty happy with the solution. However, the AWS Marketplace works best if potential customers are already AWS customers. As I’m aiming to sell API access to WordPress users, the hurdle of creating an AWS account seems too high.</p><p>What about more generic API marketplaces? There are a few providers out there. I had a deeper look into <a href="https://rapidapi.com/" target="_blank" rel="noopener">Rapid API</a>. From a technical point of view, the solution looks solid. However, Rapid API targets developers who want to integrate an API into their application. I could not find a way to integrate Rapid API into the checkout process for the users of our WordPress plugin. Besides that, I concluded that Rapid API is in the early stages of collecting payments and deducting taxes worldwide.</p><p>To have complete control over the checkout process, I looked into generic payment and subscription platforms. So, I looked into <a href="https://stripe.com/" target="_blank" rel="noopener">Stripe</a> and a few other solutions. My pain point with all those solutions is tax compliance. It’s quite tricky to comply with all the tax laws worldwide. Therefore, I ended up with a provider we have used for years: <a href="https://fastspring.com/" target="_blank" rel="noopener">FastSpring</a>. From a technical and look and feel perspective, FastSpring is getting a bit long in the tooth. But FastSpring acts as a reseller. Therefore, FastSpring is responsible for tax deductions with customers from all over the world.</p><p>I decided to use FastSpring to handle payments and subscriptions. Next, I looked for the simplest possible implementation on AWS.</p><h2 id="The-solution-API-Gateway-REST-APIs-usage-plans-API-keys-and-FastSpring"><a href="#The-solution-API-Gateway-REST-APIs-usage-plans-API-keys-and-FastSpring" class="headerlink" title="The solution: API Gateway (REST APIs), usage plans, API keys, and FastSpring"></a>The solution: API Gateway (REST APIs), usage plans, API keys, and FastSpring</h2><p>After all, I came up with the following solution to monetize a REST API.</p><ol><li>The customer goes to the storefront provided by FastSpring to create a subscription. FastSpring generates a license key.</li><li>FastSpring sends a webhook event to the API Gateway, including the subscription ID and license key.</li><li>The API Gateway invokes a Lambda function. The Lambda function creates an API key using the value of the license key and assigns the API key to a usage plan.</li><li>The customer sends a request to the API Gatway. The request includes the license key (&#x3D; API key) in the header.</li><li>The API Gateway validates the API key and usage plan and then forwards the request to the ALB.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/05/monetize-api-solution@730w.webp 730w, /images/2024/05/monetize-api-solution@730w2x.webp 1460w, /images/2024/05/monetize-api-solution@610w.webp 610w, /images/2024/05/monetize-api-solution@610w2x.webp 1220w, /images/2024/05/monetize-api-solution@450w.webp 450w, /images/2024/05/monetize-api-solution@450w2x.webp 900w, /images/2024/05/monetize-api-solution@330w.webp 330w, /images/2024/05/monetize-api-solution@330w2x.webp 660w, /images/2024/05/monetize-api-solution@545w.webp 545w, /images/2024/05/monetize-api-solution@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/05/monetize-api-solution@730w.png 730w, /images/2024/05/monetize-api-solution@730w2x.png 1460w, /images/2024/05/monetize-api-solution@610w.png 610w, /images/2024/05/monetize-api-solution@610w2x.png 1220w, /images/2024/05/monetize-api-solution@450w.png 450w, /images/2024/05/monetize-api-solution@450w2x.png 900w, /images/2024/05/monetize-api-solution@330w.png 330w, /images/2024/05/monetize-api-solution@330w2x.png 660w, /images/2024/05/monetize-api-solution@545w.png 545w, /images/2024/05/monetize-api-solution@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/05/monetize-api-solution.png" alt="" title=""></picture></p><p>What I like most about the solution is its simplicity.</p><blockquote><p>API Gateway REST APIs have two major limitations: the payload size is limited to 10 MB, and the request timeout is limited to 30 seconds.</p></blockquote><p>Next, let’s dive into some implementation details.</p><p>The Amazon API Gateway REST APIs support <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-usage-plans.html" target="_blank" rel="noopener">usage plans</a> and API keys. A usage plan allows you to define the target request rate per customer, which is crucial to protecting your infrastructure from accidental or malicious request flooding. Additionally, it is possible to define a quota for the maximum number of requests per day, week, or month. The following CloudFormation snippet shows how to create a usage plan limiting access to 1 request per second and 10,000 per day, for example.</p><p>It’s important to mention, that AWS does not guarantee to apply throttling and quotas 100% accuartely. Here is what the <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-usage-plans.html#api-gateway-api-usage-plans-overview" target="_blank" rel="noopener">AWS documentation</a> says: “Usage plan throttling and quotas are not hard limits, and are applied on a best-effort basis. In some cases, clients can exceed the quotas that you set. Don’t rely on usage plan quotas or throttling to control costs or block access to an API.” In our scenario, that’s a limitation we can live with.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">UsagePlan:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::ApiGateway::UsagePlan&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">UsagePlanName:</span> <span class="string">&#x27;demo&#x27;</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;1 req/sec and 10,000 req/day&#x27;</span></span><br><span class="line">    <span class="attr">ApiStages:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">ApiId:</span> <span class="type">!Ref</span> <span class="string">ApiGateway</span></span><br><span class="line">      <span class="attr">Stage:</span> <span class="type">!Ref</span> <span class="string">ApiStage</span></span><br><span class="line">    <span class="attr">Throttle:</span></span><br><span class="line">      <span class="attr">BurstLimit:</span> <span class="number">5</span></span><br><span class="line">      <span class="attr">RateLimit:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Quota:</span></span><br><span class="line">      <span class="attr">Limit:</span> <span class="number">10000</span></span><br><span class="line">      <span class="attr">Period:</span> <span class="string">DAY</span></span><br></pre></td></tr></table></figure><blockquote><p>The “new” Amazon API Gateway HTTP APIs still do not support usage plans. I’m using the “legacy” option REST APIs here.</p></blockquote><p>As described above, FastSpring sends webhook events whenever customers create or cancel a subscription. The following JavaScript snippet shows how a Lambda function parses the webhook event, creates an API key, and attaches the API key to the usage plan.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">APIGatewayClient</span>, <span class="title class_">CreateApiKeyCommand</span>, <span class="title class_">GetApiKeysCommand</span>, <span class="title class_">UpdateApiKeyCommand</span>, <span class="title class_">CreateUsagePlanKeyCommand</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-sdk/client-api-gateway&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; createHmac &#125; <span class="keyword">from</span> <span class="string">&#x27;node:crypto&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> apigw = <span class="keyword">new</span> <span class="title class_">APIGatewayClient</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">WEBHOOK_SECRET</span> = <span class="string">&#x27;...&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">USAGE_PLAN_ID</span> = <span class="string">&#x27;...&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">isValidSignature</span> (<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> fsSignature = event.<span class="property">headers</span>[<span class="string">&#x27;X-FS-Signature&#x27;</span>];</span><br><span class="line">  <span class="keyword">const</span> computedSignature = <span class="title function_">createHmac</span>(<span class="string">&#x27;sha256&#x27;</span>, <span class="variable constant_">WEBHOOK_SECRET</span>).<span class="title function_">update</span>(event.<span class="property">body</span>).<span class="title function_">digest</span>().<span class="title function_">toString</span>(<span class="string">&#x27;base64&#x27;</span>);</span><br><span class="line">  <span class="keyword">return</span> fsSignature === computedSignature;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">handler</span> = <span class="keyword">async</span> (<span class="params">event</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (event.<span class="property">path</span> === <span class="string">&#x27;/v1/fastspring/webhook&#x27;</span> &amp;&amp; event.<span class="property">httpMethod</span> === <span class="string">&#x27;POST&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="title function_">isValidSignature</span>(event)) &#123;</span><br><span class="line">      <span class="keyword">const</span> body = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(event.<span class="property">body</span>);</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">const</span> e <span class="keyword">of</span> body.<span class="property">events</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (e.<span class="property">type</span> === <span class="string">&#x27;subscription.activated&#x27;</span>) &#123; <span class="comment">// Customer subscribed via FastSpring</span></span><br><span class="line">          <span class="keyword">const</span> apiKey = <span class="keyword">await</span> apigw.<span class="title function_">send</span>(<span class="keyword">new</span> <span class="title class_">CreateApiKeyCommand</span>(&#123;</span><br><span class="line">            <span class="attr">name</span>: <span class="string">`subscription-<span class="subst">$&#123;e.data.subscription&#125;</span>`</span>,</span><br><span class="line">            <span class="attr">description</span>: <span class="string">`The license and API key for FastSpring subscription <span class="subst">$&#123;e.data.subscription&#125;</span>.`</span>,</span><br><span class="line">            <span class="attr">enabled</span>: <span class="literal">true</span>,</span><br><span class="line">            <span class="attr">value</span>: e.<span class="property">data</span>.<span class="property">fulfillments</span>[<span class="string">&#x27;license_0&#x27;</span>][<span class="number">0</span>].<span class="property">license</span></span><br><span class="line">          &#125;));</span><br><span class="line">          <span class="keyword">await</span> apigw.<span class="title function_">send</span>(<span class="keyword">new</span> <span class="title class_">CreateUsagePlanKeyCommand</span>(&#123;</span><br><span class="line">            <span class="attr">usagePlanId</span>: <span class="variable constant_">USAGE_PLAN_ID</span>,</span><br><span class="line">            <span class="attr">keyId</span>: apiKey.<span class="property">id</span>,</span><br><span class="line">            <span class="attr">keyType</span>: <span class="string">&#x27;API_KEY&#x27;</span></span><br><span class="line">          &#125;));</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">statusCode</span>: <span class="number">200</span>,</span><br><span class="line">        <span class="attr">headers</span>: &#123;</span><br><span class="line">          <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;&#125;)</span><br><span class="line">      &#125;;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">statusCode</span>: <span class="number">403</span>,</span><br><span class="line">        <span class="attr">headers</span>: &#123;</span><br><span class="line">          <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;<span class="attr">error</span>: <span class="string">&#x27;Invalid signature.&#x27;</span>&#125;)</span><br><span class="line">      &#125;;  </span><br><span class="line">    &#125; </span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">statusCode</span>: <span class="number">404</span>,</span><br><span class="line">      <span class="attr">headers</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">body</span>:  <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;<span class="attr">error</span>: <span class="string">&#x27;Not found.&#x27;</span>&#125;)</span><br><span class="line">    &#125;;    </span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>Last but not least, the API Gateway must be configured to validate the API key and usage plan. The following CloudFormation snippet shows how to configure the API Gateway.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">ApiGateway:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::ApiGateway::RestApi&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">ApiKeySourceType:</span> <span class="string">HEADER</span></span><br><span class="line">    <span class="attr">Body:</span></span><br><span class="line">      <span class="attr">&#x27;Fn::Transform&#x27;:</span></span><br><span class="line">        <span class="attr">Name:</span> <span class="string">&#x27;AWS::Include&#x27;</span></span><br><span class="line">        <span class="attr">Parameters:</span></span><br><span class="line">          <span class="attr">Location:</span> <span class="string">&#x27;./api-schema.yml&#x27;</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;A cloudonaut.io example.&#x27;</span></span><br><span class="line">    <span class="attr">Name:</span> <span class="string">&#x27;demo&#x27;</span></span><br><span class="line">    <span class="attr">EndpointConfiguration:</span></span><br><span class="line">      <span class="attr">Types:</span> [ <span class="string">&#x27;REGIONAL&#x27;</span>]</span><br></pre></td></tr></table></figure><p>Details are defined in the Swagger configuration file <code>api-schema.yml</code> references from the previous CloudFormation snippet. Note that the path <code>/v1/demo</code> requires an <code>api_key</code> to grant access. The API Gateway forwards <code>POST</code> requests to <code>/v1/demo</code> to the backend system <code>https://example.com/api/v1/demo</code>.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">swagger:</span> <span class="string">&#x27;2.0&#x27;</span></span><br><span class="line"><span class="attr">basePath:</span> <span class="string">&#x27;/&#x27;</span></span><br><span class="line"><span class="attr">schemes:</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">https</span></span><br><span class="line"><span class="attr">info:</span></span><br><span class="line">  <span class="attr">title:</span> <span class="string">&#x27;demo-api&#x27;</span></span><br><span class="line">  <span class="attr">version:</span> <span class="string">&#x27;1.0.0&#x27;</span></span><br><span class="line"><span class="attr">x-amazon-apigateway-request-validators:</span></span><br><span class="line">  <span class="attr">basic:</span></span><br><span class="line">    <span class="attr">validateRequestBody:</span> <span class="literal">false</span></span><br><span class="line">    <span class="attr">validateRequestParameters:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">x-amazon-apigateway-request-validator:</span> <span class="string">basic</span></span><br><span class="line"><span class="attr">securityDefinitions:</span></span><br><span class="line">  <span class="attr">api_key:</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">&quot;apiKey&quot;</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">&quot;x-api-key&quot;</span></span><br><span class="line">    <span class="attr">in:</span> <span class="string">&quot;header&quot;</span></span><br><span class="line"><span class="attr">x-amazon-apigateway-gateway-responses:</span></span><br><span class="line">  <span class="attr">INVALID_API_KEY:</span></span><br><span class="line">    <span class="attr">statusCode:</span> <span class="number">401</span></span><br><span class="line">    <span class="attr">responseTemplates:</span></span><br><span class="line">      <span class="attr">&#x27;application/json&#x27;:</span> <span class="string">&#x27;&#123;&quot;error&quot;: &quot;Invalid API key.&quot;&#125;&#x27;</span></span><br><span class="line">  <span class="attr">THROTTLED:</span></span><br><span class="line">    <span class="attr">statusCode:</span> <span class="number">429</span></span><br><span class="line">    <span class="attr">responseTemplates:</span></span><br><span class="line">      <span class="attr">&#x27;application/json&#x27;:</span> <span class="string">&#x27;&#123;&quot;error&quot;: &quot;Rate limit exceeded.&quot;&#125;&#x27;</span></span><br><span class="line">  <span class="attr">QUOTA_EXCEEDED:</span></span><br><span class="line">    <span class="attr">statusCode:</span> <span class="number">429</span></span><br><span class="line">    <span class="attr">responseTemplates:</span></span><br><span class="line">      <span class="attr">&#x27;application/json&#x27;:</span> <span class="string">&#x27;&#123;&quot;error&quot;: &quot;Quota exceeded.&quot;&#125;&#x27;</span></span><br><span class="line"><span class="attr">paths:</span></span><br><span class="line">  <span class="string">&#x27;/v1/demo&#x27;</span><span class="string">:</span></span><br><span class="line">    <span class="attr">post:</span></span><br><span class="line">      <span class="attr">security:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">api_key:</span> []</span><br><span class="line">      <span class="attr">responses:</span></span><br><span class="line">        <span class="attr">&quot;200&quot;:</span></span><br><span class="line">          <span class="attr">description:</span> <span class="string">OK</span></span><br><span class="line">      <span class="attr">x-amazon-apigateway-integration:</span></span><br><span class="line">        <span class="attr">type:</span> <span class="string">&#x27;http&#x27;</span></span><br><span class="line">        <span class="attr">httpMethod:</span> <span class="string">&#x27;POST&#x27;</span></span><br><span class="line">        <span class="attr">uri:</span> <span class="string">&#x27;https://example.com/api/v1/demo&#x27;</span></span><br><span class="line">        <span class="attr">responses:</span></span><br><span class="line">          <span class="attr">default:</span></span><br><span class="line">            <span class="attr">statusCode:</span> <span class="string">&#x27;200&#x27;</span></span><br><span class="line">        <span class="attr">passthroughBehavior:</span> <span class="string">&#x27;when_no_match&#x27;</span></span><br><span class="line">        <span class="attr">contentHandling:</span> <span class="string">&#x27;CONVERT_TO_BINARY&#x27;</span></span><br><span class="line"><span class="attr">definitions:</span></span><br><span class="line">  <span class="attr">Error:</span></span><br><span class="line">    <span class="attr">properties:</span></span><br><span class="line">      <span class="attr">error:</span></span><br><span class="line">        <span class="attr">type:</span> <span class="string">string</span></span><br><span class="line">    <span class="attr">required:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">error</span></span><br></pre></td></tr></table></figure><blockquote><p>Need help with implementing a similar solution? Let me know!</p></blockquote><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>When selling APIs to potential customers who are most likely already AWS customers, AWS Marketplace is a great choice. However, when selling to potential customers without an AWS account, a solution consisting of API Gateway, usage plans, API keys, Lambda, and FastSpring is a simple but powerful alternative.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Cleaning up AMIs</title>
      <link>https://cloudonaut.io/cleaning-up-amis/</link>
      <description>Delete unused AMIs with the command line tool aws-amicleaner to reduce costs.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/ebs/">ebs</category>
      <guid isPermaLink="true">https://cloudonaut.io/cleaning-up-amis/</guid>
      <pubDate>Wed, 08 May 2024 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Costs are like fingernails. You have to cut them constantly. When working with AWS, cleaning up unused resources is crucial. Otherwise, you will end up with a steadily growing AWS bill and waste money.</p><p>Do you build AMIs automatically, for example, with Packer? Learn how to automatically clean up unused AMIs to reduce EBS storage consumption and costs.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/05/cleaning-up-amis-title@730w.webp 730w, /images/2024/05/cleaning-up-amis-title@730w2x.webp 1460w, /images/2024/05/cleaning-up-amis-title@610w.webp 610w, /images/2024/05/cleaning-up-amis-title@610w2x.webp 1220w, /images/2024/05/cleaning-up-amis-title@450w.webp 450w, /images/2024/05/cleaning-up-amis-title@450w2x.webp 900w, /images/2024/05/cleaning-up-amis-title@330w.webp 330w, /images/2024/05/cleaning-up-amis-title@330w2x.webp 660w, /images/2024/05/cleaning-up-amis-title@545w.webp 545w, /images/2024/05/cleaning-up-amis-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/05/cleaning-up-amis-title@730w.png 730w, /images/2024/05/cleaning-up-amis-title@730w2x.png 1460w, /images/2024/05/cleaning-up-amis-title@610w.png 610w, /images/2024/05/cleaning-up-amis-title@610w2x.png 1220w, /images/2024/05/cleaning-up-amis-title@450w.png 450w, /images/2024/05/cleaning-up-amis-title@450w2x.png 900w, /images/2024/05/cleaning-up-amis-title@330w.png 330w, /images/2024/05/cleaning-up-amis-title@330w2x.png 660w, /images/2024/05/cleaning-up-amis-title@545w.png 545w, /images/2024/05/cleaning-up-amis-title@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/05/cleaning-up-amis-title.png" alt="Cleaning up AMIs" title="Cleaning up AMIs"></picture></p><p>About a year ago, the tool we used to remove unused AMIs stopped working. As we could not find a working alternative, we started the project <a href="https://github.com/widdix/aws-amicleaner" target="_blank" rel="noopener">aws-amicleaner</a>.</p><p>How does the <a href="https://github.com/widdix/aws-amicleaner" target="_blank" rel="noopener">aws-amicleaner</a> command line tool work?</p><ol><li>Include AMIs by name or tag.</li><li>Exclude AMIs in use, younger than N days, or the newest N images.</li><li>Manually confirm the list of AMIs for deletion.</li><li>Delete AMIs and linked EBS snapshots.</li></ol><h2 id="Examples"><a href="#Examples" class="headerlink" title="Examples"></a>Examples</h2><blockquote><p>Requires Node.js &gt;&#x3D; 18.</p></blockquote><p>The following example shows how to delete all AMIs in <code>eu-west-1</code> matching the following conditions:</p><ul><li>Name starts with <code>amiprefix-</code></li><li>Older than five days</li><li>Not in use by EC2 instances, ASGs, Launch Configurations, and Launch Templates.</li></ul><p>Also, keep the newest three images that match the conditions.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npx aws-amicleaner --region eu-west-1 --include-name <span class="string">&#x27;amiprefix-*&#x27;</span> --exclude-newest 3 --exclude-days 5 --exclude-in-use --verbose</span><br></pre></td></tr></table></figure><p>The following listing shows a typical confirmation screen.</p><figure class="highlight gherkin"><table><tr><td class="code"><pre><span class="line">+-----------+-------------+----------------------+--------------------------+---------+-----------------+-------------------------+</span><br><span class="line">|<span class="string"> Region    </span>|<span class="string"> ID          </span>|<span class="string"> Name                 </span>|<span class="string"> Creation Date            </span>|<span class="string"> Delete? </span>|<span class="string"> Include reasons </span>|<span class="string"> Exclude reasons         </span>|<span class="string"> </span></span><br><span class="line"><span class="string">+-----------+-------------+----------------------+--------------------------+---------+-----------------+-------------------------+</span></span><br><span class="line"><span class="string"></span>|<span class="string"> eu-west-1 </span>|<span class="string"> ami-0a...72 </span>|<span class="string"> amiprefix-1685107232 </span>|<span class="string"> 2023-05-27T13:32:21.000Z </span>|<span class="string"> no      </span>|<span class="string"> name match      </span>|<span class="string"> days not passed, newest </span>|<span class="string"> </span></span><br><span class="line"><span class="string"></span>|<span class="string"> eu-west-1 </span>|<span class="string"> ami-02...3f </span>|<span class="string"> amiprefix-1685103569 </span>|<span class="string"> 2023-05-27T12:30:50.000Z </span>|<span class="string"> no      </span>|<span class="string"> name match      </span>|<span class="string"> days not passed, newest </span>|<span class="string"> </span></span><br><span class="line"><span class="string"></span>|<span class="string"> eu-west-1 </span>|<span class="string"> ami-09...d5 </span>|<span class="string"> amiprefix-1685095689 </span>|<span class="string"> 2023-05-27T10:19:59.000Z </span>|<span class="string"> no      </span>|<span class="string"> name match      </span>|<span class="string"> days not passed, newest </span>|<span class="string"> </span></span><br><span class="line"><span class="string"></span>|<span class="string"> eu-west-1 </span>|<span class="string"> ami-0f...c7 </span>|<span class="string"> amiprefix-1685039741 </span>|<span class="string"> 2023-05-26T18:47:37.000Z </span>|<span class="string"> no      </span>|<span class="string"> name match      </span>|<span class="string"> days not passed         </span>|<span class="string"> </span></span><br><span class="line"><span class="string"></span>|<span class="string"> eu-west-1 </span>|<span class="string"> ami-0f...f0 </span>|<span class="string"> amiprefix-1685018189 </span>|<span class="string"> 2023-05-26T12:49:02.000Z </span>|<span class="string"> no      </span>|<span class="string"> name match      </span>|<span class="string"> days not passed         </span>|<span class="string"> </span></span><br><span class="line"><span class="string"></span>|<span class="string"> eu-west-1 </span>|<span class="string"> ami-06...a8 </span>|<span class="string"> amiprefix-1685015512 </span>|<span class="string"> 2023-05-26T12:04:39.000Z </span>|<span class="string"> no      </span>|<span class="string"> name match      </span>|<span class="string"> days not passed         </span>|<span class="string"> </span></span><br><span class="line"><span class="string"></span>|<span class="string"> eu-west-1 </span>|<span class="string"> ami-04...44 </span>|<span class="string"> amiprefix-1684998014 </span>|<span class="string"> 2023-05-25T07:14:42.000Z </span>|<span class="string"> yes     </span>|<span class="string"> name match      </span>|<span class="string">                         </span>|</span><br><span class="line">|<span class="string"> eu-west-1 </span>|<span class="string"> ami-02...6e </span>|<span class="string"> amiprefix-1684954911 </span>|<span class="string"> 2023-05-24T19:15:37.000Z </span>|<span class="string"> yes     </span>|<span class="string"> name match      </span>|<span class="string">                         </span>|</span><br><span class="line">|<span class="string"> eu-west-1 </span>|<span class="string"> ami-0e...da </span>|<span class="string"> amiprefix-1684952424 </span>|<span class="string"> 2023-05-24T18:32:06.000Z </span>|<span class="string"> yes     </span>|<span class="string"> name match      </span>|<span class="string">                         </span>|</span><br><span class="line">|<span class="string"> eu-west-1 </span>|<span class="string"> ami-0b...54 </span>|<span class="string"> amiprefix-1684949922 </span>|<span class="string"> 2023-05-24T17:50:26.000Z </span>|<span class="string"> yes     </span>|<span class="string"> name match      </span>|<span class="string">                         </span>|</span><br><span class="line">|<span class="string"> eu-west-1 </span>|<span class="string"> ami-0b...2c </span>|<span class="string"> amiprefix-1684937102 </span>|<span class="string"> 2023-05-24T14:17:53.000Z </span>|<span class="string"> yes     </span>|<span class="string"> name match      </span>|<span class="string">                         </span>|</span><br><span class="line">|<span class="string"> eu-west-1 </span>|<span class="string"> ami-0e...aa </span>|<span class="string"> amiprefix-1684915092 </span>|<span class="string"> 2023-05-24T08:09:42.000Z </span>|<span class="string"> yes     </span>|<span class="string"> name match      </span>|<span class="string">                         </span>|</span><br><span class="line">+-----------+-------------+----------------------+--------------------------+---------+-----------------+-------------------------+</span><br><span class="line"></span><br><span class="line">Do you want to continue and remove 6 AMIs [y/N] ? : </span><br></pre></td></tr></table></figure><p>The following example shows how to delete AMIs from all regions that start with <code>eu-west-*</code>, which resolves to <code>eu-west-1</code>, <code>eu-west-2</code>, and <code>eu-west-3</code> those days. Also, the example filters AMIs by tag key <code>CostCenter</code> and tag value <code>X342-*1111</code> and the age of 7 days. Besides that the <a href="https://github.com/widdix/aws-amicleaner" target="_blank" rel="noopener">aws-amicleaner</a> keeps the newest five images matching those conditions.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npx aws-amicleaner --region <span class="string">&#x27;eu-west-*&#x27;</span> --include-tag-key CostCenter --include-tag-value <span class="string">&#x27;X342-*-1111&#x27;</span></span><br></pre></td></tr></table></figure><p>Looking for a way to run the <a href="https://github.com/widdix/aws-amicleaner" target="_blank" rel="noopener">aws-amicleaner</a> in an automated way. Use the <code>--force-delete</code> option to skip the manual approval. We use GitHub Actions to remove unused AMIs once per month.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npx aws-amicleaner --region <span class="string">&#x27;eu-west-*&#x27;</span> --include-tag-key CostCenter --include-tag-value <span class="string">&#x27;X342-*-1111&#x27;</span> --force-delete</span><br></pre></td></tr></table></figure><h2 id="Arguments"><a href="#Arguments" class="headerlink" title="Arguments"></a>Arguments</h2><p>Want more? Here is a list of all available arguments supported by <a href="https://github.com/widdix/aws-amicleaner" target="_blank" rel="noopener">aws-amicleaner</a>. </p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">-h, <span class="comment">--help            show this help message and exit</span></span><br><span class="line"><span class="comment">--region REGION       The AWS region, e.g. us-east-1, arg can be used more than once, wildcard * supported</span></span><br><span class="line"><span class="comment">--include-name INCLUDENAME</span></span><br><span class="line">                      The <span class="type">name</span> that must be present, wildcard * supported</span><br><span class="line"><span class="comment">--include-tag-key INCLUDETAGKEY</span></span><br><span class="line">                      The tag key that must be present</span><br><span class="line"><span class="comment">--include-tag-value INCLUDETAGVALUE</span></span><br><span class="line">                      The tag <span class="keyword">value</span> (<span class="keyword">for</span> the tag key) that must be present, wildcard * supported</span><br><span class="line"><span class="comment">--exclude-newest EXCLUDENEWEST</span></span><br><span class="line">                      <span class="keyword">Exclude</span> the newest N AMIs</span><br><span class="line"><span class="comment">--exclude-days EXCLUDEDAYS</span></span><br><span class="line">                      <span class="keyword">Exclude</span> AMIs <span class="keyword">from</span> deletion that are younger than N days</span><br><span class="line"><span class="comment">--exclude-in-use, --no-exclude-in-use</span></span><br><span class="line">                      <span class="keyword">Exclude</span> AMIs <span class="keyword">from</span> deletion that are <span class="keyword">in</span> use <span class="keyword">by</span> EC2 instances, ASGs, Launch Configurations, <span class="keyword">and</span> Launch Templates (<span class="keyword">default</span>: <span class="keyword">true</span>)</span><br><span class="line">-f, <span class="comment">--force-delete, --no-force-delete</span></span><br><span class="line">                      Skip confirmation <span class="keyword">before</span> deletion (<span class="keyword">default</span>: <span class="keyword">false</span>)</span><br><span class="line"><span class="comment">--verbose, --no-verbose</span></span><br><span class="line">                      Display additional information (<span class="keyword">default</span>: <span class="keyword">true</span>)</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>The <a href="https://github.com/widdix/aws-amicleaner" target="_blank" rel="noopener">aws-amicleaner</a> is a small but helpfull tool to delete unused AMIs and the corresponding EBS snapshots to reduce costs.</p><p>It’s simple to get started (requires Node.js &gt;&#x3D; 18).</p><figure class="highlight ada"><table><tr><td class="code"><pre><span class="line">npx aws-amicleaner <span class="comment">--help</span></span><br></pre></td></tr></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to reduce costs for GitHub Actions?</title>
      <link>https://cloudonaut.io/how-to-reduce-costs-for-github-actions/</link>
      <description>Discover cost-saving strategies for GitHub Actions with our guide on utilizing Octolense for expenditure insights and HyperEnv for deploying economical self-hosted runners on AWS.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/github/">github</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-reduce-costs-for-github-actions/</guid>
      <pubDate>Tue, 27 Feb 2024 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>GitHub Actions is my favorite CI&#x2F;CD solution. Over the past year, I gradually switched all projects from CodePipeline to GitHub Actions. To this day, I enjoy the smooth user experience. However, GitHub-hosted runners quickly become a big item on GitHub’s monthly bill.</p><p>That’s why I will share how to reduce costs for GitHub Actions with Octolense by Sandro Volpicella and HyperEnv for GitHub Actions Runner made by Michael and me in the following.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/02/how-to-reduce-costs-for-github-actions-title@730w.webp 730w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@730w2x.webp 1460w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@610w.webp 610w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@610w2x.webp 1220w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@450w.webp 450w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@450w2x.webp 900w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@330w.webp 330w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@330w2x.webp 660w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@545w.webp 545w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/02/how-to-reduce-costs-for-github-actions-title@730w.jpg 730w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@730w2x.jpg 1460w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@610w.jpg 610w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@610w2x.jpg 1220w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@450w.jpg 450w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@450w2x.jpg 900w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@330w.jpg 330w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@330w2x.jpg 660w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@545w.jpg 545w, /images/2024/02/how-to-reduce-costs-for-github-actions-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/02/how-to-reduce-costs-for-github-actions-title.jpg" alt="How to reduce costs for GitHub Actions?" title="How to reduce costs for GitHub Actions?"></picture></p><blockquote><p>Do you prefer watching a video instead of reading? Here you go!</p></blockquote><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=tS_ymzoW9W8">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/tS_ymzoW9W8" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="Get-insights-into-usage-with-Octolense"><a href="#Get-insights-into-usage-with-Octolense" class="headerlink" title="Get insights into usage with Octolense"></a>Get insights into usage with Octolense</h2><p>Analyzing usage is vital when it comes to cutting costs. The pricing model of GitHub-hosted runners is simple. You pay for every build minute depending on the operating system and number of CPUs. Here is an excerpt of the GitHub Actions pricing page.</p><table class="table table-striped table-responsive"><thead><tr><th>Operating system</th><th>vCPUs</th><th>Per-minute rate (USD)</th></tr></thead><tbody><tr><td>Linux</td><td>2</td><td>$0.008</td></tr><tr><td>Linux</td><td>4</td><td>$0.016</td></tr><tr><td>Linux</td><td>8</td><td>$0.032</td></tr><tr><td>Linux</td><td>16</td><td>$0.064</td></tr><tr><td>Linux</td><td>32</td><td>$0.128</td></tr><tr><td>Linux</td><td>64</td><td>$0.256</td></tr><tr><td>Windows</td><td>2</td><td>$0.016</td></tr><tr><td>Windows</td><td>8</td><td>$0.064</td></tr><tr><td>Windows</td><td>16</td><td>$0.128</td></tr><tr><td>Windows</td><td>32</td><td>$0.256</td></tr><tr><td>Windows</td><td>64</td><td>$0.512</td></tr><tr><td>macOS</td><td>3 or 4 (M1 or Intel)</td><td>$0.08</td></tr><tr><td>macOS</td><td>12</td><td>$0.12</td></tr><tr><td>macOS</td><td>6 (M1)</td><td>$0.16</td></tr></tbody></table><p>How do you find out which repository consumes the most build minutes?</p><p>Unfortunately, GitHub only provides insights into the total amount of consumed build minutes.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/02/github-billing-actions@730w.webp 730w, /images/2024/02/github-billing-actions@730w2x.webp 1460w, /images/2024/02/github-billing-actions@610w.webp 610w, /images/2024/02/github-billing-actions@610w2x.webp 1220w, /images/2024/02/github-billing-actions@450w.webp 450w, /images/2024/02/github-billing-actions@450w2x.webp 900w, /images/2024/02/github-billing-actions@330w.webp 330w, /images/2024/02/github-billing-actions@330w2x.webp 660w, /images/2024/02/github-billing-actions@545w.webp 545w, /images/2024/02/github-billing-actions@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/02/github-billing-actions@730w.png 730w, /images/2024/02/github-billing-actions@730w2x.png 1460w, /images/2024/02/github-billing-actions@610w.png 610w, /images/2024/02/github-billing-actions@610w2x.png 1220w, /images/2024/02/github-billing-actions@450w.png 450w, /images/2024/02/github-billing-actions@450w2x.png 900w, /images/2024/02/github-billing-actions@330w.png 330w, /images/2024/02/github-billing-actions@330w2x.png 660w, /images/2024/02/github-billing-actions@545w.png 545w, /images/2024/02/github-billing-actions@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/02/github-billing-actions.png" alt="GitHub: The billing overview shows the total amount of consumed build minutes." title="GitHub: The billing overview shows the total amount of consumed build minutes."></picture></p><p>The ability to generate a CSV report only helps a little to analyze spending.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">Date</span>,Product,SKU,Quantity,Unit Type,Price Per Unit ($),Multiplier,Owner,Repository Slug,Username,Actions Workflow,Notes</span><br><span class="line"><span class="attribute">2024</span>-<span class="number">02</span>-<span class="number">22</span>,Actions,Compute - UBUNTU,<span class="number">30</span>,minute,<span class="number">0</span>.<span class="number">008</span>,<span class="number">1</span>.<span class="number">0</span>,stratocumulus,android,andreaswittig,.github/workflows/build.yml,</span><br><span class="line"><span class="attribute">2024</span>-<span class="number">02</span>-<span class="number">22</span>,Actions,Compute - UBUNTU,<span class="number">90</span>,minute,<span class="number">0</span>.<span class="number">008</span>,<span class="number">1</span>.<span class="number">0</span>,stratocumulus,ios,andreaswittig,.github/workflows/build.yml,</span><br><span class="line"><span class="attribute">2024</span>-<span class="number">02</span>-<span class="number">22</span>,Actions,Compute - UBUNTU,<span class="number">60</span>,minute,<span class="number">0</span>.<span class="number">008</span>,<span class="number">1</span>.<span class="number">0</span>,stratocumulus,spa,andreaswittig,.github/workflows/build.yml,</span><br></pre></td></tr></table></figure><p>Luckily, Sandro built Octolense, a simple-to-use and powerful tool to get insights into GitHub Actions usage. After connecting with GitHub, Octolense opens a dashboard showing the key metrics <em>Billable Minutes</em>, <em>Paid Minutes</em>, and <em>Workflow Runs</em>. Besides that, the dashboard lists the <em>Top Cost Intensive Repositories</em>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/02/octolense-dashboard@730w.webp 730w, /images/2024/02/octolense-dashboard@730w2x.webp 1460w, /images/2024/02/octolense-dashboard@610w.webp 610w, /images/2024/02/octolense-dashboard@610w2x.webp 1220w, /images/2024/02/octolense-dashboard@450w.webp 450w, /images/2024/02/octolense-dashboard@450w2x.webp 900w, /images/2024/02/octolense-dashboard@330w.webp 330w, /images/2024/02/octolense-dashboard@330w2x.webp 660w, /images/2024/02/octolense-dashboard@545w.webp 545w, /images/2024/02/octolense-dashboard@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/02/octolense-dashboard@730w.png 730w, /images/2024/02/octolense-dashboard@730w2x.png 1460w, /images/2024/02/octolense-dashboard@610w.png 610w, /images/2024/02/octolense-dashboard@610w2x.png 1220w, /images/2024/02/octolense-dashboard@450w.png 450w, /images/2024/02/octolense-dashboard@450w2x.png 900w, /images/2024/02/octolense-dashboard@330w.png 330w, /images/2024/02/octolense-dashboard@330w2x.png 660w, /images/2024/02/octolense-dashboard@545w.png 545w, /images/2024/02/octolense-dashboard@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/02/octolense-dashboard.png" alt="Octolense: The dashboard with insights into GitHub Actions usage" title="Octolense: The dashboard with insights into GitHub Actions usage"></picture></p><p>And there is more. Opening the detail view of a repository unveils insights into successful and failed workflow runs.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/02/octolense-repository@730w.webp 730w, /images/2024/02/octolense-repository@730w2x.webp 1460w, /images/2024/02/octolense-repository@610w.webp 610w, /images/2024/02/octolense-repository@610w2x.webp 1220w, /images/2024/02/octolense-repository@450w.webp 450w, /images/2024/02/octolense-repository@450w2x.webp 900w, /images/2024/02/octolense-repository@330w.webp 330w, /images/2024/02/octolense-repository@330w2x.webp 660w, /images/2024/02/octolense-repository@545w.webp 545w, /images/2024/02/octolense-repository@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/02/octolense-repository@730w.png 730w, /images/2024/02/octolense-repository@730w2x.png 1460w, /images/2024/02/octolense-repository@610w.png 610w, /images/2024/02/octolense-repository@610w2x.png 1220w, /images/2024/02/octolense-repository@450w.png 450w, /images/2024/02/octolense-repository@450w2x.png 900w, /images/2024/02/octolense-repository@330w.png 330w, /images/2024/02/octolense-repository@330w2x.png 660w, /images/2024/02/octolense-repository@545w.png 545w, /images/2024/02/octolense-repository@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/02/octolense-repository.png" alt="Octolense: Detailed insights into workflow runs" title="Octolense: Detailed insights into workflow runs"></picture></p><p>Octolense provides valuable insights into the use of GitHub actions and allows you to identify the repositories where optimizing your CI&#x2F;CD workflows will have the most significant impact.</p><blockquote><p><a href="https://octolense.com/?utm_medium=Referral&utm_source=cloudonaut&utm_campaign=blogpost_github_actions_costs" target="_blank" rel="noopener">Get started with Octolense to analyze GitHub Actions usage!</a></p></blockquote><h2 id="Reduce-costs-with-self-hosted-runners-by-HyperEnv"><a href="#Reduce-costs-with-self-hosted-runners-by-HyperEnv" class="headerlink" title="Reduce costs with self-hosted runners by HyperEnv"></a>Reduce costs with self-hosted runners by HyperEnv</h2><p>By default, a workflow job runs on a GitHub-hosted runner. Alternatively, you can also use self-hosted runners to execute workflow jobs. Michael and I built HyperEnv for GitHub Actions Runner to make deploying self-hosted runners on AWS as easy as possible. </p><p>How does HyperEnv work? The following diagram illustrates the components automatically deployed to your AWS account within 10 minutes.</p><ol><li>GitHub sends a webhook event whenever a new job gets created.</li><li>HyperEnv launches an EC2 instance with GitHub runner and build environments pre-installed.</li><li>The GitHub runner executes the job.</li><li>The EC2 instance terminates.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/02/hyperenv-architecture@730w.webp 730w, /images/2024/02/hyperenv-architecture@730w2x.webp 1460w, /images/2024/02/hyperenv-architecture@610w.webp 610w, /images/2024/02/hyperenv-architecture@610w2x.webp 1220w, /images/2024/02/hyperenv-architecture@450w.webp 450w, /images/2024/02/hyperenv-architecture@450w2x.webp 900w, /images/2024/02/hyperenv-architecture@330w.webp 330w, /images/2024/02/hyperenv-architecture@330w2x.webp 660w, /images/2024/02/hyperenv-architecture@545w.webp 545w, /images/2024/02/hyperenv-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/02/hyperenv-architecture@730w.jpg 730w, /images/2024/02/hyperenv-architecture@730w2x.jpg 1460w, /images/2024/02/hyperenv-architecture@610w.jpg 610w, /images/2024/02/hyperenv-architecture@610w2x.jpg 1220w, /images/2024/02/hyperenv-architecture@450w.jpg 450w, /images/2024/02/hyperenv-architecture@450w2x.jpg 900w, /images/2024/02/hyperenv-architecture@330w.jpg 330w, /images/2024/02/hyperenv-architecture@330w2x.jpg 660w, /images/2024/02/hyperenv-architecture@545w.jpg 545w, /images/2024/02/hyperenv-architecture@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/02/hyperenv-architecture.jpg" alt="HyperEnv: Spin up EC2 instances on-demand to execute GitHub Actions jobs" title="HyperEnv: Spin up EC2 instances on-demand to execute GitHub Actions jobs"></picture></p><p>Switching from GitHub-hosted runners to HyperEnv reduces costs by at least 30%.</p><p>The following table shows a cost estimation for <strong>20,000 build minutes</strong> running on <code>t3.large</code> instances (2 vCPU, 8 GB memory, 25 GB volume) in <code>us-east-1</code>.</p><table class="table table-striped table-responsive"><thead><tr><th>Category</th><th>Description</th><th>Costs</th></tr></thead><tbody><tr><td>HyperEnv for GitHub Actions Runner</td><td>$0.002 per vCPU and build minute</td><td>$80.00</td></tr><tr><td>EC2 instance</td><td>$0.0014 per build minute</td><td>$27.73</td></tr><tr><td>EBS volume</td><td>$0.0000019 per GB and build minute</td><td>$0.93</td></tr><tr><td>CloudWatch and others</td><td>$0.00001 per build minute</td><td>$0.20</td></tr><tr><td><strong>Total Costs</strong></td><td><strong>HyperEnv for GitHub Actions Runner + AWS infrastructure</strong></td><td><strong>$110.66</strong></td></tr></tbody></table><p>That’s cost savings of 31% compared to GitHub-hosted runners.</p><p>Besides, many workflows do not require a machine with 2 vCPUs and 8 GB memory. By choosing a smaller instance type, you can reduce the costs for GitHub Actions executed on self-hosted runners even more.</p><blockquote><p><a href="https://hyperenv.com/github-actions/?utm_medium=Referral&utm_source=cloudonaut&utm_campaign=blogpost_github_actions_costs" target="_blank" rel="noopener">Deploy HyperEnv for GitHub Actions Runner and reduce costs by at least 30%!</a></p></blockquote><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Gain insights into your GitHub actions usage with <a href="https://octolense.com/?utm_medium=Referral&utm_source=cloudonaut&utm_campaign=blogpost_github_actions_costs" target="_blank" rel="noopener">Octolense</a> and reduce costs for GitHub Actions by switching from GitHub-hosted Runners to <a href="https://hyperenv.com/github-actions/?utm_medium=Referral&utm_source=cloudonaut&utm_campaign=blogpost_github_actions_costs" target="_blank" rel="noopener">HyperEnv</a>.</p><p><em>Do you have any other tips on how to reduce costs for GitHub Actions? Let me know!</em></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>KMS Key Policy Privilege Escalation</title>
      <link>https://cloudonaut.io/kms-key-policy-privilege-escalation/</link>
      <description>How can IAM identities escalate privileges to access customer-managed KMS keys protected by a key policy?</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <category domain="https://cloudonaut.io/tag/kms/">kms</category>
      <guid isPermaLink="true">https://cloudonaut.io/kms-key-policy-privilege-escalation/</guid>
      <pubDate>Wed, 31 Jan 2024 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Encrypting data at rest is a widespread best practice on AWS. In 2019, Werner Vogels set the tone with his motivational slogan, “Dance like nobody’s watching. Encrypt like everyone is!”. AWS shipped the ability to encrypt data at rest for almost all its services. Many services use the AWS Key Management Service (KMS) to handle the keys for server-side encryption. KMS provides default keys, which are very simple to use, and customer-managed keys with an extra authorization layer.</p><p>Are you defining key policies to strictly restrict access to customer-managed keys? Then, the following will blow your mind. Under some circumstances, an IAM identity (user or role) can grant itself administrator access to any customer-managed key.</p><blockquote><p>This is not an unknown security vulnerability. The procedured explained here is discussed at <a href="https://repost.aws/questions/QUV7ubqz8ETRCOxHSuSH6zDQ/unable-to-delete-kms-customer-managed-key-cmk-using-administratoraccess-role-or-root-login-credentials" target="_blank" rel="noopener">re:Post</a>, for example.</p></blockquote><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/01/kms-key-policy-privilege-escalation-title@730w.webp 730w, /images/2024/01/kms-key-policy-privilege-escalation-title@730w2x.webp 1460w, /images/2024/01/kms-key-policy-privilege-escalation-title@610w.webp 610w, /images/2024/01/kms-key-policy-privilege-escalation-title@610w2x.webp 1220w, /images/2024/01/kms-key-policy-privilege-escalation-title@450w.webp 450w, /images/2024/01/kms-key-policy-privilege-escalation-title@450w2x.webp 900w, /images/2024/01/kms-key-policy-privilege-escalation-title@330w.webp 330w, /images/2024/01/kms-key-policy-privilege-escalation-title@330w2x.webp 660w, /images/2024/01/kms-key-policy-privilege-escalation-title@545w.webp 545w, /images/2024/01/kms-key-policy-privilege-escalation-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/01/kms-key-policy-privilege-escalation-title@730w.jpg 730w, /images/2024/01/kms-key-policy-privilege-escalation-title@730w2x.jpg 1460w, /images/2024/01/kms-key-policy-privilege-escalation-title@610w.jpg 610w, /images/2024/01/kms-key-policy-privilege-escalation-title@610w2x.jpg 1220w, /images/2024/01/kms-key-policy-privilege-escalation-title@450w.jpg 450w, /images/2024/01/kms-key-policy-privilege-escalation-title@450w2x.jpg 900w, /images/2024/01/kms-key-policy-privilege-escalation-title@330w.jpg 330w, /images/2024/01/kms-key-policy-privilege-escalation-title@330w2x.jpg 660w, /images/2024/01/kms-key-policy-privilege-escalation-title@545w.jpg 545w, /images/2024/01/kms-key-policy-privilege-escalation-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/01/kms-key-policy-privilege-escalation-title.jpg" alt="KMS Key Policy Privilege Escalation" title="KMS Key Policy Privilege Escalation"></picture></p><h2 id="The-story"><a href="#The-story" class="headerlink" title="The story"></a>The story</h2><p>Bob, the owner of an AWS account, is granted <code>AdministratorAccess</code>. He creates a customer-managed key and assigns the following key policy. He wants to ensure that no one else can delete or modify the key, so he only grants his own user administrator access to the key.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2012-10-17&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Statement&quot;</span><span class="punctuation">:</span><span class="punctuation">[</span><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;Sid&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow access for Key Administrators&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;Principal&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;AWS&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;arn:aws:iam::091140455148:user/bob&quot;</span></span><br><span class="line">      <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;kms:Create*&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;kms:Describe*&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;kms:Enable*&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;kms:List*&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;kms:Put*&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;kms:Update*&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;kms:Revoke*&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;kms:Disable*&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;kms:Get*&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;kms:Delete*&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;kms:TagResource&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;kms:UntagResource&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;kms:ScheduleKeyDeletion&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;kms:CancelKeyDeletion&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Alice, a colleague of Bob’s, has <code>AdministratorAccess</code> to the same AWS account. However, due to the key policy, she is not able to delete or modify the customer-managed key created by Bob. Nevertheless, Alice found a way to delete the key anyway.</p><p>First, Alice deletes the IAM user <code>arn:aws:iam::091140455148:user/bob</code>, which is not an issue as <code>iam:DeleteUser</code> is granted by the <code>AdministratorAccess</code> policy.</p><p>Next, Alice updates the phone number of the AWS account with her mobile number. The <code>AdministratorAccess</code> policy grants her access to the <code>account:PutContactInformation</code> action to do so.</p><p>Afterwards, Alice contacts AWS support and tells the story that by mistake the customer-managed key is no longer accessible to any key administrator. She asks to reset the key policy. The <code>AdministratorAccess</code> policy grants her access to the required <code>support:CreateCase</code> action.</p><p>AWS verifies the key policy. Indeed, the key is not accessible to any key administrator.</p><p>Next, AWS updates the support case and asks to create a new IAM user named <code>recovery</code>, that shall be used to reset the key. Also, AWS provides a one-time password that will be used to verify the policy reset later.</p><p>Alice creates a new IAM user as described by AWS and asks to proceed with the policy reset by updating the support case. All she needs is access to <code>iam:CreateUser</code> granted by the <code>AdministratorAccess</code> policy.</p><p>A few hours later, AWS tries to verify that the request to reset the key policy is valid. To do so, AWS calls the number specified in the contact details of the AWS account.</p><p>And guess what? Alice answers the call. She confirms the reset of the key policy.</p><p>So, the process continues. AWS resets the key policy and grants the IAM user <code>recovery</code> administrator access to the customer-managed key.</p><p>Alice creates access keys for the IAM user <code>recovery</code>, which requires access to the <code>iam:CreateAccessKey</code> action.</p><p>Finally, Alice uses these access keys to schedule the customer-managed key for deletion.</p><h2 id="The-preconditions"><a href="#The-preconditions" class="headerlink" title="The preconditions"></a>The preconditions</h2><p>Access to the following IAM actions is needed.</p><ul><li><code>iam:DeleteUser</code> to delete all IAM users listed as key administrators</li><li><code>iam:DeleteRole</code> to delete all IAM roles listed as key administrators</li><li><code>account:PutContactInformation</code> to update the contact information of the AWS account</li><li><code>support:CreateCase</code> to create a support case to ask for a key policy reset</li><li><code>iam:CreateUser</code> to create the IAM user needed for the key policy reset procedure</li><li><code>iam:CreateAccessKey</code> to create credentials for the IAM user used during the key policy reset procedure</li></ul><p>In addition, the key policy must not grant administrator access to any other existing IAM identity. In particular, the key policy must not contain the following statement, which delegates authorization entirely to IAM and is used by AWS as the default.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Sid&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Enable IAM Permissions&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Principal&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;AWS&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:iam::111122223333:root&quot;</span></span><br><span class="line">   <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="string">&quot;kms:*&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Last but not least, an AWS support plan is needed.</p><h2 id="The-mitigations"><a href="#The-mitigations" class="headerlink" title="The mitigations"></a>The mitigations</h2><p>As an AWS customer, you could restrict access to <code>account:PutContactInformation</code> using service control policies (SCPs). Alternatively, you could avoid relying on key policies and completely delegate authorization to IAM.</p><p>AWS needs to find another way to deal with key policies. A half-baked procedure executed by AWS support is not a solution and undermines my trust in KMS. In my opinion, it should not be possible to reset key policies at all.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Connect GitHub Actions with AWS VPC</title>
      <link>https://cloudonaut.io/connect-github-actions-with-aws-vpc/</link>
      <description>How to access RDS, ElasticSearch, OpenSearch, ElastiCache, or internal ALB from GitHub Actions?</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/github/">github</category>
      <guid isPermaLink="true">https://cloudonaut.io/connect-github-actions-with-aws-vpc/</guid>
      <pubDate>Fri, 19 Jan 2024 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>GitHub Actions is my preferred CI&#x2F;CD solution. I’m using GitHub Actions to build and deploy applications on AWS. However, GitHub Actions does not have access to private subnets, which is required in the following scenarios:</p><ul><li>Execute database migrations for RDS (Relational Database Service).</li><li>Run load or integration tests against internal ALBs or NLBs (Elastic Load Balancing).</li><li>Seed ElasticSearch, OpenSearch, or ElastiCache with data.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/01/connect-github-actions-with-aws-vpc-title@730w.webp 730w, /images/2024/01/connect-github-actions-with-aws-vpc-title@730w2x.webp 1460w, /images/2024/01/connect-github-actions-with-aws-vpc-title@610w.webp 610w, /images/2024/01/connect-github-actions-with-aws-vpc-title@610w2x.webp 1220w, /images/2024/01/connect-github-actions-with-aws-vpc-title@450w.webp 450w, /images/2024/01/connect-github-actions-with-aws-vpc-title@450w2x.webp 900w, /images/2024/01/connect-github-actions-with-aws-vpc-title@330w.webp 330w, /images/2024/01/connect-github-actions-with-aws-vpc-title@330w2x.webp 660w, /images/2024/01/connect-github-actions-with-aws-vpc-title@545w.webp 545w, /images/2024/01/connect-github-actions-with-aws-vpc-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/01/connect-github-actions-with-aws-vpc-title@730w.jpg 730w, /images/2024/01/connect-github-actions-with-aws-vpc-title@730w2x.jpg 1460w, /images/2024/01/connect-github-actions-with-aws-vpc-title@610w.jpg 610w, /images/2024/01/connect-github-actions-with-aws-vpc-title@610w2x.jpg 1220w, /images/2024/01/connect-github-actions-with-aws-vpc-title@450w.jpg 450w, /images/2024/01/connect-github-actions-with-aws-vpc-title@450w2x.jpg 900w, /images/2024/01/connect-github-actions-with-aws-vpc-title@330w.jpg 330w, /images/2024/01/connect-github-actions-with-aws-vpc-title@330w2x.jpg 660w, /images/2024/01/connect-github-actions-with-aws-vpc-title@545w.jpg 545w, /images/2024/01/connect-github-actions-with-aws-vpc-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/01/connect-github-actions-with-aws-vpc-title.jpg" alt="Connect GitHub Actions with AWS VPC" title="Connect GitHub Actions with AWS VPC"></picture></p><p>In the following, I will demonstrate how to access a VPC (Virtual Private Cloud) from GitHub Actions with the help of <a href="https://hyperenv.com/github-actions/" target="_blank" rel="noopener">HyperEnv for GitHub Actions Runner</a>, a solution I built recently.</p><h2 id="What-are-GitHub-hosted-runners"><a href="#What-are-GitHub-hosted-runners" class="headerlink" title="What are GitHub-hosted runners?"></a>What are GitHub-hosted runners?</h2><p>By default, GitHub Actions executes jobs on machines provided by GitHub, so-called GitHub-hosted runners. Each GitHub-hosted runner comes with the runner application and other preinstalled tools and is available with Ubuntu Linux, Windows, or macOS operating systems.</p><p>A GitHub-hosted runner is connected to the Internet but cannot access any private networks, like a VPC on AWS. Therefore, reaching resources like an RDS database, an internal ALB, an ElasticSearch&#x2F;OpenSearch domain, or an ElastiCache instance from within a running job is impossible.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/01/github-hosted-runner@730w.webp 730w, /images/2024/01/github-hosted-runner@730w2x.webp 1460w, /images/2024/01/github-hosted-runner@610w.webp 610w, /images/2024/01/github-hosted-runner@610w2x.webp 1220w, /images/2024/01/github-hosted-runner@450w.webp 450w, /images/2024/01/github-hosted-runner@450w2x.webp 900w, /images/2024/01/github-hosted-runner@330w.webp 330w, /images/2024/01/github-hosted-runner@330w2x.webp 660w, /images/2024/01/github-hosted-runner@545w.webp 545w, /images/2024/01/github-hosted-runner@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/01/github-hosted-runner@730w.png 730w, /images/2024/01/github-hosted-runner@730w2x.png 1460w, /images/2024/01/github-hosted-runner@610w.png 610w, /images/2024/01/github-hosted-runner@610w2x.png 1220w, /images/2024/01/github-hosted-runner@450w.png 450w, /images/2024/01/github-hosted-runner@450w2x.png 900w, /images/2024/01/github-hosted-runner@330w.png 330w, /images/2024/01/github-hosted-runner@330w2x.png 660w, /images/2024/01/github-hosted-runner@545w.png 545w, /images/2024/01/github-hosted-runner@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/01/github-hosted-runner.png" alt="GitHub-hosted runner connected to the Internet but not private networks" title="GitHub-hosted runner connected to the Internet but not private networks"></picture></p><h2 id="How-to-access-private-networks-from-GitHub-Actions"><a href="#How-to-access-private-networks-from-GitHub-Actions" class="headerlink" title="How to access private networks from GitHub Actions?"></a>How to access private networks from GitHub Actions?</h2><p>There are two options to access private networks from GitHub Actions.</p><p>First, establish a tunnel between the GitHub-hosted runner and the private network, for example, by using VPN or SSH.</p><p>Second, run GitHub Actions inside your VPC.</p><p>How is that possible? GitHub Actions not only provides GitHub-hosted runners but also supports self-hosted runners. As illustrated in the following figure, an EC2 instance acting as a self-hosted runner launched in a VPC allows all jobs running on the machine to connect to resources in private subnets such as RDS, ElasticSearch, OpenSearch, ElastiCache, and more.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/01/self-hosted-runner@730w.webp 730w, /images/2024/01/self-hosted-runner@730w2x.webp 1460w, /images/2024/01/self-hosted-runner@610w.webp 610w, /images/2024/01/self-hosted-runner@610w2x.webp 1220w, /images/2024/01/self-hosted-runner@450w.webp 450w, /images/2024/01/self-hosted-runner@450w2x.webp 900w, /images/2024/01/self-hosted-runner@330w.webp 330w, /images/2024/01/self-hosted-runner@330w2x.webp 660w, /images/2024/01/self-hosted-runner@545w.webp 545w, /images/2024/01/self-hosted-runner@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/01/self-hosted-runner@730w.png 730w, /images/2024/01/self-hosted-runner@730w2x.png 1460w, /images/2024/01/self-hosted-runner@610w.png 610w, /images/2024/01/self-hosted-runner@610w2x.png 1220w, /images/2024/01/self-hosted-runner@450w.png 450w, /images/2024/01/self-hosted-runner@450w2x.png 900w, /images/2024/01/self-hosted-runner@330w.png 330w, /images/2024/01/self-hosted-runner@330w2x.png 660w, /images/2024/01/self-hosted-runner@545w.png 545w, /images/2024/01/self-hosted-runner@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/01/self-hosted-runner.png" alt="GitHub-hosted runner connected to the Internet but not private networks" title="GitHub-hosted runner connected to the Internet but not private networks"></picture></p><h2 id="How-to-deploy-a-self-hosted-GitHub-Actions-runner-on-AWS"><a href="#How-to-deploy-a-self-hosted-GitHub-Actions-runner-on-AWS" class="headerlink" title="How to deploy a self-hosted GitHub Actions runner on AWS?"></a>How to deploy a self-hosted GitHub Actions runner on AWS?</h2><p>But how do you deploy a self-hosted runner on AWS? I’ve previously written about <a href="https://cloudonaut.io/self-hosted-github-runners-on-aws/">self-hosted GitHub runners on AWS</a>. The tricky part is to come up with a scalable and cost-efficient solution.</p><p>That’s why I built <a href="https://hyperenv.com/github-actions/" target="_blank" rel="noopener">HyperEnv for GitHub Actions Runner</a>. The following figure illustrates the solution.</p><ol><li>GitHub sends a webhook event when starting a job.</li><li>The API Gateway receives the event.</li><li>A Lambda function validates the event and sends a message to SQS.</li><li>Another Lambda function reads the message from SQS and launches an EC2 instance in an VPC of your choice.</li><li>The EC2 instance starts and registers the GitHub runner.</li><li>The GitHub runner executes the job.</li><li>The EC2 instance terminates itself.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2024/01/hyperenv@730w.webp 730w, /images/2024/01/hyperenv@730w2x.webp 1460w, /images/2024/01/hyperenv@610w.webp 610w, /images/2024/01/hyperenv@610w2x.webp 1220w, /images/2024/01/hyperenv@450w.webp 450w, /images/2024/01/hyperenv@450w2x.webp 900w, /images/2024/01/hyperenv@330w.webp 330w, /images/2024/01/hyperenv@330w2x.webp 660w, /images/2024/01/hyperenv@545w.webp 545w, /images/2024/01/hyperenv@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2024/01/hyperenv@730w.png 730w, /images/2024/01/hyperenv@730w2x.png 1460w, /images/2024/01/hyperenv@610w.png 610w, /images/2024/01/hyperenv@610w2x.png 1220w, /images/2024/01/hyperenv@450w.png 450w, /images/2024/01/hyperenv@450w2x.png 900w, /images/2024/01/hyperenv@330w.png 330w, /images/2024/01/hyperenv@330w2x.png 660w, /images/2024/01/hyperenv@545w.png 545w, /images/2024/01/hyperenv@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2024/01/hyperenv.png" alt="HyperEnv for GitHub Actions Runner" title="HyperEnv for GitHub Actions Runner"></picture></p><blockquote><p><a href="https://hyperenv.com/github-actions/setup-guide.html" target="_blank" rel="noopener">Deploy HyperEnv for GitHub Actions</a> to your AWS account to enable GitHub Actions jobs to connect with RDS, ElasticSearch, OpenSearch, ElastiCache running in your VPC.</p></blockquote><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=w65m4cIAUkc">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/w65m4cIAUkc" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Protect Amazon Connect from viruses and malware by scanning attachments</title>
      <link>https://cloudonaut.io/protect-amazon-connect-from-viruses-and-malware-by-scanning-attachments/</link>
      <description>
        <![CDATA[<p>Four years ago, we <a href="/review-amazon-connect-programmable-telephone-system/">stumbled into Amazon Connect</a>. In essence, <a href=]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/connect/">connect</category>
      <guid isPermaLink="true">https://cloudonaut.io/protect-amazon-connect-from-viruses-and-malware-by-scanning-attachments/</guid>
      <pubDate>Tue, 21 Nov 2023 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Four years ago, we <a href="/review-amazon-connect-programmable-telephone-system/">stumbled into Amazon Connect</a>. In essence, <a href="https://aws.amazon.com/connect/" target="_blank" rel="noopener">Amazon Connect</a> allows your users to reach your organization represented by agents via phone or chat. While chatting, Amazon Connect allows users and agents to upload attachments. For many years, there was no good solution to ensure those files were malware-free. Given that anonymous users can start Amazon Connect chats, that’s quite scary. Lucky us, Amazon Connect just released a feature that <a href="https://aws.amazon.com/about-aws/whats-new/2023/11/amazon-connect-scanning-attachments-malware/" target="_blank" rel="noopener">enables scanning of attachments for malware</a>. You might think: “Great,  Amazon Connect scans all files from now on”. But no, Amazon Connect <strong>enables you to scan the attachments yourself</strong>. In other words, Amazon Connect invokes a Lambda function that you run to check if a file is clean or infected. If your Lambda function marks the file as clean, it will become visible to the other party. In the following blog post, I share what I learned while integrating our product <a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV - Antivirus protection for Amazon S3</a> with Amazon Connect to scan files for malware.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/11/amazon-connect-malware@730w.webp 730w, /images/2023/11/amazon-connect-malware@730w2x.webp 1460w, /images/2023/11/amazon-connect-malware@610w.webp 610w, /images/2023/11/amazon-connect-malware@610w2x.webp 1220w, /images/2023/11/amazon-connect-malware@450w.webp 450w, /images/2023/11/amazon-connect-malware@450w2x.webp 900w, /images/2023/11/amazon-connect-malware@330w.webp 330w, /images/2023/11/amazon-connect-malware@330w2x.webp 660w, /images/2023/11/amazon-connect-malware@545w.webp 545w, /images/2023/11/amazon-connect-malware@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/11/amazon-connect-malware@730w.png 730w, /images/2023/11/amazon-connect-malware@730w2x.png 1460w, /images/2023/11/amazon-connect-malware@610w.png 610w, /images/2023/11/amazon-connect-malware@610w2x.png 1220w, /images/2023/11/amazon-connect-malware@450w.png 450w, /images/2023/11/amazon-connect-malware@450w2x.png 900w, /images/2023/11/amazon-connect-malware@330w.png 330w, /images/2023/11/amazon-connect-malware@330w2x.png 660w, /images/2023/11/amazon-connect-malware@545w.png 545w, /images/2023/11/amazon-connect-malware@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/11/amazon-connect-malware.png" alt="Amazon Connect attachment scanning" title="Amazon Connect attachment scanning"></picture></p><p>The following figure shows an interaction between a user and an agent. The user (on the left) uploads a clean file first, followed by a virus. The agent (on the right) only receives the clean file. The infected file gets blocked.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/11/amazon-connect-user-agent@730w.webp 730w, /images/2023/11/amazon-connect-user-agent@730w2x.webp 1460w, /images/2023/11/amazon-connect-user-agent@610w.webp 610w, /images/2023/11/amazon-connect-user-agent@610w2x.webp 1220w, /images/2023/11/amazon-connect-user-agent@450w.webp 450w, /images/2023/11/amazon-connect-user-agent@450w2x.webp 900w, /images/2023/11/amazon-connect-user-agent@330w.webp 330w, /images/2023/11/amazon-connect-user-agent@330w2x.webp 660w, /images/2023/11/amazon-connect-user-agent@545w.webp 545w, /images/2023/11/amazon-connect-user-agent@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/11/amazon-connect-user-agent@730w.png 730w, /images/2023/11/amazon-connect-user-agent@730w2x.png 1460w, /images/2023/11/amazon-connect-user-agent@610w.png 610w, /images/2023/11/amazon-connect-user-agent@610w2x.png 1220w, /images/2023/11/amazon-connect-user-agent@450w.png 450w, /images/2023/11/amazon-connect-user-agent@450w2x.png 900w, /images/2023/11/amazon-connect-user-agent@330w.png 330w, /images/2023/11/amazon-connect-user-agent@330w2x.png 660w, /images/2023/11/amazon-connect-user-agent@545w.png 545w, /images/2023/11/amazon-connect-user-agent@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/11/amazon-connect-user-agent.png" alt="Amazon Connect user uploading attachments in a chat" title="Amazon Connect user uploading attachments in a chat"></picture></p><p>In the following, I share my code with you.</p><blockquote><p>bucketAV customers can skip the rest of the blog post and use our <a href="https://bucketav.com/help/integrations/amazon-connect.html" target="_blank" rel="noopener">Amazon Connect integration to scan attachments as documented here</a>.</p></blockquote><h2 id="The-contract"><a href="#The-contract" class="headerlink" title="The contract"></a>The contract</h2><p>Amazon Connect calls the Lambda function with the following payload in a synchronous invocation:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;InstanceId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ca6f678d-42f6-4b25-b6ad-2e74db01117a&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;File&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;FileId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;76d46e1d-6720-4705-98be-fbc2ba034638&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;FileCreationTime&quot;</span><span class="punctuation">:</span> <span class="number">1700126816287</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;FileName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cloudonaut.png&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;FileSizeInBytes&quot;</span><span class="punctuation">:</span> <span class="number">2877697</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;FileLocation&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;S3Location&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;Key&quot;</span><span class="punctuation">:</span> <span class="string">&quot;connect/bucketav/Attachments/chat/2023/11/16/ecd01b13-6c44-4432-a8da-0c7a9149b79a_76d46e1d-6720-4705-98be-fbc2ba034638_20231116T09:26_UTC.png&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;Bucket&quot;</span><span class="punctuation">:</span> <span class="string">&quot;amazon-connect-0af5e3328eab&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;Arn&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:s3:::amazon-connect-0af5e3328eab/connect/bucketav/Attachments/chat/2023/11/16/ecd01b13-6c44-4432-a8da-0c7a9149b79a_76d46e1d-6720-4705-98be-fbc2ba034638_20231116T09:26_UTC.png&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>The function is expected to return the following for clean files within 60 seconds:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;APPROVED&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>For infected files:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;REJECTED&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Amazon Connect retries up to 3 times when a synchronous invocation returns an error. The maximum file size for an attachment to a case or a chat is 20MB.</p><h2 id="The-implementation"><a href="#The-implementation" class="headerlink" title="The implementation"></a>The implementation</h2><p>bucketAV scans files uploaded to Amazon S3 asynchronously. A scan job is submitted to an SQS queue, and bucketAV publishes the result to an SNS topic. Unfortunately, Amazon Connect follows a synchronous model. Therefore, I fall back to good old busy waiting to integrate bucketAV’s asynchronous model with Amazon Connect’s synchronous approach.</p><p>The first Lambda function is connected to the SNS topic where bucketAV publishes scan results. When a scan result is published, the Lambda function stores it in a DynamoDB table. The environment variable <code>TABLE_NAME</code> is used to pass the name of the DynamoDB table that stores scan results.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// connect-subscription.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">DynamoDB</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-sdk/client-dynamodb&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">TRACE_TTL_IN_SECONDS</span> = <span class="number">120</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> dynamodb = <span class="keyword">new</span> <span class="title class_">DynamoDB</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-08-10&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">handler</span>(<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Invoke: <span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(event)&#125;</span>`</span>);</span><br><span class="line">  <span class="keyword">const</span> now = <span class="title class_">Date</span>.<span class="title function_">now</span>();</span><br><span class="line">  <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>(event.<span class="property">Records</span>.<span class="title function_">map</span>(<span class="function"><span class="params">record</span> =&gt;</span> dynamodb.<span class="title function_">putItem</span>(&#123;</span><br><span class="line">    <span class="title class_">TableName</span>: process.<span class="property">env</span>.<span class="property">TABLE_NAME</span>,</span><br><span class="line">    <span class="title class_">Item</span>: &#123;</span><br><span class="line">      <span class="attr">id</span>: &#123;<span class="attr">S</span>: record.<span class="property">Sns</span>.<span class="property">MessageAttributes</span>.<span class="property">trace_id</span>.<span class="property">Value</span>&#125;,</span><br><span class="line">      <span class="attr">status</span>: &#123;<span class="attr">S</span>: record.<span class="property">Sns</span>.<span class="property">MessageAttributes</span>.<span class="property">status</span>.<span class="property">Value</span>&#125;,</span><br><span class="line">      <span class="attr">ttl</span>: &#123;<span class="attr">N</span>: (now/<span class="number">1000</span>+<span class="variable constant_">TRACE_TTL_IN_SECONDS</span>).<span class="title function_">toFixed</span>(<span class="number">0</span>)&#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)));</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The second Lambda function is the one invoked by Amazon Connect. When an attachment is uploaded, the Lambda function:</p><ul><li>Submits a scan job to the SQS queue.</li><li>Queries the DynamoDB table every second until a scan result is available (remember, the first Lambda function stores the scan result).</li><li>Returns APPROVED for clean files or REJECTED for infected files as Amazon Connect expects.<br>The environment variable <code>TABLE_NAME</code> is the same as for the first Lambda function. <code>SCAN_QUEUE_URL</code> references the URL of the SQS queue that enqueues scan jobs. I also use <code>STACK_NAME</code> to reference the CloudFormation stack name that deploys the Lambda function.</li></ul><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// connect.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="variable constant_">SQS</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-sdk/client-sqs&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">DynamoDB</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-sdk/client-dynamodb&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">POLL_TIMEOUT_IN_MILLISECONDS</span> = <span class="number">1000</span>*<span class="number">55</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> sqs = <span class="keyword">new</span> <span class="title function_">SQS</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-11-05&#x27;</span>&#125;);</span><br><span class="line"><span class="keyword">const</span> dynamodb = <span class="keyword">new</span> <span class="title class_">DynamoDB</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-08-10&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">wait</span>(<span class="params">ms</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve</span>) =&gt;</span> <span class="built_in">setTimeout</span>(resolve, ms));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">handler</span>(<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Invoke: <span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(event)&#125;</span>`</span>);</span><br><span class="line">  <span class="keyword">const</span> start = <span class="title class_">Date</span>.<span class="title function_">now</span>();</span><br><span class="line">  <span class="keyword">const</span> traceId = <span class="string">`bucketav:connect:<span class="subst">$&#123;process.env.STACK_NAME&#125;</span>:<span class="subst">$&#123;event.File.FileId&#125;</span>`</span>;</span><br><span class="line">  <span class="keyword">const</span> object = &#123;</span><br><span class="line">    <span class="attr">bucket</span>: event.<span class="property">File</span>.<span class="property">FileLocation</span>.<span class="property">S3Location</span>.<span class="property">Bucket</span>,</span><br><span class="line">    <span class="attr">key</span>: event.<span class="property">File</span>.<span class="property">FileLocation</span>.<span class="property">S3Location</span>.<span class="property">Key</span>,</span><br><span class="line">    <span class="attr">trace_id</span>: traceId</span><br><span class="line">  &#125;;  </span><br><span class="line">  <span class="keyword">await</span> sqs.<span class="title function_">sendMessage</span>(&#123;</span><br><span class="line">    <span class="title class_">MessageBody</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(&#123;</span><br><span class="line">      <span class="attr">objects</span>: [object]</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="title class_">QueueUrl</span>: process.<span class="property">env</span>.<span class="property">SCAN_QUEUE_URL</span></span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="keyword">while</span>((<span class="title class_">Date</span>.<span class="title function_">now</span>()-start) &lt; <span class="variable constant_">POLL_TIMEOUT_IN_MILLISECONDS</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123;<span class="title class_">Item</span>: item&#125; = <span class="keyword">await</span> dynamodb.<span class="title function_">getItem</span>(&#123;</span><br><span class="line">      <span class="title class_">TableName</span>: process.<span class="property">env</span>.<span class="property">TABLE_NAME</span>,</span><br><span class="line">      <span class="title class_">Key</span>: &#123;</span><br><span class="line">        <span class="attr">id</span>: &#123;<span class="attr">S</span>: traceId&#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">if</span> (item === <span class="literal">undefined</span>) &#123;</span><br><span class="line">      <span class="keyword">await</span> <span class="title function_">wait</span>(<span class="number">1000</span>);</span><br><span class="line">      <span class="keyword">continue</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (item.<span class="property">status</span>.<span class="property">S</span> === <span class="string">&#x27;clean&#x27;</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="title class_">Status</span>: <span class="string">&#x27;APPROVED&#x27;</span></span><br><span class="line">      &#125;;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (item.<span class="property">status</span>.<span class="property">S</span> === <span class="string">&#x27;infected&#x27;</span> || item.<span class="property">status</span>.<span class="property">S</span> === <span class="string">&#x27;no&#x27;</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="title class_">Status</span>: <span class="string">&#x27;REJECTED&#x27;</span></span><br><span class="line">      &#125;;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`unexpected status: <span class="subst">$&#123;item.status.S&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;timeout&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The following AWS infrastructure is required:</p><ul><li>DynamoDB Table.</li><li>IAM roles for both Lambda functions.</li><li>Lambda functions.</li><li>SNS subscription for the first Lambda function.</li><li>Permission for the first Lambda function to allow SNS to invoke the function.<br>The following code is used to deploy the application with the AWS CDK:</li></ul><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">CfnParameter</span>, <span class="title class_">CfnCondition</span>, <span class="title class_">CfnOutput</span>, <span class="title class_">Fn</span>, <span class="title class_">Aws</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;aws-cdk-lib&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> dynamodb = <span class="built_in">require</span>(<span class="string">&#x27;aws-cdk-lib/aws-dynamodb&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> cloudwatch = <span class="built_in">require</span>(<span class="string">&#x27;aws-cdk-lib/aws-cloudwatch&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> sns = <span class="built_in">require</span>(<span class="string">&#x27;aws-cdk-lib/aws-sns&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> lambda = <span class="built_in">require</span>(<span class="string">&#x27;aws-cdk-lib/aws-lambda&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> iam = <span class="built_in">require</span>(<span class="string">&#x27;aws-cdk-lib/aws-iam&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> logs = <span class="built_in">require</span>(<span class="string">&#x27;aws-cdk-lib/aws-logs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> esbuild = <span class="built_in">require</span>(<span class="string">&#x27;esbuild&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Helper function to format Lambda function source code in a way CloudFormation can deploy (inline code)</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">zipFile</span>(<span class="params">lambdaFile, target</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> esbuild.<span class="title function_">buildSync</span>(&#123;</span><br><span class="line">    <span class="attr">entryPoints</span>: [lambdaFile],</span><br><span class="line">    <span class="attr">external</span>: [<span class="string">&#x27;@aws-sdk/*&#x27;</span>],</span><br><span class="line">    <span class="attr">target</span>: [target],</span><br><span class="line">    <span class="attr">platform</span>: <span class="string">&#x27;node&#x27;</span>,</span><br><span class="line">    <span class="attr">bundle</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">write</span>: <span class="literal">false</span></span><br><span class="line">  &#125;).<span class="property">outputFiles</span>[<span class="number">0</span>].<span class="property">text</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> bucketAVStackName = <span class="keyword">new</span> <span class="title class_">CfnParameter</span>(<span class="variable language_">this</span>, <span class="string">&#x27;BucketAVStackName&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">description</span>: <span class="string">&#x27;CloudFormation stack name of bucketAV (if you followed our docs, the name is bucketav)&#x27;</span>,</span><br><span class="line">  <span class="attr">type</span>: <span class="string">&#x27;String&#x27;</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> table = <span class="keyword">new</span> dynamodb.<span class="title class_">CfnTable</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Table&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">attributeDefinitions</span>: [&#123;</span><br><span class="line">    <span class="attr">attributeName</span>: <span class="string">&#x27;id&#x27;</span>,</span><br><span class="line">    <span class="attr">attributeType</span>: <span class="string">&#x27;S&#x27;</span></span><br><span class="line">  &#125;],</span><br><span class="line">  <span class="attr">billingMode</span>: <span class="string">&#x27;PAY_PER_REQUEST&#x27;</span>,</span><br><span class="line">  <span class="attr">keySchema</span>: [&#123;</span><br><span class="line">    <span class="attr">attributeName</span>: <span class="string">&#x27;id&#x27;</span>,</span><br><span class="line">    <span class="attr">keyType</span>: <span class="string">&#x27;HASH&#x27;</span></span><br><span class="line">  &#125;],</span><br><span class="line">  <span class="attr">sseSpecification</span>: &#123;</span><br><span class="line">    <span class="attr">sseEnabled</span>: <span class="literal">true</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">timeToLiveSpecification</span>: &#123;</span><br><span class="line">    <span class="attr">attributeName</span>: <span class="string">&#x27;ttl&#x27;</span>,</span><br><span class="line">    <span class="attr">enabled</span>: <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> subscriptionLambdaRole = <span class="keyword">new</span> iam.<span class="title class_">CfnRole</span>(<span class="variable language_">this</span>, <span class="string">&#x27;SubscriptionLambdaRole&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">assumeRolePolicyDocument</span>: &#123;</span><br><span class="line">    <span class="title class_">Version</span>: <span class="string">&#x27;2012-10-17&#x27;</span>,</span><br><span class="line">    <span class="title class_">Statement</span>: [&#123;</span><br><span class="line">      <span class="title class_">Effect</span>: <span class="string">&#x27;Allow&#x27;</span>,</span><br><span class="line">      <span class="title class_">Principal</span>: &#123;</span><br><span class="line">        <span class="title class_">Service</span>: <span class="string">&#x27;lambda.amazonaws.com&#x27;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="title class_">Action</span>: <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    &#125;]</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">policies</span>: [&#123;</span><br><span class="line">    <span class="attr">policyName</span>: <span class="string">&#x27;lambda&#x27;</span>,</span><br><span class="line">    <span class="attr">policyDocument</span>: &#123;</span><br><span class="line">      <span class="title class_">Statement</span>: [&#123;</span><br><span class="line">        <span class="title class_">Effect</span>: <span class="string">&#x27;Allow&#x27;</span>,</span><br><span class="line">        <span class="title class_">Action</span>: <span class="string">&#x27;dynamodb:PutItem&#x27;</span>,</span><br><span class="line">        <span class="title class_">Resource</span>: table.<span class="property">attrArn</span></span><br><span class="line">      &#125;]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;]</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> subscriptionLambdaFunction = <span class="keyword">new</span> lambda.<span class="title class_">CfnFunction</span>(<span class="variable language_">this</span>, <span class="string">&#x27;SubscriptionLambdaFunction&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">code</span>: &#123;</span><br><span class="line">    <span class="attr">zipFile</span>: <span class="title function_">zipFile</span>(<span class="string">&#x27;connect-subscription.js&#x27;</span>, <span class="string">&#x27;node18&#x27;</span>)</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">environment</span>: &#123;</span><br><span class="line">    <span class="attr">variables</span>: &#123;</span><br><span class="line">      <span class="attr">TABLE_NAME</span>: table.<span class="property">ref</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">handler</span>: <span class="string">&#x27;index.handler&#x27;</span>,</span><br><span class="line">  <span class="attr">memorySize</span>: <span class="number">1769</span>,</span><br><span class="line">  <span class="attr">role</span>: subscriptionLambdaRole.<span class="property">attrArn</span>,</span><br><span class="line">  <span class="attr">runtime</span>: <span class="string">&#x27;nodejs18.x&#x27;</span>,</span><br><span class="line">  <span class="attr">timeout</span>: <span class="number">60</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> subscriptionLambdaPermission = <span class="keyword">new</span> lambda.<span class="title class_">CfnPermission</span>(<span class="variable language_">this</span>, <span class="string">&#x27;SubscriptionLambdaPermission&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">action</span>: <span class="string">&#x27;lambda:InvokeFunction&#x27;</span>,</span><br><span class="line">  <span class="attr">functionName</span>: subscriptionLambdaFunction.<span class="property">ref</span>,</span><br><span class="line">  <span class="attr">principal</span>: <span class="string">&#x27;sns.amazonaws.com&#x27;</span>,</span><br><span class="line">  <span class="attr">sourceArn</span>: <span class="title class_">Fn</span>.<span class="title function_">importValue</span>(<span class="string">`<span class="subst">$&#123;bucketAVStackName.valueAsString&#125;</span>-FindingsTopicArn`</span>)</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> subscriptionLambdaLogGroup = <span class="keyword">new</span> logs.<span class="title class_">CfnLogGroup</span>(<span class="variable language_">this</span>, <span class="string">&#x27;SubscriptionLambdaLogGroup&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">logGroupName</span>: <span class="string">`/aws/lambda/<span class="subst">$&#123;subscriptionLambdaFunction.ref&#125;</span>`</span>,</span><br><span class="line">  <span class="attr">retentionInDays</span>: <span class="number">14</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> subscriptionLambdaPolicy = <span class="keyword">new</span> iam.<span class="title class_">CfnPolicy</span>(<span class="variable language_">this</span>, <span class="string">&#x27;SubscriptionLambdaPolicy&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">roles</span>: [</span><br><span class="line">    subscriptionLambdaRole.<span class="property">ref</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">policyName</span>: <span class="string">&#x27;logs&#x27;</span>,</span><br><span class="line">  <span class="attr">policyDocument</span>: &#123;</span><br><span class="line">    <span class="title class_">Statement</span>: [&#123;</span><br><span class="line">      <span class="title class_">Effect</span>: <span class="string">&#x27;Allow&#x27;</span>,</span><br><span class="line">      <span class="title class_">Action</span>: [</span><br><span class="line">        <span class="string">&#x27;logs:CreateLogStream&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">      ],</span><br><span class="line">      <span class="title class_">Resource</span>: subscriptionLambdaLogGroup.<span class="property">attrArn</span></span><br><span class="line">    &#125;]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> subscription = <span class="keyword">new</span> sns.<span class="title class_">CfnSubscription</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Subscription&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">endpoint</span>: subscriptionLambdaFunction.<span class="property">attrArn</span>,</span><br><span class="line">  <span class="attr">filterPolicy</span>: &#123;</span><br><span class="line">    <span class="attr">trace_id</span>: [&#123;<span class="attr">prefix</span>: <span class="string">`bucketav:connect:<span class="subst">$&#123;Aws.STACK_NAME&#125;</span>:`</span>&#125;]</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">protocol</span>: <span class="string">&#x27;lambda&#x27;</span>,</span><br><span class="line">  <span class="attr">topicArn</span>: <span class="title class_">Fn</span>.<span class="title function_">importValue</span>(<span class="string">`<span class="subst">$&#123;bucketAVStackName.valueAsString&#125;</span>-FindingsTopicArn`</span>)</span><br><span class="line">&#125;);</span><br><span class="line">subscription.<span class="title function_">addDependency</span>(subscriptionLambdaPermission);</span><br><span class="line">subscription.<span class="title function_">addDependency</span>(subscriptionLambdaPolicy);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> connectLambdaRole = <span class="keyword">new</span> iam.<span class="title class_">CfnRole</span>(<span class="variable language_">this</span>, <span class="string">&#x27;ConnectLambdaRole&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">assumeRolePolicyDocument</span>: &#123;</span><br><span class="line">    <span class="title class_">Version</span>: <span class="string">&#x27;2012-10-17&#x27;</span>,</span><br><span class="line">    <span class="title class_">Statement</span>: [&#123;</span><br><span class="line">      <span class="title class_">Effect</span>: <span class="string">&#x27;Allow&#x27;</span>,</span><br><span class="line">      <span class="title class_">Principal</span>: &#123;</span><br><span class="line">        <span class="title class_">Service</span>: <span class="string">&#x27;lambda.amazonaws.com&#x27;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="title class_">Action</span>: <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    &#125;]</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">policies</span>: [&#123;</span><br><span class="line">    <span class="attr">policyName</span>: <span class="string">&#x27;lambda&#x27;</span>,</span><br><span class="line">    <span class="attr">policyDocument</span>: &#123;</span><br><span class="line">      <span class="title class_">Statement</span>: [&#123;</span><br><span class="line">        <span class="title class_">Effect</span>: <span class="string">&#x27;Allow&#x27;</span>,</span><br><span class="line">        <span class="title class_">Action</span>: <span class="string">&#x27;sqs:SendMessage&#x27;</span>,</span><br><span class="line">        <span class="title class_">Resource</span>: <span class="title class_">Fn</span>.<span class="title function_">importValue</span>(<span class="string">`<span class="subst">$&#123;bucketAVStackName.valueAsString&#125;</span>-ScanQueueArn`</span>)</span><br><span class="line">      &#125;, &#123;</span><br><span class="line">        <span class="title class_">Effect</span>: <span class="string">&#x27;Allow&#x27;</span>,</span><br><span class="line">        <span class="title class_">Action</span>: <span class="string">&#x27;dynamodb:GetItem&#x27;</span>,</span><br><span class="line">        <span class="title class_">Resource</span>: table.<span class="property">attrArn</span></span><br><span class="line">      &#125;]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;]</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> connectLambdaFunction = <span class="keyword">new</span> lambda.<span class="title class_">CfnFunction</span>(<span class="variable language_">this</span>, <span class="string">&#x27;ConnectLambdaFunction&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">code</span>: &#123;</span><br><span class="line">    <span class="attr">zipFile</span>: <span class="title function_">zipFile</span>(<span class="string">&#x27;connect.js&#x27;</span>, <span class="string">&#x27;node18&#x27;</span>)</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">environment</span>: &#123;</span><br><span class="line">    <span class="attr">variables</span>: &#123;</span><br><span class="line">      <span class="attr">TABLE_NAME</span>: table.<span class="property">ref</span>,</span><br><span class="line">      <span class="attr">STACK_NAME</span>: <span class="title class_">Aws</span>.<span class="property">STACK_NAME</span>,</span><br><span class="line">      <span class="attr">SCAN_QUEUE_URL</span>: <span class="title class_">Fn</span>.<span class="title function_">importValue</span>(<span class="string">`<span class="subst">$&#123;bucketAVStackName.valueAsString&#125;</span>-ScanQueueUrl`</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">handler</span>: <span class="string">&#x27;index.handler&#x27;</span>,</span><br><span class="line">  <span class="attr">memorySize</span>: <span class="number">1769</span>,</span><br><span class="line">  <span class="attr">role</span>: connectLambdaRole.<span class="property">attrArn</span>,</span><br><span class="line">  <span class="attr">runtime</span>: <span class="string">&#x27;nodejs18.x&#x27;</span>,</span><br><span class="line">  <span class="attr">timeout</span>: <span class="number">60</span> <span class="comment">// Maximum timeout for an attachment scanner: 60 seconds</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> connectLambdaLogGroup = <span class="keyword">new</span> logs.<span class="title class_">CfnLogGroup</span>(<span class="variable language_">this</span>, <span class="string">&#x27;ConnectLambdaLogGroup&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">logGroupName</span>: <span class="string">`/aws/lambda/<span class="subst">$&#123;connectLambdaFunction.ref&#125;</span>`</span>,</span><br><span class="line">  <span class="attr">retentionInDays</span>: <span class="number">14</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> iam.<span class="title class_">CfnPolicy</span>(<span class="variable language_">this</span>, <span class="string">&#x27;ConnectLambdaPolicy&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">roles</span>: [</span><br><span class="line">    connectLambdaRole.<span class="property">ref</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">policyName</span>: <span class="string">&#x27;logs&#x27;</span>,</span><br><span class="line">  <span class="attr">policyDocument</span>: &#123;</span><br><span class="line">    <span class="title class_">Statement</span>: [&#123;</span><br><span class="line">      <span class="title class_">Effect</span>: <span class="string">&#x27;Allow&#x27;</span>,</span><br><span class="line">      <span class="title class_">Action</span>: [</span><br><span class="line">        <span class="string">&#x27;logs:CreateLogStream&#x27;</span>,</span><br><span class="line">        <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">      ],</span><br><span class="line">      <span class="title class_">Resource</span>: connectLambdaLogGroup.<span class="property">attrArn</span></span><br><span class="line">    &#125;]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Amazon Connects adds permissions automatically when enabling attachment scanning</span></span><br><span class="line"><span class="comment">// new lambda.CfnPermission(this, &#x27;ConnectLambdaPermission&#x27;, &#123;</span></span><br><span class="line"><span class="comment">//   action: &#x27;lambda:InvokeFunction&#x27;,</span></span><br><span class="line"><span class="comment">//   functionName: connectLambdaFunction.ref,</span></span><br><span class="line"><span class="comment">//   principal: &#x27;connect.amazonaws.com&#x27;,</span></span><br><span class="line"><span class="comment">//   sourceArn: `arn:aws:connect:$&#123;Aws.REGION&#125;:$&#123;Aws.ACCOUNT_ID&#125;:instance/INSTANCE_ID`</span></span><br><span class="line"><span class="comment">// &#125;);</span></span><br></pre></td></tr></table></figure><p>That’s it. I hope my blog post helps you to integrate your antivirus solution with Amazon Connect. If you don’t have an existing antivirus solution for AWS, check out bucketAV and the new <a href="https://bucketav.com/help/integrations/amazon-connect.html" target="_blank" rel="noopener">Amazon Connect attachment scanning</a> integration.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Worldwide availability of EC2 instance types</title>
      <link>https://cloudonaut.io/worldwide-availability-of-ec2-instance-types/</link>
      <description>
        <![CDATA[<p>The promise sounds tempting; with AWS, you can roll out your infrastructure in 33 regions worldwide. Indeed, it is an eye-opening moment]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <guid isPermaLink="true">https://cloudonaut.io/worldwide-availability-of-ec2-instance-types/</guid>
      <pubDate>Wed, 15 Nov 2023 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The promise sounds tempting; with AWS, you can roll out your infrastructure in 33 regions worldwide. Indeed, it is an eye-opening moment when rolling out the same infrastructure into multiple regions to serve users in different parts of the world. However, a few stumbling blocks exist when rolling out an application to every available region. I’ve recently stumbled upon one of them when working on <a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV</a>. Not all EC2 instance families and types are available in all regions.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/11/ec2-instance-types-worldwide-title@730w.webp 730w, /images/2023/11/ec2-instance-types-worldwide-title@730w2x.webp 1460w, /images/2023/11/ec2-instance-types-worldwide-title@610w.webp 610w, /images/2023/11/ec2-instance-types-worldwide-title@610w2x.webp 1220w, /images/2023/11/ec2-instance-types-worldwide-title@450w.webp 450w, /images/2023/11/ec2-instance-types-worldwide-title@450w2x.webp 900w, /images/2023/11/ec2-instance-types-worldwide-title@330w.webp 330w, /images/2023/11/ec2-instance-types-worldwide-title@330w2x.webp 660w, /images/2023/11/ec2-instance-types-worldwide-title@545w.webp 545w, /images/2023/11/ec2-instance-types-worldwide-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/11/ec2-instance-types-worldwide-title@730w.jpg 730w, /images/2023/11/ec2-instance-types-worldwide-title@730w2x.jpg 1460w, /images/2023/11/ec2-instance-types-worldwide-title@610w.jpg 610w, /images/2023/11/ec2-instance-types-worldwide-title@610w2x.jpg 1220w, /images/2023/11/ec2-instance-types-worldwide-title@450w.jpg 450w, /images/2023/11/ec2-instance-types-worldwide-title@450w2x.jpg 900w, /images/2023/11/ec2-instance-types-worldwide-title@330w.jpg 330w, /images/2023/11/ec2-instance-types-worldwide-title@330w2x.jpg 660w, /images/2023/11/ec2-instance-types-worldwide-title@545w.jpg 545w, /images/2023/11/ec2-instance-types-worldwide-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/11/ec2-instance-types-worldwide-title.jpg" alt="Worldwide availability of EC2 instance types" title="Worldwide availability of EC2 instance types"></picture></p><p>Here are some interesting facts.</p><h2 id="5-Things-you-should-know-about-the-availability-of-EC2-instance-types"><a href="#5-Things-you-should-know-about-the-availability-of-EC2-instance-types" class="headerlink" title="5 Things you should know about the availability of EC2 instance types"></a>5 Things you should know about the availability of EC2 instance types</h2><ul><li>Only 9 instance families are available in all regions: <code>c6g</code>, <code>i3en</code>, <code>i4i</code>, <code>m6g</code>, <code>m6gd</code>, <code>r6g</code>, <code>r7g</code>, <code>t3</code>, <code>t4g</code>.</li><li>New instance types are rolling out slowly. For example, <code>i7i</code> was released  in April 2025 and is available in 9% of the regions only.</li><li>Even older instance types like <code>m7i</code>, which was released in August 2023, are not availble in all regions yet.</li><li>New regions support only a small subset of all available instance types. For example, <code>ap-east-2</code> supports only 25 instance families.</li><li>In some regions, not all instance types of an instance family are supported. For example, while the instance family <code>i4i</code> is supported in <code>il-central-1</code>, the instance types <code>i4i.24xlarge</code> and <code>i4i.12xlarge</code> are not.</li></ul><p>Want to look into the details? The following tables are for you.</p><h2 id="Region-Coverage-by-Instance-Family"><a href="#Region-Coverage-by-Instance-Family" class="headerlink" title="Region Coverage by Instance Family"></a>Region Coverage by Instance Family</h2><p>The following high-score list shows which instance families are available in most regions.</p><table class="table table-striped table-responsive table-no-wrap"><thead><tr><th>Instance Family</th><th style="text-align:right">Region Coverage</th></tr></thead><tbody><tr><td>c6g</td><td style="text-align:right">100% (34)</td></tr><tr><td>i3en</td><td style="text-align:right">100% (34)</td></tr><tr><td>i4i</td><td style="text-align:right">100% (34)</td></tr><tr><td>m6g</td><td style="text-align:right">100% (34)</td></tr><tr><td>m6gd</td><td style="text-align:right">100% (34)</td></tr><tr><td>r6g</td><td style="text-align:right">100% (34)</td></tr><tr><td>r7g</td><td style="text-align:right">100% (34)</td></tr><tr><td>t3</td><td style="text-align:right">100% (34)</td></tr><tr><td>t4g</td><td style="text-align:right">100% (34)</td></tr><tr><td>c6in</td><td style="text-align:right">94% (32)</td></tr><tr><td>c7g</td><td style="text-align:right">94% (32)</td></tr><tr><td>m6i</td><td style="text-align:right">94% (32)</td></tr><tr><td>m7g</td><td style="text-align:right">94% (32)</td></tr><tr><td>r6i</td><td style="text-align:right">91% (31)</td></tr><tr><td>c5</td><td style="text-align:right">85% (29)</td></tr><tr><td>c6i</td><td style="text-align:right">85% (29)</td></tr><tr><td>c7i</td><td style="text-align:right">85% (29)</td></tr><tr><td>m5</td><td style="text-align:right">85% (29)</td></tr><tr><td>m5d</td><td style="text-align:right">85% (29)</td></tr><tr><td>m7i</td><td style="text-align:right">85% (29)</td></tr><tr><td>r5</td><td style="text-align:right">85% (29)</td></tr><tr><td>x2idn</td><td style="text-align:right">85% (29)</td></tr><tr><td>c5d</td><td style="text-align:right">82% (28)</td></tr><tr><td>c6gn</td><td style="text-align:right">82% (28)</td></tr><tr><td>i3</td><td style="text-align:right">82% (28)</td></tr><tr><td>r5d</td><td style="text-align:right">82% (28)</td></tr><tr><td>i7i</td><td style="text-align:right">79% (27)</td></tr><tr><td>r7gd</td><td style="text-align:right">79% (27)</td></tr><tr><td>r7i</td><td style="text-align:right">79% (27)</td></tr><tr><td>c7i-flex</td><td style="text-align:right">76% (26)</td></tr><tr><td>m7i-flex</td><td style="text-align:right">76% (26)</td></tr><tr><td>c8g</td><td style="text-align:right">71% (24)</td></tr><tr><td>x2iedn</td><td style="text-align:right">71% (24)</td></tr><tr><td>m6id</td><td style="text-align:right">68% (23)</td></tr><tr><td>u-6tb1</td><td style="text-align:right">68% (23)</td></tr><tr><td>c5n</td><td style="text-align:right">65% (22)</td></tr><tr><td>c6id</td><td style="text-align:right">62% (21)</td></tr><tr><td>c7gd</td><td style="text-align:right">62% (21)</td></tr><tr><td>g4dn</td><td style="text-align:right">62% (21)</td></tr><tr><td>m7gd</td><td style="text-align:right">62% (21)</td></tr><tr><td>m8g</td><td style="text-align:right">62% (21)</td></tr><tr><td>r8g</td><td style="text-align:right">62% (21)</td></tr><tr><td>c5a</td><td style="text-align:right">59% (20)</td></tr><tr><td>c6gd</td><td style="text-align:right">59% (20)</td></tr><tr><td>d2</td><td style="text-align:right">59% (20)</td></tr><tr><td>inf1</td><td style="text-align:right">59% (20)</td></tr><tr><td>r6gd</td><td style="text-align:right">59% (20)</td></tr><tr><td>r6id</td><td style="text-align:right">59% (20)</td></tr><tr><td>r5n</td><td style="text-align:right">56% (19)</td></tr><tr><td>g6</td><td style="text-align:right">50% (17)</td></tr><tr><td>t3a</td><td style="text-align:right">50% (17)</td></tr><tr><td>x1</td><td style="text-align:right">50% (17)</td></tr><tr><td>g5</td><td style="text-align:right">47% (16)</td></tr><tr><td>gr6</td><td style="text-align:right">47% (16)</td></tr><tr><td>i8g</td><td style="text-align:right">47% (16)</td></tr><tr><td>m5a</td><td style="text-align:right">47% (16)</td></tr><tr><td>m6a</td><td style="text-align:right">47% (16)</td></tr><tr><td>r4</td><td style="text-align:right">47% (16)</td></tr><tr><td>r5a</td><td style="text-align:right">47% (16)</td></tr><tr><td>t2</td><td style="text-align:right">47% (16)</td></tr><tr><td>c4</td><td style="text-align:right">44% (15)</td></tr><tr><td>c6a</td><td style="text-align:right">44% (15)</td></tr><tr><td>i7ie</td><td style="text-align:right">44% (15)</td></tr><tr><td>m4</td><td style="text-align:right">44% (15)</td></tr><tr><td>m5ad</td><td style="text-align:right">44% (15)</td></tr><tr><td>m6idn</td><td style="text-align:right">44% (15)</td></tr><tr><td>m6in</td><td style="text-align:right">44% (15)</td></tr><tr><td>r5ad</td><td style="text-align:right">44% (15)</td></tr><tr><td>d3</td><td style="text-align:right">41% (14)</td></tr><tr><td>im4gn</td><td style="text-align:right">41% (14)</td></tr><tr><td>inf2</td><td style="text-align:right">41% (14)</td></tr><tr><td>r5b</td><td style="text-align:right">41% (14)</td></tr><tr><td>u-3tb1</td><td style="text-align:right">41% (14)</td></tr><tr><td>x1e</td><td style="text-align:right">41% (14)</td></tr><tr><td>g6f</td><td style="text-align:right">38% (13)</td></tr><tr><td>gr6f</td><td style="text-align:right">38% (13)</td></tr><tr><td>p4d</td><td style="text-align:right">38% (13)</td></tr><tr><td>r5dn</td><td style="text-align:right">38% (13)</td></tr><tr><td>is4gen</td><td style="text-align:right">35% (12)</td></tr><tr><td>mac1</td><td style="text-align:right">35% (12)</td></tr><tr><td>p5</td><td style="text-align:right">35% (12)</td></tr><tr><td>r3</td><td style="text-align:right">35% (12)</td></tr><tr><td>r6a</td><td style="text-align:right">35% (12)</td></tr><tr><td>r8gd</td><td style="text-align:right">35% (12)</td></tr><tr><td>u7i-6tb</td><td style="text-align:right">35% (12)</td></tr><tr><td>z1d</td><td style="text-align:right">35% (12)</td></tr><tr><td>c5ad</td><td style="text-align:right">32% (11)</td></tr><tr><td>c8gd</td><td style="text-align:right">32% (11)</td></tr><tr><td>c8gn</td><td style="text-align:right">32% (11)</td></tr><tr><td>i2</td><td style="text-align:right">32% (11)</td></tr><tr><td>m5zn</td><td style="text-align:right">32% (11)</td></tr><tr><td>m8gd</td><td style="text-align:right">32% (11)</td></tr><tr><td>p3</td><td style="text-align:right">32% (11)</td></tr><tr><td>r8i</td><td style="text-align:right">32% (11)</td></tr><tr><td>r8i-flex</td><td style="text-align:right">32% (11)</td></tr><tr><td>p5en</td><td style="text-align:right">29% (10)</td></tr><tr><td>u7i-12tb</td><td style="text-align:right">29% (10)</td></tr><tr><td>a1</td><td style="text-align:right">26% (9)</td></tr><tr><td>c3</td><td style="text-align:right">26% (9)</td></tr><tr><td>c7a</td><td style="text-align:right">26% (9)</td></tr><tr><td>m3</td><td style="text-align:right">26% (9)</td></tr><tr><td>m7a</td><td style="text-align:right">26% (9)</td></tr><tr><td>r6idn</td><td style="text-align:right">26% (9)</td></tr><tr><td>r6in</td><td style="text-align:right">26% (9)</td></tr><tr><td>c1</td><td style="text-align:right">24% (8)</td></tr><tr><td>d3en</td><td style="text-align:right">24% (8)</td></tr><tr><td>f2</td><td style="text-align:right">24% (8)</td></tr><tr><td>g4ad</td><td style="text-align:right">24% (8)</td></tr><tr><td>g5g</td><td style="text-align:right">24% (8)</td></tr><tr><td>g6e</td><td style="text-align:right">24% (8)</td></tr><tr><td>i4g</td><td style="text-align:right">24% (8)</td></tr><tr><td>m1</td><td style="text-align:right">24% (8)</td></tr><tr><td>m2</td><td style="text-align:right">24% (8)</td></tr><tr><td>r7a</td><td style="text-align:right">24% (8)</td></tr><tr><td>t1</td><td style="text-align:right">24% (8)</td></tr><tr><td>u7in-16tb</td><td style="text-align:right">24% (8)</td></tr><tr><td>m5dn</td><td style="text-align:right">21% (7)</td></tr><tr><td>m5n</td><td style="text-align:right">21% (7)</td></tr><tr><td>p5e</td><td style="text-align:right">21% (7)</td></tr><tr><td>u7i-8tb</td><td style="text-align:right">21% (7)</td></tr><tr><td>f1</td><td style="text-align:right">18% (6)</td></tr><tr><td>mac2-m2</td><td style="text-align:right">18% (6)</td></tr><tr><td>p4de</td><td style="text-align:right">18% (6)</td></tr><tr><td>r7iz</td><td style="text-align:right">18% (6)</td></tr><tr><td>trn1</td><td style="text-align:right">18% (6)</td></tr><tr><td>x8g</td><td style="text-align:right">18% (6)</td></tr><tr><td>c7gn</td><td style="text-align:right">15% (5)</td></tr><tr><td>m8a</td><td style="text-align:right">15% (5)</td></tr><tr><td>m8i</td><td style="text-align:right">15% (5)</td></tr><tr><td>m8i-flex</td><td style="text-align:right">15% (5)</td></tr><tr><td>mac2</td><td style="text-align:right">15% (5)</td></tr><tr><td>x2iezn</td><td style="text-align:right">15% (5)</td></tr><tr><td>c8i</td><td style="text-align:right">12% (4)</td></tr><tr><td>c8i-flex</td><td style="text-align:right">12% (4)</td></tr><tr><td>h1</td><td style="text-align:right">12% (4)</td></tr><tr><td>hpc6a</td><td style="text-align:right">12% (4)</td></tr><tr><td>hpc7a</td><td style="text-align:right">12% (4)</td></tr><tr><td>i8ge</td><td style="text-align:right">12% (4)</td></tr><tr><td>mac2-m2pro</td><td style="text-align:right">12% (4)</td></tr><tr><td>p3dn</td><td style="text-align:right">12% (4)</td></tr><tr><td>u7in-24tb</td><td style="text-align:right">12% (4)</td></tr><tr><td>vt1</td><td style="text-align:right">12% (4)</td></tr><tr><td>x2gd</td><td style="text-align:right">12% (4)</td></tr><tr><td>c8a</td><td style="text-align:right">9% (3)</td></tr><tr><td>hpc6id</td><td style="text-align:right">9% (3)</td></tr><tr><td>hpc7g</td><td style="text-align:right">9% (3)</td></tr><tr><td>p6-b200</td><td style="text-align:right">9% (3)</td></tr><tr><td>r8a</td><td style="text-align:right">9% (3)</td></tr><tr><td>trn1n</td><td style="text-align:right">9% (3)</td></tr><tr><td>trn2</td><td style="text-align:right">9% (3)</td></tr><tr><td>c8gb</td><td style="text-align:right">6% (2)</td></tr><tr><td>dl1</td><td style="text-align:right">6% (2)</td></tr><tr><td>dl2q</td><td style="text-align:right">6% (2)</td></tr><tr><td>mac-m4</td><td style="text-align:right">6% (2)</td></tr><tr><td>mac-m4pro</td><td style="text-align:right">6% (2)</td></tr><tr><td>mac2-m1ultra</td><td style="text-align:right">6% (2)</td></tr><tr><td>r8gb</td><td style="text-align:right">6% (2)</td></tr><tr><td>r8gn</td><td style="text-align:right">6% (2)</td></tr><tr><td>u7in-32tb</td><td style="text-align:right">6% (2)</td></tr><tr><td>x8aedz</td><td style="text-align:right">6% (2)</td></tr><tr><td>p6-b300</td><td style="text-align:right">3% (1)</td></tr></tbody></table><h2 id="Available-Instance-Families-per-Region"><a href="#Available-Instance-Families-per-Region" class="headerlink" title="Available Instance Families per Region"></a>Available Instance Families per Region</h2><h3 id="af-south-1"><a href="#af-south-1" class="headerlink" title="af-south-1"></a>af-south-1</h3><p>The following instance families are supported in region <code>af-south-1</code>.</p><p><code>c5</code>, <code>c5a</code>, <code>c5ad</code>, <code>c5d</code>, <code>c5n</code>, <code>c6g</code>, <code>c6i</code>, <code>c6in</code>, <code>c7g</code>, <code>d2</code>, <code>g4dn</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>inf1</code>, <code>m5</code>, <code>m5d</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m7g</code>, <code>r5</code>, <code>r5d</code>, <code>r5dn</code>, <code>r5n</code>, <code>r6g</code>, <code>r6i</code>, <code>r6id</code>, <code>r7g</code>, <code>r7gd</code>, <code>t3</code>, <code>t4g</code>, <code>u-6tb1</code>, <code>x1</code>, <code>x1e</code>, <code>x2idn</code>, <code>x2iedn</code></p><h3 id="ap-east-1"><a href="#ap-east-1" class="headerlink" title="ap-east-1"></a>ap-east-1</h3><p>The following instance families are supported in region <code>ap-east-1</code>.</p><p><code>c5</code>, <code>c5a</code>, <code>c5d</code>, <code>c5n</code>, <code>c6a</code>, <code>c6g</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6in</code>, <code>c7g</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>d2</code>, <code>g4dn</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>inf1</code>, <code>m5</code>, <code>m5d</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m7g</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8g</code>, <code>r5</code>, <code>r5d</code>, <code>r5n</code>, <code>r6g</code>, <code>r6i</code>, <code>r7g</code>, <code>r7gd</code>, <code>r8g</code>, <code>t3</code>, <code>t4g</code>, <code>u-3tb1</code>, <code>x1</code></p><h3 id="ap-east-2"><a href="#ap-east-2" class="headerlink" title="ap-east-2"></a>ap-east-2</h3><p>The following instance families are supported in region <code>ap-east-2</code>.</p><p><code>c6g</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c7g</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>i3en</code>, <code>i4i</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>r6g</code>, <code>r6i</code>, <code>r6id</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>t3</code>, <code>t4g</code></p><h3 id="ap-northeast-1"><a href="#ap-northeast-1" class="headerlink" title="ap-northeast-1"></a>ap-northeast-1</h3><p>The following instance families are supported in region <code>ap-northeast-1</code>.</p><p><code>a1</code>, <code>c1</code>, <code>c3</code>, <code>c4</code>, <code>c5</code>, <code>c5a</code>, <code>c5d</code>, <code>c5n</code>, <code>c6a</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7a</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7gn</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>c8gd</code>, <code>d2</code>, <code>d3</code>, <code>d3en</code>, <code>f2</code>, <code>g4ad</code>, <code>g4dn</code>, <code>g5</code>, <code>g5g</code>, <code>g6</code>, <code>g6e</code>, <code>g6f</code>, <code>gr6</code>, <code>gr6f</code>, <code>hpc7g</code>, <code>i2</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>i7ie</code>, <code>i8g</code>, <code>im4gn</code>, <code>inf1</code>, <code>inf2</code>, <code>is4gen</code>, <code>m1</code>, <code>m2</code>, <code>m3</code>, <code>m4</code>, <code>m5</code>, <code>m5a</code>, <code>m5ad</code>, <code>m5d</code>, <code>m5dn</code>, <code>m5n</code>, <code>m5zn</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m6idn</code>, <code>m6in</code>, <code>m7a</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8a</code>, <code>m8g</code>, <code>m8gd</code>, <code>mac1</code>, <code>p3</code>, <code>p3dn</code>, <code>p4d</code>, <code>p4de</code>, <code>p5</code>, <code>p5en</code>, <code>r3</code>, <code>r4</code>, <code>r5</code>, <code>r5a</code>, <code>r5ad</code>, <code>r5b</code>, <code>r5d</code>, <code>r5dn</code>, <code>r5n</code>, <code>r6a</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r6id</code>, <code>r6idn</code>, <code>r6in</code>, <code>r7a</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r7iz</code>, <code>r8g</code>, <code>r8gd</code>, <code>t1</code>, <code>t2</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>u-3tb1</code>, <code>u-6tb1</code>, <code>u7i-6tb</code>, <code>vt1</code>, <code>x1</code>, <code>x1e</code>, <code>x2idn</code>, <code>x2iedn</code>, <code>x2iezn</code>, <code>x8aedz</code>, <code>z1d</code></p><h3 id="ap-northeast-2"><a href="#ap-northeast-2" class="headerlink" title="ap-northeast-2"></a>ap-northeast-2</h3><p>The following instance families are supported in region <code>ap-northeast-2</code>.</p><p><code>c4</code>, <code>c5</code>, <code>c5a</code>, <code>c5d</code>, <code>c5n</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>d2</code>, <code>f2</code>, <code>g4dn</code>, <code>g5</code>, <code>g5g</code>, <code>g6</code>, <code>g6e</code>, <code>g6f</code>, <code>gr6</code>, <code>gr6f</code>, <code>i2</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>i8g</code>, <code>inf1</code>, <code>inf2</code>, <code>m4</code>, <code>m5</code>, <code>m5a</code>, <code>m5ad</code>, <code>m5d</code>, <code>m5zn</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m6idn</code>, <code>m6in</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8g</code>, <code>mac1</code>, <code>p3</code>, <code>p4d</code>, <code>p5en</code>, <code>r3</code>, <code>r4</code>, <code>r5</code>, <code>r5a</code>, <code>r5ad</code>, <code>r5b</code>, <code>r5d</code>, <code>r5dn</code>, <code>r5n</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r6id</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r8g</code>, <code>t2</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>u-6tb1</code>, <code>u7i-12tb</code>, <code>u7i-6tb</code>, <code>u7i-8tb</code>, <code>u7in-16tb</code>, <code>x1</code>, <code>x1e</code>, <code>x2idn</code>, <code>x2iedn</code>, <code>z1d</code></p><h3 id="ap-northeast-3"><a href="#ap-northeast-3" class="headerlink" title="ap-northeast-3"></a>ap-northeast-3</h3><p>The following instance families are supported in region <code>ap-northeast-3</code>.</p><p><code>c4</code>, <code>c5</code>, <code>c5d</code>, <code>c5n</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6in</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c8g</code>, <code>d2</code>, <code>g4dn</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>i8g</code>, <code>m4</code>, <code>m5</code>, <code>m5d</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m7g</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8g</code>, <code>r4</code>, <code>r5</code>, <code>r5d</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r8g</code>, <code>t2</code>, <code>t3</code>, <code>t4g</code>, <code>u-6tb1</code>, <code>x1</code>, <code>x1e</code>, <code>x2idn</code>, <code>x2iedn</code></p><h3 id="ap-south-1"><a href="#ap-south-1" class="headerlink" title="ap-south-1"></a>ap-south-1</h3><p>The following instance families are supported in region <code>ap-south-1</code>.</p><p><code>a1</code>, <code>c4</code>, <code>c5</code>, <code>c5a</code>, <code>c5d</code>, <code>c5n</code>, <code>c6a</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6in</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>d2</code>, <code>d3</code>, <code>g4dn</code>, <code>g5</code>, <code>g6</code>, <code>g6f</code>, <code>gr6</code>, <code>gr6f</code>, <code>i2</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>i8g</code>, <code>im4gn</code>, <code>inf1</code>, <code>inf2</code>, <code>is4gen</code>, <code>m4</code>, <code>m5</code>, <code>m5a</code>, <code>m5ad</code>, <code>m5d</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m6idn</code>, <code>m6in</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8g</code>, <code>m8i</code>, <code>m8i-flex</code>, <code>mac1</code>, <code>p4d</code>, <code>p5</code>, <code>p5en</code>, <code>r3</code>, <code>r4</code>, <code>r5</code>, <code>r5a</code>, <code>r5ad</code>, <code>r5d</code>, <code>r5n</code>, <code>r6a</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r6id</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r8g</code>, <code>t2</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>trn1</code>, <code>u-6tb1</code>, <code>u7i-12tb</code>, <code>u7i-8tb</code>, <code>u7in-16tb</code>, <code>x1</code>, <code>x1e</code>, <code>x2idn</code>, <code>x2iedn</code>, <code>z1d</code></p><h3 id="ap-south-2"><a href="#ap-south-2" class="headerlink" title="ap-south-2"></a>ap-south-2</h3><p>The following instance families are supported in region <code>ap-south-2</code>.</p><p><code>c5</code>, <code>c5d</code>, <code>c6a</code>, <code>c6g</code>, <code>c6i</code>, <code>c6in</code>, <code>c7g</code>, <code>c7i</code>, <code>c8g</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>m5</code>, <code>m5d</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m7g</code>, <code>m8g</code>, <code>r5</code>, <code>r5d</code>, <code>r6a</code>, <code>r6g</code>, <code>r6i</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>t3</code>, <code>t4g</code>, <code>u-6tb1</code>, <code>u7i-12tb</code>, <code>x2idn</code>, <code>x2iedn</code></p><h3 id="ap-southeast-1"><a href="#ap-southeast-1" class="headerlink" title="ap-southeast-1"></a>ap-southeast-1</h3><p>The following instance families are supported in region <code>ap-southeast-1</code>.</p><p><code>a1</code>, <code>c1</code>, <code>c3</code>, <code>c4</code>, <code>c5</code>, <code>c5a</code>, <code>c5ad</code>, <code>c5d</code>, <code>c5n</code>, <code>c6a</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>c8gn</code>, <code>d2</code>, <code>d3</code>, <code>d3en</code>, <code>g4dn</code>, <code>g5g</code>, <code>hpc6a</code>, <code>i2</code>, <code>i3</code>, <code>i3en</code>, <code>i4g</code>, <code>i4i</code>, <code>i7i</code>, <code>i7ie</code>, <code>i8g</code>, <code>im4gn</code>, <code>inf1</code>, <code>inf2</code>, <code>is4gen</code>, <code>m1</code>, <code>m2</code>, <code>m3</code>, <code>m4</code>, <code>m5</code>, <code>m5a</code>, <code>m5ad</code>, <code>m5d</code>, <code>m5dn</code>, <code>m5n</code>, <code>m5zn</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m6idn</code>, <code>m6in</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8g</code>, <code>mac1</code>, <code>mac2</code>, <code>p3</code>, <code>p4de</code>, <code>r3</code>, <code>r4</code>, <code>r5</code>, <code>r5a</code>, <code>r5ad</code>, <code>r5b</code>, <code>r5d</code>, <code>r5dn</code>, <code>r5n</code>, <code>r6a</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r6id</code>, <code>r6idn</code>, <code>r6in</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r8g</code>, <code>r8i</code>, <code>r8i-flex</code>, <code>t1</code>, <code>t2</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>u-3tb1</code>, <code>u-6tb1</code>, <code>u7in-16tb</code>, <code>x1</code>, <code>x1e</code>, <code>x2idn</code>, <code>x2iedn</code>, <code>z1d</code></p><h3 id="ap-southeast-2"><a href="#ap-southeast-2" class="headerlink" title="ap-southeast-2"></a>ap-southeast-2</h3><p>The following instance families are supported in region <code>ap-southeast-2</code>.</p><p><code>a1</code>, <code>c1</code>, <code>c3</code>, <code>c4</code>, <code>c5</code>, <code>c5a</code>, <code>c5ad</code>, <code>c5d</code>, <code>c5n</code>, <code>c6a</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>c8gd</code>, <code>c8gn</code>, <code>d2</code>, <code>d3</code>, <code>d3en</code>, <code>f1</code>, <code>f2</code>, <code>g4dn</code>, <code>g5</code>, <code>g6</code>, <code>g6f</code>, <code>gr6</code>, <code>gr6f</code>, <code>hpc6a</code>, <code>i2</code>, <code>i3</code>, <code>i3en</code>, <code>i4g</code>, <code>i4i</code>, <code>i7i</code>, <code>i7ie</code>, <code>i8g</code>, <code>im4gn</code>, <code>inf1</code>, <code>inf2</code>, <code>is4gen</code>, <code>m1</code>, <code>m2</code>, <code>m3</code>, <code>m4</code>, <code>m5</code>, <code>m5a</code>, <code>m5ad</code>, <code>m5d</code>, <code>m5zn</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m6idn</code>, <code>m6in</code>, <code>m7a</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8g</code>, <code>m8gd</code>, <code>mac1</code>, <code>mac2-m2</code>, <code>mac2-m2pro</code>, <code>p3</code>, <code>p4d</code>, <code>p5</code>, <code>p5e</code>, <code>r3</code>, <code>r4</code>, <code>r5</code>, <code>r5a</code>, <code>r5ad</code>, <code>r5b</code>, <code>r5d</code>, <code>r5dn</code>, <code>r5n</code>, <code>r6a</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r6id</code>, <code>r6idn</code>, <code>r6in</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r8g</code>, <code>r8gd</code>, <code>r8i</code>, <code>r8i-flex</code>, <code>t1</code>, <code>t2</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>trn1</code>, <code>u-3tb1</code>, <code>u-6tb1</code>, <code>u7i-6tb</code>, <code>u7in-16tb</code>, <code>x1</code>, <code>x1e</code>, <code>x2idn</code>, <code>x2iedn</code>, <code>x8g</code>, <code>z1d</code></p><h3 id="ap-southeast-3"><a href="#ap-southeast-3" class="headerlink" title="ap-southeast-3"></a>ap-southeast-3</h3><p>The following instance families are supported in region <code>ap-southeast-3</code>.</p><p><code>c5</code>, <code>c5d</code>, <code>c5n</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6in</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>d3en</code>, <code>g5</code>, <code>g5g</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>i7ie</code>, <code>m5</code>, <code>m5d</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m7g</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>p5</code>, <code>p5e</code>, <code>p5en</code>, <code>r5</code>, <code>r5d</code>, <code>r6g</code>, <code>r6gd</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r8g</code>, <code>t3</code>, <code>t4g</code>, <code>u-6tb1</code>, <code>u7i-6tb</code>, <code>x2idn</code>, <code>x2iedn</code></p><h3 id="ap-southeast-4"><a href="#ap-southeast-4" class="headerlink" title="ap-southeast-4"></a>ap-southeast-4</h3><p>The following instance families are supported in region <code>ap-southeast-4</code>.</p><p><code>c5</code>, <code>c5d</code>, <code>c6g</code>, <code>c6in</code>, <code>c7i</code>, <code>c8g</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>m5</code>, <code>m5d</code>, <code>m6g</code>, <code>m6gd</code>, <code>m7g</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>r5</code>, <code>r5d</code>, <code>r6g</code>, <code>r7g</code>, <code>r7i</code>, <code>r8g</code>, <code>t3</code>, <code>t4g</code>, <code>trn1</code>, <code>trn2</code>, <code>x2idn</code></p><h3 id="ap-southeast-5"><a href="#ap-southeast-5" class="headerlink" title="ap-southeast-5"></a>ap-southeast-5</h3><p>The following instance families are supported in region <code>ap-southeast-5</code>.</p><p><code>c6g</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>c8gd</code>, <code>c8gn</code>, <code>g6</code>, <code>gr6</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>i7ie</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8gd</code>, <code>r6g</code>, <code>r6i</code>, <code>r6id</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r8g</code>, <code>r8gd</code>, <code>r8i</code>, <code>r8i-flex</code>, <code>t3</code>, <code>t4g</code>, <code>x2idn</code>, <code>x2iedn</code></p><h3 id="ap-southeast-6"><a href="#ap-southeast-6" class="headerlink" title="ap-southeast-6"></a>ap-southeast-6</h3><p>The following instance families are supported in region <code>ap-southeast-6</code>.</p><p><code>c6g</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c7g</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>i3en</code>, <code>i4i</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>r6g</code>, <code>r6i</code>, <code>r6id</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>t3</code>, <code>t4g</code></p><h3 id="ap-southeast-7"><a href="#ap-southeast-7" class="headerlink" title="ap-southeast-7"></a>ap-southeast-7</h3><p>The following instance families are supported in region <code>ap-southeast-7</code>.</p><p><code>c6g</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7g</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>c8gn</code>, <code>i3en</code>, <code>i4i</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>r6g</code>, <code>r6i</code>, <code>r6id</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>t3</code>, <code>t4g</code>, <code>x2idn</code>, <code>x2iedn</code></p><h3 id="ca-central-1"><a href="#ca-central-1" class="headerlink" title="ca-central-1"></a>ca-central-1</h3><p>The following instance families are supported in region <code>ca-central-1</code>.</p><p><code>c4</code>, <code>c5</code>, <code>c5a</code>, <code>c5d</code>, <code>c5n</code>, <code>c6a</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>c8gd</code>, <code>d2</code>, <code>d3</code>, <code>f2</code>, <code>g4ad</code>, <code>g4dn</code>, <code>g5</code>, <code>g6</code>, <code>g6f</code>, <code>gr6</code>, <code>gr6f</code>, <code>i3</code>, <code>i3en</code>, <code>i4g</code>, <code>i4i</code>, <code>i7i</code>, <code>i8g</code>, <code>im4gn</code>, <code>inf1</code>, <code>is4gen</code>, <code>m4</code>, <code>m5</code>, <code>m5a</code>, <code>m5ad</code>, <code>m5d</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m6idn</code>, <code>m6in</code>, <code>m7g</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8g</code>, <code>m8gd</code>, <code>mac2-m2</code>, <code>p3</code>, <code>p4d</code>, <code>p5</code>, <code>r4</code>, <code>r5</code>, <code>r5a</code>, <code>r5ad</code>, <code>r5b</code>, <code>r5d</code>, <code>r5n</code>, <code>r6a</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r7g</code>, <code>r7i</code>, <code>r8g</code>, <code>r8gd</code>, <code>r8i</code>, <code>r8i-flex</code>, <code>t2</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>u-3tb1</code>, <code>u-6tb1</code>, <code>x1</code>, <code>x1e</code>, <code>x2idn</code>, <code>x2iedn</code></p><h3 id="ca-west-1"><a href="#ca-west-1" class="headerlink" title="ca-west-1"></a>ca-west-1</h3><p>The following instance families are supported in region <code>ca-west-1</code>.</p><p><code>c5</code>, <code>c6g</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7g</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>m5</code>, <code>m5d</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>r5</code>, <code>r6g</code>, <code>r6i</code>, <code>r6id</code>, <code>r7g</code>, <code>t3</code>, <code>t4g</code></p><h3 id="eu-central-1"><a href="#eu-central-1" class="headerlink" title="eu-central-1"></a>eu-central-1</h3><p>The following instance families are supported in region <code>eu-central-1</code>.</p><p><code>a1</code>, <code>c3</code>, <code>c4</code>, <code>c5</code>, <code>c5a</code>, <code>c5ad</code>, <code>c5d</code>, <code>c5n</code>, <code>c6a</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7a</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>c8gd</code>, <code>c8gn</code>, <code>d2</code>, <code>d3</code>, <code>d3en</code>, <code>dl2q</code>, <code>f1</code>, <code>f2</code>, <code>g4ad</code>, <code>g4dn</code>, <code>g5</code>, <code>g5g</code>, <code>g6</code>, <code>g6e</code>, <code>g6f</code>, <code>gr6</code>, <code>gr6f</code>, <code>i2</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>i7ie</code>, <code>i8g</code>, <code>i8ge</code>, <code>im4gn</code>, <code>inf1</code>, <code>inf2</code>, <code>is4gen</code>, <code>m3</code>, <code>m4</code>, <code>m5</code>, <code>m5a</code>, <code>m5ad</code>, <code>m5d</code>, <code>m5dn</code>, <code>m5n</code>, <code>m5zn</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m6idn</code>, <code>m6in</code>, <code>m7a</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8g</code>, <code>m8gd</code>, <code>mac1</code>, <code>mac2-m2</code>, <code>p3</code>, <code>p4d</code>, <code>p4de</code>, <code>r3</code>, <code>r4</code>, <code>r5</code>, <code>r5a</code>, <code>r5ad</code>, <code>r5b</code>, <code>r5d</code>, <code>r5dn</code>, <code>r5n</code>, <code>r6a</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r6id</code>, <code>r6idn</code>, <code>r6in</code>, <code>r7a</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r7iz</code>, <code>r8g</code>, <code>r8gd</code>, <code>r8i</code>, <code>r8i-flex</code>, <code>t2</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>u-3tb1</code>, <code>u-6tb1</code>, <code>u7i-12tb</code>, <code>u7i-6tb</code>, <code>u7i-8tb</code>, <code>u7in-16tb</code>, <code>u7in-24tb</code>, <code>x1</code>, <code>x1e</code>, <code>x2idn</code>, <code>x2iedn</code>, <code>x8g</code>, <code>z1d</code></p><h3 id="eu-central-2"><a href="#eu-central-2" class="headerlink" title="eu-central-2"></a>eu-central-2</h3><p>The following instance families are supported in region <code>eu-central-2</code>.</p><p><code>c5</code>, <code>c5d</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6in</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>d3</code>, <code>g6</code>, <code>gr6</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>m5</code>, <code>m5d</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m6idn</code>, <code>m6in</code>, <code>m7g</code>, <code>m7i</code>, <code>r5</code>, <code>r5d</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r7g</code>, <code>t3</code>, <code>t4g</code>, <code>u-3tb1</code>, <code>u-6tb1</code>, <code>x2idn</code></p><h3 id="eu-north-1"><a href="#eu-north-1" class="headerlink" title="eu-north-1"></a>eu-north-1</h3><p>The following instance families are supported in region <code>eu-north-1</code>.</p><p><code>c5</code>, <code>c5a</code>, <code>c5d</code>, <code>c5n</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6in</code>, <code>c7a</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>c8gn</code>, <code>d2</code>, <code>g4dn</code>, <code>g5</code>, <code>g6</code>, <code>g6e</code>, <code>g6f</code>, <code>gr6</code>, <code>gr6f</code>, <code>hpc6a</code>, <code>hpc6id</code>, <code>hpc7a</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>i7ie</code>, <code>i8g</code>, <code>inf1</code>, <code>inf2</code>, <code>m5</code>, <code>m5d</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6idn</code>, <code>m6in</code>, <code>m7a</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8g</code>, <code>mac1</code>, <code>p4d</code>, <code>p5</code>, <code>p5e</code>, <code>p5en</code>, <code>r5</code>, <code>r5b</code>, <code>r5d</code>, <code>r5dn</code>, <code>r5n</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r6idn</code>, <code>r6in</code>, <code>r7a</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r8g</code>, <code>t3</code>, <code>t4g</code>, <code>u-6tb1</code>, <code>u7i-12tb</code>, <code>u7i-6tb</code>, <code>x2idn</code>, <code>x2iedn</code>, <code>x8g</code></p><h3 id="eu-south-1"><a href="#eu-south-1" class="headerlink" title="eu-south-1"></a>eu-south-1</h3><p>The following instance families are supported in region <code>eu-south-1</code>.</p><p><code>c5</code>, <code>c5a</code>, <code>c5ad</code>, <code>c5d</code>, <code>c5n</code>, <code>c6g</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7g</code>, <code>c8g</code>, <code>d2</code>, <code>g4dn</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>im4gn</code>, <code>inf1</code>, <code>m5</code>, <code>m5a</code>, <code>m5d</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m7i</code>, <code>r5</code>, <code>r5a</code>, <code>r5b</code>, <code>r5d</code>, <code>r5dn</code>, <code>r5n</code>, <code>r6g</code>, <code>r6i</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>u-3tb1</code>, <code>u-6tb1</code>, <code>x2idn</code>, <code>x2iedn</code></p><h3 id="eu-south-2"><a href="#eu-south-2" class="headerlink" title="eu-south-2"></a>eu-south-2</h3><p>The following instance families are supported in region <code>eu-south-2</code>.</p><p><code>c5</code>, <code>c5d</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6in</code>, <code>c7a</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>c8gd</code>, <code>c8i</code>, <code>c8i-flex</code>, <code>g5g</code>, <code>g6</code>, <code>g6e</code>, <code>g6f</code>, <code>gr6</code>, <code>gr6f</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>i7ie</code>, <code>i8g</code>, <code>im4gn</code>, <code>m5</code>, <code>m5d</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6id</code>, <code>m6idn</code>, <code>m6in</code>, <code>m7a</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8a</code>, <code>m8g</code>, <code>m8gd</code>, <code>m8i</code>, <code>m8i-flex</code>, <code>p5en</code>, <code>r5</code>, <code>r5d</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6id</code>, <code>r7a</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r8g</code>, <code>r8gd</code>, <code>r8i</code>, <code>r8i-flex</code>, <code>t3</code>, <code>t4g</code>, <code>u-6tb1</code>, <code>x2idn</code>, <code>x2iedn</code></p><h3 id="eu-west-1"><a href="#eu-west-1" class="headerlink" title="eu-west-1"></a>eu-west-1</h3><p>The following instance families are supported in region <code>eu-west-1</code>.</p><p><code>a1</code>, <code>c1</code>, <code>c3</code>, <code>c4</code>, <code>c5</code>, <code>c5a</code>, <code>c5ad</code>, <code>c5d</code>, <code>c5n</code>, <code>c6a</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7a</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7gn</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>c8gd</code>, <code>d2</code>, <code>d3</code>, <code>d3en</code>, <code>f1</code>, <code>g4ad</code>, <code>g4dn</code>, <code>g5</code>, <code>h1</code>, <code>hpc7a</code>, <code>hpc7g</code>, <code>i2</code>, <code>i3</code>, <code>i3en</code>, <code>i4g</code>, <code>i4i</code>, <code>i7i</code>, <code>i7ie</code>, <code>i8g</code>, <code>im4gn</code>, <code>inf1</code>, <code>inf2</code>, <code>is4gen</code>, <code>m1</code>, <code>m2</code>, <code>m3</code>, <code>m4</code>, <code>m5</code>, <code>m5a</code>, <code>m5ad</code>, <code>m5d</code>, <code>m5dn</code>, <code>m5n</code>, <code>m5zn</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m6idn</code>, <code>m6in</code>, <code>m7a</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8g</code>, <code>mac1</code>, <code>mac2</code>, <code>p3</code>, <code>p3dn</code>, <code>p4d</code>, <code>r3</code>, <code>r4</code>, <code>r5</code>, <code>r5a</code>, <code>r5ad</code>, <code>r5b</code>, <code>r5d</code>, <code>r5dn</code>, <code>r5n</code>, <code>r6a</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r6id</code>, <code>r6idn</code>, <code>r6in</code>, <code>r7a</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r7iz</code>, <code>r8g</code>, <code>r8gd</code>, <code>t1</code>, <code>t2</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>u-3tb1</code>, <code>u-6tb1</code>, <code>u7i-12tb</code>, <code>u7i-6tb</code>, <code>u7i-8tb</code>, <code>u7in-16tb</code>, <code>vt1</code>, <code>x1</code>, <code>x1e</code>, <code>x2gd</code>, <code>x2idn</code>, <code>x2iedn</code>, <code>x2iezn</code>, <code>z1d</code></p><h3 id="eu-west-2"><a href="#eu-west-2" class="headerlink" title="eu-west-2"></a>eu-west-2</h3><p>The following instance families are supported in region <code>eu-west-2</code>.</p><p><code>c4</code>, <code>c5</code>, <code>c5a</code>, <code>c5d</code>, <code>c5n</code>, <code>c6a</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7a</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>c8gd</code>, <code>d2</code>, <code>d3</code>, <code>f1</code>, <code>f2</code>, <code>g4ad</code>, <code>g4dn</code>, <code>g5</code>, <code>g6</code>, <code>g6f</code>, <code>gr6</code>, <code>gr6f</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>i7ie</code>, <code>i8g</code>, <code>im4gn</code>, <code>inf1</code>, <code>inf2</code>, <code>is4gen</code>, <code>m4</code>, <code>m5</code>, <code>m5a</code>, <code>m5ad</code>, <code>m5d</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m7g</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8g</code>, <code>m8gd</code>, <code>mac1</code>, <code>p3</code>, <code>p4d</code>, <code>p5</code>, <code>p5e</code>, <code>r4</code>, <code>r5</code>, <code>r5a</code>, <code>r5ad</code>, <code>r5b</code>, <code>r5d</code>, <code>r5n</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r6id</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r8g</code>, <code>r8gd</code>, <code>r8i</code>, <code>r8i-flex</code>, <code>t2</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>u-6tb1</code>, <code>u7i-6tb</code>, <code>u7i-8tb</code>, <code>x1</code>, <code>x2idn</code>, <code>x2iedn</code>, <code>z1d</code></p><h3 id="eu-west-3"><a href="#eu-west-3" class="headerlink" title="eu-west-3"></a>eu-west-3</h3><p>The following instance families are supported in region <code>eu-west-3</code>.</p><p><code>c5</code>, <code>c5a</code>, <code>c5d</code>, <code>c5n</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>d2</code>, <code>d3</code>, <code>g4dn</code>, <code>g6</code>, <code>gr6</code>, <code>hpc6id</code>, <code>hpc7a</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>im4gn</code>, <code>inf1</code>, <code>inf2</code>, <code>is4gen</code>, <code>m5</code>, <code>m5a</code>, <code>m5ad</code>, <code>m5d</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8g</code>, <code>r4</code>, <code>r5</code>, <code>r5a</code>, <code>r5ad</code>, <code>r5d</code>, <code>r5dn</code>, <code>r5n</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r7g</code>, <code>r7i</code>, <code>t2</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>u-3tb1</code>, <code>u-6tb1</code>, <code>u7i-6tb</code>, <code>x1</code>, <code>x2idn</code>, <code>x2iedn</code></p><h3 id="il-central-1"><a href="#il-central-1" class="headerlink" title="il-central-1"></a>il-central-1</h3><p>The following instance families are supported in region <code>il-central-1</code>.</p><p><code>c5</code>, <code>c5d</code>, <code>c6g</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7g</code>, <code>d3</code>, <code>g5</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>m5</code>, <code>m5d</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m7g</code>, <code>p4de</code>, <code>r5</code>, <code>r5d</code>, <code>r6g</code>, <code>r6i</code>, <code>r6id</code>, <code>r7g</code>, <code>r7gd</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>u-6tb1</code>, <code>x2idn</code></p><h3 id="me-central-1"><a href="#me-central-1" class="headerlink" title="me-central-1"></a>me-central-1</h3><p>The following instance families are supported in region <code>me-central-1</code>.</p><p><code>c5</code>, <code>c5d</code>, <code>c6g</code>, <code>c6in</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8gn</code>, <code>g5</code>, <code>g6</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>m5</code>, <code>m5d</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>r5</code>, <code>r5d</code>, <code>r6g</code>, <code>r6i</code>, <code>r7g</code>, <code>r7gd</code>, <code>t3</code>, <code>t4g</code>, <code>x2idn</code>, <code>x2iezn</code></p><h3 id="me-south-1"><a href="#me-south-1" class="headerlink" title="me-south-1"></a>me-south-1</h3><p>The following instance families are supported in region <code>me-south-1</code>.</p><p><code>c5</code>, <code>c5a</code>, <code>c5ad</code>, <code>c5d</code>, <code>c5n</code>, <code>c6g</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6in</code>, <code>c7g</code>, <code>d2</code>, <code>g4dn</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>inf1</code>, <code>m5</code>, <code>m5d</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m7g</code>, <code>m8g</code>, <code>r5</code>, <code>r5d</code>, <code>r6g</code>, <code>r6i</code>, <code>r7g</code>, <code>t3</code>, <code>t4g</code>, <code>x2idn</code></p><h3 id="mx-central-1"><a href="#mx-central-1" class="headerlink" title="mx-central-1"></a>mx-central-1</h3><p>The following instance families are supported in region <code>mx-central-1</code>.</p><p><code>c6g</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7g</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>i3en</code>, <code>i4i</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>r6g</code>, <code>r6i</code>, <code>r6id</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>t3</code>, <code>t4g</code></p><h3 id="sa-east-1"><a href="#sa-east-1" class="headerlink" title="sa-east-1"></a>sa-east-1</h3><p>The following instance families are supported in region <code>sa-east-1</code>.</p><p><code>c1</code>, <code>c3</code>, <code>c4</code>, <code>c5</code>, <code>c5a</code>, <code>c5ad</code>, <code>c5d</code>, <code>c5n</code>, <code>c6a</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>g4dn</code>, <code>g5</code>, <code>g6</code>, <code>g6f</code>, <code>gr6</code>, <code>gr6f</code>, <code>i3</code>, <code>i3en</code>, <code>i4g</code>, <code>i4i</code>, <code>i7i</code>, <code>i7ie</code>, <code>i8g</code>, <code>inf1</code>, <code>inf2</code>, <code>m1</code>, <code>m2</code>, <code>m3</code>, <code>m4</code>, <code>m5</code>, <code>m5a</code>, <code>m5ad</code>, <code>m5d</code>, <code>m5zn</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8g</code>, <code>m8gd</code>, <code>p4d</code>, <code>p5</code>, <code>p5e</code>, <code>r3</code>, <code>r4</code>, <code>r5</code>, <code>r5a</code>, <code>r5ad</code>, <code>r5b</code>, <code>r5d</code>, <code>r5n</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r7g</code>, <code>r7i</code>, <code>r8g</code>, <code>r8gd</code>, <code>t1</code>, <code>t2</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>trn2</code>, <code>u-3tb1</code>, <code>u-6tb1</code>, <code>u7i-12tb</code>, <code>x1</code>, <code>x1e</code>, <code>x2idn</code>, <code>x2iedn</code></p><h3 id="us-east-1"><a href="#us-east-1" class="headerlink" title="us-east-1"></a>us-east-1</h3><p>The following instance families are supported in region <code>us-east-1</code>.</p><p><code>a1</code>, <code>c1</code>, <code>c3</code>, <code>c4</code>, <code>c5</code>, <code>c5a</code>, <code>c5ad</code>, <code>c5d</code>, <code>c5n</code>, <code>c6a</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7a</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7gn</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8a</code>, <code>c8g</code>, <code>c8gb</code>, <code>c8gd</code>, <code>c8gn</code>, <code>c8i</code>, <code>c8i-flex</code>, <code>d2</code>, <code>d3</code>, <code>d3en</code>, <code>dl1</code>, <code>f1</code>, <code>f2</code>, <code>g4ad</code>, <code>g4dn</code>, <code>g5</code>, <code>g5g</code>, <code>g6</code>, <code>g6e</code>, <code>g6f</code>, <code>gr6</code>, <code>gr6f</code>, <code>h1</code>, <code>hpc7g</code>, <code>i2</code>, <code>i3</code>, <code>i3en</code>, <code>i4g</code>, <code>i4i</code>, <code>i7i</code>, <code>i7ie</code>, <code>i8g</code>, <code>i8ge</code>, <code>im4gn</code>, <code>inf1</code>, <code>inf2</code>, <code>is4gen</code>, <code>m1</code>, <code>m2</code>, <code>m3</code>, <code>m4</code>, <code>m5</code>, <code>m5a</code>, <code>m5ad</code>, <code>m5d</code>, <code>m5dn</code>, <code>m5n</code>, <code>m5zn</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m6idn</code>, <code>m6in</code>, <code>m7a</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8a</code>, <code>m8g</code>, <code>m8gd</code>, <code>m8i</code>, <code>m8i-flex</code>, <code>mac-m4</code>, <code>mac-m4pro</code>, <code>mac1</code>, <code>mac2</code>, <code>mac2-m1ultra</code>, <code>mac2-m2</code>, <code>mac2-m2pro</code>, <code>p3</code>, <code>p3dn</code>, <code>p4d</code>, <code>p4de</code>, <code>p5</code>, <code>p5en</code>, <code>p6-b200</code>, <code>r3</code>, <code>r4</code>, <code>r5</code>, <code>r5a</code>, <code>r5ad</code>, <code>r5b</code>, <code>r5d</code>, <code>r5dn</code>, <code>r5n</code>, <code>r6a</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r6id</code>, <code>r6idn</code>, <code>r6in</code>, <code>r7a</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r7iz</code>, <code>r8a</code>, <code>r8g</code>, <code>r8gb</code>, <code>r8gd</code>, <code>r8gn</code>, <code>r8i</code>, <code>r8i-flex</code>, <code>t1</code>, <code>t2</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>trn1</code>, <code>trn1n</code>, <code>u-3tb1</code>, <code>u-6tb1</code>, <code>u7i-12tb</code>, <code>u7i-6tb</code>, <code>u7i-8tb</code>, <code>u7in-16tb</code>, <code>u7in-24tb</code>, <code>u7in-32tb</code>, <code>vt1</code>, <code>x1</code>, <code>x1e</code>, <code>x2gd</code>, <code>x2idn</code>, <code>x2iedn</code>, <code>x2iezn</code>, <code>x8g</code>, <code>z1d</code></p><h3 id="us-east-2"><a href="#us-east-2" class="headerlink" title="us-east-2"></a>us-east-2</h3><p>The following instance families are supported in region <code>us-east-2</code>.</p><p><code>a1</code>, <code>c4</code>, <code>c5</code>, <code>c5a</code>, <code>c5ad</code>, <code>c5d</code>, <code>c5n</code>, <code>c6a</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7a</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7gn</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8a</code>, <code>c8g</code>, <code>c8gd</code>, <code>c8gn</code>, <code>c8i</code>, <code>c8i-flex</code>, <code>d2</code>, <code>d3</code>, <code>g4ad</code>, <code>g4dn</code>, <code>g5</code>, <code>g6</code>, <code>g6e</code>, <code>g6f</code>, <code>gr6</code>, <code>gr6f</code>, <code>h1</code>, <code>hpc6a</code>, <code>hpc6id</code>, <code>hpc7a</code>, <code>i2</code>, <code>i3</code>, <code>i3en</code>, <code>i4g</code>, <code>i4i</code>, <code>i7i</code>, <code>i7ie</code>, <code>i8g</code>, <code>i8ge</code>, <code>im4gn</code>, <code>inf1</code>, <code>inf2</code>, <code>is4gen</code>, <code>m4</code>, <code>m5</code>, <code>m5a</code>, <code>m5ad</code>, <code>m5d</code>, <code>m5dn</code>, <code>m5n</code>, <code>m5zn</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m6idn</code>, <code>m6in</code>, <code>m7a</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8a</code>, <code>m8g</code>, <code>m8gd</code>, <code>m8i</code>, <code>m8i-flex</code>, <code>mac1</code>, <code>mac2</code>, <code>mac2-m2</code>, <code>mac2-m2pro</code>, <code>p3</code>, <code>p4d</code>, <code>p5</code>, <code>p5e</code>, <code>p5en</code>, <code>p6-b200</code>, <code>r3</code>, <code>r4</code>, <code>r5</code>, <code>r5a</code>, <code>r5ad</code>, <code>r5b</code>, <code>r5d</code>, <code>r5dn</code>, <code>r5n</code>, <code>r6a</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r6id</code>, <code>r6idn</code>, <code>r6in</code>, <code>r7a</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r7iz</code>, <code>r8a</code>, <code>r8g</code>, <code>r8gd</code>, <code>r8i</code>, <code>r8i-flex</code>, <code>t2</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>trn1</code>, <code>trn1n</code>, <code>trn2</code>, <code>u-3tb1</code>, <code>u-6tb1</code>, <code>u7i-12tb</code>, <code>u7i-6tb</code>, <code>u7in-24tb</code>, <code>x1</code>, <code>x1e</code>, <code>x2gd</code>, <code>x2idn</code>, <code>x2iedn</code>, <code>x8g</code>, <code>z1d</code></p><h3 id="us-west-1"><a href="#us-west-1" class="headerlink" title="us-west-1"></a>us-west-1</h3><p>The following instance families are supported in region <code>us-west-1</code>.</p><p><code>c1</code>, <code>c3</code>, <code>c4</code>, <code>c5</code>, <code>c5a</code>, <code>c5d</code>, <code>c5n</code>, <code>c6a</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6in</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8g</code>, <code>c8gn</code>, <code>d2</code>, <code>g4dn</code>, <code>i2</code>, <code>i3</code>, <code>i3en</code>, <code>i4i</code>, <code>i7i</code>, <code>i7ie</code>, <code>inf1</code>, <code>m1</code>, <code>m2</code>, <code>m3</code>, <code>m4</code>, <code>m5</code>, <code>m5a</code>, <code>m5ad</code>, <code>m5d</code>, <code>m5zn</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m6idn</code>, <code>m6in</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8g</code>, <code>p5</code>, <code>p5en</code>, <code>r3</code>, <code>r4</code>, <code>r5</code>, <code>r5a</code>, <code>r5ad</code>, <code>r5d</code>, <code>r5n</code>, <code>r6a</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r8g</code>, <code>r8i</code>, <code>r8i-flex</code>, <code>t1</code>, <code>t2</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>x2idn</code>, <code>x2iedn</code>, <code>z1d</code></p><h3 id="us-west-2"><a href="#us-west-2" class="headerlink" title="us-west-2"></a>us-west-2</h3><p>The following instance families are supported in region <code>us-west-2</code>.</p><p><code>a1</code>, <code>c1</code>, <code>c3</code>, <code>c4</code>, <code>c5</code>, <code>c5a</code>, <code>c5ad</code>, <code>c5d</code>, <code>c5n</code>, <code>c6a</code>, <code>c6g</code>, <code>c6gd</code>, <code>c6gn</code>, <code>c6i</code>, <code>c6id</code>, <code>c6in</code>, <code>c7a</code>, <code>c7g</code>, <code>c7gd</code>, <code>c7gn</code>, <code>c7i</code>, <code>c7i-flex</code>, <code>c8a</code>, <code>c8g</code>, <code>c8gb</code>, <code>c8gd</code>, <code>c8gn</code>, <code>c8i</code>, <code>c8i-flex</code>, <code>d2</code>, <code>d3</code>, <code>d3en</code>, <code>dl1</code>, <code>dl2q</code>, <code>f1</code>, <code>f2</code>, <code>g4ad</code>, <code>g4dn</code>, <code>g5</code>, <code>g5g</code>, <code>g6</code>, <code>g6e</code>, <code>g6f</code>, <code>gr6</code>, <code>gr6f</code>, <code>h1</code>, <code>i2</code>, <code>i3</code>, <code>i3en</code>, <code>i4g</code>, <code>i4i</code>, <code>i7i</code>, <code>i7ie</code>, <code>i8g</code>, <code>i8ge</code>, <code>im4gn</code>, <code>inf1</code>, <code>inf2</code>, <code>is4gen</code>, <code>m1</code>, <code>m2</code>, <code>m3</code>, <code>m4</code>, <code>m5</code>, <code>m5a</code>, <code>m5ad</code>, <code>m5d</code>, <code>m5dn</code>, <code>m5n</code>, <code>m5zn</code>, <code>m6a</code>, <code>m6g</code>, <code>m6gd</code>, <code>m6i</code>, <code>m6id</code>, <code>m6idn</code>, <code>m6in</code>, <code>m7a</code>, <code>m7g</code>, <code>m7gd</code>, <code>m7i</code>, <code>m7i-flex</code>, <code>m8a</code>, <code>m8g</code>, <code>m8gd</code>, <code>m8i</code>, <code>m8i-flex</code>, <code>mac-m4</code>, <code>mac-m4pro</code>, <code>mac1</code>, <code>mac2</code>, <code>mac2-m1ultra</code>, <code>mac2-m2</code>, <code>mac2-m2pro</code>, <code>p3</code>, <code>p3dn</code>, <code>p4d</code>, <code>p4de</code>, <code>p5</code>, <code>p5e</code>, <code>p5en</code>, <code>p6-b200</code>, <code>p6-b300</code>, <code>r3</code>, <code>r4</code>, <code>r5</code>, <code>r5a</code>, <code>r5ad</code>, <code>r5b</code>, <code>r5d</code>, <code>r5dn</code>, <code>r5n</code>, <code>r6a</code>, <code>r6g</code>, <code>r6gd</code>, <code>r6i</code>, <code>r6id</code>, <code>r6idn</code>, <code>r6in</code>, <code>r7a</code>, <code>r7g</code>, <code>r7gd</code>, <code>r7i</code>, <code>r7iz</code>, <code>r8a</code>, <code>r8g</code>, <code>r8gb</code>, <code>r8gd</code>, <code>r8gn</code>, <code>r8i</code>, <code>r8i-flex</code>, <code>t1</code>, <code>t2</code>, <code>t3</code>, <code>t3a</code>, <code>t4g</code>, <code>trn1</code>, <code>trn1n</code>, <code>u-3tb1</code>, <code>u-6tb1</code>, <code>u7i-12tb</code>, <code>u7i-6tb</code>, <code>u7i-8tb</code>, <code>u7in-16tb</code>, <code>u7in-24tb</code>, <code>u7in-32tb</code>, <code>vt1</code>, <code>x1</code>, <code>x1e</code>, <code>x2gd</code>, <code>x2idn</code>, <code>x2iedn</code>, <code>x2iezn</code>, <code>x8aedz</code>, <code>x8g</code>, <code>z1d</code></p><h2 id="Supported-Regions-by-Instance-FAmily"><a href="#Supported-Regions-by-Instance-FAmily" class="headerlink" title="Supported Regions by Instance FAmily"></a>Supported Regions by Instance FAmily</h2><h3 id="a1"><a href="#a1" class="headerlink" title="a1"></a>a1</h3><p>The <code>a1</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="c1"><a href="#c1" class="headerlink" title="c1"></a>c1</h3><p>The <code>c1</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-west-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c3"><a href="#c3" class="headerlink" title="c3"></a>c3</h3><p>The <code>c3</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c4"><a href="#c4" class="headerlink" title="c4"></a>c4</h3><p>The <code>c4</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c5"><a href="#c5" class="headerlink" title="c5"></a>c5</h3><p>The <code>c5</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c5a"><a href="#c5a" class="headerlink" title="c5a"></a>c5a</h3><p>The <code>c5a</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>me-south-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c5ad"><a href="#c5ad" class="headerlink" title="c5ad"></a>c5ad</h3><p>The <code>c5ad</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>me-south-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="c5d"><a href="#c5d" class="headerlink" title="c5d"></a>c5d</h3><p>The <code>c5d</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c5n"><a href="#c5n" class="headerlink" title="c5n"></a>c5n</h3><p>The <code>c5n</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>me-south-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c6a"><a href="#c6a" class="headerlink" title="c6a"></a>c6a</h3><p>The <code>c6a</code> instance family is supported in the following regions.</p><p><code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c6g"><a href="#c6g" class="headerlink" title="c6g"></a>c6g</h3><p>The <code>c6g</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c6gd"><a href="#c6gd" class="headerlink" title="c6gd"></a>c6gd</h3><p>The <code>c6gd</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c6gn"><a href="#c6gn" class="headerlink" title="c6gn"></a>c6gn</h3><p>The <code>c6gn</code> instance family is supported in the following regions.</p><p><code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c6i"><a href="#c6i" class="headerlink" title="c6i"></a>c6i</h3><p>The <code>c6i</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c6id"><a href="#c6id" class="headerlink" title="c6id"></a>c6id</h3><p>The <code>c6id</code> instance family is supported in the following regions.</p><p><code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="c6in"><a href="#c6in" class="headerlink" title="c6in"></a>c6in</h3><p>The <code>c6in</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c7a"><a href="#c7a" class="headerlink" title="c7a"></a>c7a</h3><p>The <code>c7a</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="c7g"><a href="#c7g" class="headerlink" title="c7g"></a>c7g</h3><p>The <code>c7g</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c7gd"><a href="#c7gd" class="headerlink" title="c7gd"></a>c7gd</h3><p>The <code>c7gd</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-5</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c7gn"><a href="#c7gn" class="headerlink" title="c7gn"></a>c7gn</h3><p>The <code>c7gn</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="c7i"><a href="#c7i" class="headerlink" title="c7i"></a>c7i</h3><p>The <code>c7i</code> instance family is supported in the following regions.</p><p><code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>me-central-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c7i-flex"><a href="#c7i-flex" class="headerlink" title="c7i-flex"></a>c7i-flex</h3><p>The <code>c7i-flex</code> instance family is supported in the following regions.</p><p><code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>me-central-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c8a"><a href="#c8a" class="headerlink" title="c8a"></a>c8a</h3><p>The <code>c8a</code> instance family is supported in the following regions.</p><p><code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="c8g"><a href="#c8g" class="headerlink" title="c8g"></a>c8g</h3><p>The <code>c8g</code> instance family is supported in the following regions.</p><p><code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c8gb"><a href="#c8gb" class="headerlink" title="c8gb"></a>c8gb</h3><p>The <code>c8gb</code> instance family is supported in the following regions.</p><p><code>us-east-1</code>, <code>us-west-2</code></p><h3 id="c8gd"><a href="#c8gd" class="headerlink" title="c8gd"></a>c8gd</h3><p>The <code>c8gd</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-5</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="c8gn"><a href="#c8gn" class="headerlink" title="c8gn"></a>c8gn</h3><p>The <code>c8gn</code> instance family is supported in the following regions.</p><p><code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-5</code>, <code>ap-southeast-7</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>me-central-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="c8i"><a href="#c8i" class="headerlink" title="c8i"></a>c8i</h3><p>The <code>c8i</code> instance family is supported in the following regions.</p><p><code>eu-south-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="c8i-flex"><a href="#c8i-flex" class="headerlink" title="c8i-flex"></a>c8i-flex</h3><p>The <code>c8i-flex</code> instance family is supported in the following regions.</p><p><code>eu-south-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="d2"><a href="#d2" class="headerlink" title="d2"></a>d2</h3><p>The <code>d2</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>me-south-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="d3"><a href="#d3" class="headerlink" title="d3"></a>d3</h3><p>The <code>d3</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="d3en"><a href="#d3en" class="headerlink" title="d3en"></a>d3en</h3><p>The <code>d3en</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-west-2</code></p><h3 id="dl1"><a href="#dl1" class="headerlink" title="dl1"></a>dl1</h3><p>The <code>dl1</code> instance family is supported in the following regions.</p><p><code>us-east-1</code>, <code>us-west-2</code></p><h3 id="dl2q"><a href="#dl2q" class="headerlink" title="dl2q"></a>dl2q</h3><p>The <code>dl2q</code> instance family is supported in the following regions.</p><p><code>eu-central-1</code>, <code>us-west-2</code></p><h3 id="f1"><a href="#f1" class="headerlink" title="f1"></a>f1</h3><p>The <code>f1</code> instance family is supported in the following regions.</p><p><code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>us-east-1</code>, <code>us-west-2</code></p><h3 id="f2"><a href="#f2" class="headerlink" title="f2"></a>f2</h3><p>The <code>f2</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-west-2</code>, <code>us-east-1</code>, <code>us-west-2</code></p><h3 id="g4ad"><a href="#g4ad" class="headerlink" title="g4ad"></a>g4ad</h3><p>The <code>g4ad</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="g4dn"><a href="#g4dn" class="headerlink" title="g4dn"></a>g4dn</h3><p>The <code>g4dn</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>me-south-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="g5"><a href="#g5" class="headerlink" title="g5"></a>g5</h3><p>The <code>g5</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="g5g"><a href="#g5g" class="headerlink" title="g5g"></a>g5g</h3><p>The <code>g5g</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-3</code>, <code>eu-central-1</code>, <code>eu-south-2</code>, <code>us-east-1</code>, <code>us-west-2</code></p><h3 id="g6"><a href="#g6" class="headerlink" title="g6"></a>g6</h3><p>The <code>g6</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-5</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>me-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="g6e"><a href="#g6e" class="headerlink" title="g6e"></a>g6e</h3><p>The <code>g6e</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="g6f"><a href="#g6f" class="headerlink" title="g6f"></a>g6f</h3><p>The <code>g6f</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-2</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="gr6"><a href="#gr6" class="headerlink" title="gr6"></a>gr6</h3><p>The <code>gr6</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-5</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="gr6f"><a href="#gr6f" class="headerlink" title="gr6f"></a>gr6f</h3><p>The <code>gr6f</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-2</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="h1"><a href="#h1" class="headerlink" title="h1"></a>h1</h3><p>The <code>h1</code> instance family is supported in the following regions.</p><p><code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="hpc6a"><a href="#hpc6a" class="headerlink" title="hpc6a"></a>hpc6a</h3><p>The <code>hpc6a</code> instance family is supported in the following regions.</p><p><code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-north-1</code>, <code>us-east-2</code></p><h3 id="hpc6id"><a href="#hpc6id" class="headerlink" title="hpc6id"></a>hpc6id</h3><p>The <code>hpc6id</code> instance family is supported in the following regions.</p><p><code>eu-north-1</code>, <code>eu-west-3</code>, <code>us-east-2</code></p><h3 id="hpc7a"><a href="#hpc7a" class="headerlink" title="hpc7a"></a>hpc7a</h3><p>The <code>hpc7a</code> instance family is supported in the following regions.</p><p><code>eu-north-1</code>, <code>eu-west-1</code>, <code>eu-west-3</code>, <code>us-east-2</code></p><h3 id="hpc7g"><a href="#hpc7g" class="headerlink" title="hpc7g"></a>hpc7g</h3><p>The <code>hpc7g</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>eu-west-1</code>, <code>us-east-1</code></p><h3 id="i2"><a href="#i2" class="headerlink" title="i2"></a>i2</h3><p>The <code>i2</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="i3"><a href="#i3" class="headerlink" title="i3"></a>i3</h3><p>The <code>i3</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="i3en"><a href="#i3en" class="headerlink" title="i3en"></a>i3en</h3><p>The <code>i3en</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="i4g"><a href="#i4g" class="headerlink" title="i4g"></a>i4g</h3><p>The <code>i4g</code> instance family is supported in the following regions.</p><p><code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-west-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="i4i"><a href="#i4i" class="headerlink" title="i4i"></a>i4i</h3><p>The <code>i4i</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="i7i"><a href="#i7i" class="headerlink" title="i7i"></a>i7i</h3><p>The <code>i7i</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>me-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="i7ie"><a href="#i7ie" class="headerlink" title="i7ie"></a>i7ie</h3><p>The <code>i7ie</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-5</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="i8g"><a href="#i8g" class="headerlink" title="i8g"></a>i8g</h3><p>The <code>i8g</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="i8ge"><a href="#i8ge" class="headerlink" title="i8ge"></a>i8ge</h3><p>The <code>i8ge</code> instance family is supported in the following regions.</p><p><code>eu-central-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="im4gn"><a href="#im4gn" class="headerlink" title="im4gn"></a>im4gn</h3><p>The <code>im4gn</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="inf1"><a href="#inf1" class="headerlink" title="inf1"></a>inf1</h3><p>The <code>inf1</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>me-south-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="inf2"><a href="#inf2" class="headerlink" title="inf2"></a>inf2</h3><p>The <code>inf2</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="is4gen"><a href="#is4gen" class="headerlink" title="is4gen"></a>is4gen</h3><p>The <code>is4gen</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="m1"><a href="#m1" class="headerlink" title="m1"></a>m1</h3><p>The <code>m1</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-west-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m2"><a href="#m2" class="headerlink" title="m2"></a>m2</h3><p>The <code>m2</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-west-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m3"><a href="#m3" class="headerlink" title="m3"></a>m3</h3><p>The <code>m3</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m4"><a href="#m4" class="headerlink" title="m4"></a>m4</h3><p>The <code>m4</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m5"><a href="#m5" class="headerlink" title="m5"></a>m5</h3><p>The <code>m5</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m5a"><a href="#m5a" class="headerlink" title="m5a"></a>m5a</h3><p>The <code>m5a</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m5ad"><a href="#m5ad" class="headerlink" title="m5ad"></a>m5ad</h3><p>The <code>m5ad</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m5d"><a href="#m5d" class="headerlink" title="m5d"></a>m5d</h3><p>The <code>m5d</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m5dn"><a href="#m5dn" class="headerlink" title="m5dn"></a>m5dn</h3><p>The <code>m5dn</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="m5n"><a href="#m5n" class="headerlink" title="m5n"></a>m5n</h3><p>The <code>m5n</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="m5zn"><a href="#m5zn" class="headerlink" title="m5zn"></a>m5zn</h3><p>The <code>m5zn</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m6a"><a href="#m6a" class="headerlink" title="m6a"></a>m6a</h3><p>The <code>m6a</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m6g"><a href="#m6g" class="headerlink" title="m6g"></a>m6g</h3><p>The <code>m6g</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m6gd"><a href="#m6gd" class="headerlink" title="m6gd"></a>m6gd</h3><p>The <code>m6gd</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m6i"><a href="#m6i" class="headerlink" title="m6i"></a>m6i</h3><p>The <code>m6i</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m6id"><a href="#m6id" class="headerlink" title="m6id"></a>m6id</h3><p>The <code>m6id</code> instance family is supported in the following regions.</p><p><code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>il-central-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m6idn"><a href="#m6idn" class="headerlink" title="m6idn"></a>m6idn</h3><p>The <code>m6idn</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m6in"><a href="#m6in" class="headerlink" title="m6in"></a>m6in</h3><p>The <code>m6in</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m7a"><a href="#m7a" class="headerlink" title="m7a"></a>m7a</h3><p>The <code>m7a</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="m7g"><a href="#m7g" class="headerlink" title="m7g"></a>m7g</h3><p>The <code>m7g</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m7gd"><a href="#m7gd" class="headerlink" title="m7gd"></a>m7gd</h3><p>The <code>m7gd</code> instance family is supported in the following regions.</p><p><code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-3</code>, <code>me-central-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m7i"><a href="#m7i" class="headerlink" title="m7i"></a>m7i</h3><p>The <code>m7i</code> instance family is supported in the following regions.</p><p><code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>me-central-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m7i-flex"><a href="#m7i-flex" class="headerlink" title="m7i-flex"></a>m7i-flex</h3><p>The <code>m7i-flex</code> instance family is supported in the following regions.</p><p><code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m8a"><a href="#m8a" class="headerlink" title="m8a"></a>m8a</h3><p>The <code>m8a</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>eu-south-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="m8g"><a href="#m8g" class="headerlink" title="m8g"></a>m8g</h3><p>The <code>m8g</code> instance family is supported in the following regions.</p><p><code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>me-south-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="m8gd"><a href="#m8gd" class="headerlink" title="m8gd"></a>m8gd</h3><p>The <code>m8gd</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-5</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-south-2</code>, <code>eu-west-2</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="m8i"><a href="#m8i" class="headerlink" title="m8i"></a>m8i</h3><p>The <code>m8i</code> instance family is supported in the following regions.</p><p><code>ap-south-1</code>, <code>eu-south-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="m8i-flex"><a href="#m8i-flex" class="headerlink" title="m8i-flex"></a>m8i-flex</h3><p>The <code>m8i-flex</code> instance family is supported in the following regions.</p><p><code>ap-south-1</code>, <code>eu-south-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="mac-m4"><a href="#mac-m4" class="headerlink" title="mac-m4"></a>mac-m4</h3><p>The <code>mac-m4</code> instance family is supported in the following regions.</p><p><code>us-east-1</code>, <code>us-west-2</code></p><h3 id="mac-m4pro"><a href="#mac-m4pro" class="headerlink" title="mac-m4pro"></a>mac-m4pro</h3><p>The <code>mac-m4pro</code> instance family is supported in the following regions.</p><p><code>us-east-1</code>, <code>us-west-2</code></p><h3 id="mac1"><a href="#mac1" class="headerlink" title="mac1"></a>mac1</h3><p>The <code>mac1</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="mac2"><a href="#mac2" class="headerlink" title="mac2"></a>mac2</h3><p>The <code>mac2</code> instance family is supported in the following regions.</p><p><code>ap-southeast-1</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="mac2-m1ultra"><a href="#mac2-m1ultra" class="headerlink" title="mac2-m1ultra"></a>mac2-m1ultra</h3><p>The <code>mac2-m1ultra</code> instance family is supported in the following regions.</p><p><code>us-east-1</code>, <code>us-west-2</code></p><h3 id="mac2-m2"><a href="#mac2-m2" class="headerlink" title="mac2-m2"></a>mac2-m2</h3><p>The <code>mac2-m2</code> instance family is supported in the following regions.</p><p><code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="mac2-m2pro"><a href="#mac2-m2pro" class="headerlink" title="mac2-m2pro"></a>mac2-m2pro</h3><p>The <code>mac2-m2pro</code> instance family is supported in the following regions.</p><p><code>ap-southeast-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="p3"><a href="#p3" class="headerlink" title="p3"></a>p3</h3><p>The <code>p3</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="p3dn"><a href="#p3dn" class="headerlink" title="p3dn"></a>p3dn</h3><p>The <code>p3dn</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-west-2</code></p><h3 id="p4d"><a href="#p4d" class="headerlink" title="p4d"></a>p4d</h3><p>The <code>p4d</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="p4de"><a href="#p4de" class="headerlink" title="p4de"></a>p4de</h3><p>The <code>p4de</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-1</code>, <code>eu-central-1</code>, <code>il-central-1</code>, <code>us-east-1</code>, <code>us-west-2</code></p><h3 id="p5"><a href="#p5" class="headerlink" title="p5"></a>p5</h3><p>The <code>p5</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-south-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ca-central-1</code>, <code>eu-north-1</code>, <code>eu-west-2</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="p5e"><a href="#p5e" class="headerlink" title="p5e"></a>p5e</h3><p>The <code>p5e</code> instance family is supported in the following regions.</p><p><code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>eu-north-1</code>, <code>eu-west-2</code>, <code>sa-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="p5en"><a href="#p5en" class="headerlink" title="p5en"></a>p5en</h3><p>The <code>p5en</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-3</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="p6-b200"><a href="#p6-b200" class="headerlink" title="p6-b200"></a>p6-b200</h3><p>The <code>p6-b200</code> instance family is supported in the following regions.</p><p><code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="p6-b300"><a href="#p6-b300" class="headerlink" title="p6-b300"></a>p6-b300</h3><p>The <code>p6-b300</code> instance family is supported in the following regions.</p><p><code>us-west-2</code></p><h3 id="r3"><a href="#r3" class="headerlink" title="r3"></a>r3</h3><p>The <code>r3</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r4"><a href="#r4" class="headerlink" title="r4"></a>r4</h3><p>The <code>r4</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r5"><a href="#r5" class="headerlink" title="r5"></a>r5</h3><p>The <code>r5</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r5a"><a href="#r5a" class="headerlink" title="r5a"></a>r5a</h3><p>The <code>r5a</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r5ad"><a href="#r5ad" class="headerlink" title="r5ad"></a>r5ad</h3><p>The <code>r5ad</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r5b"><a href="#r5b" class="headerlink" title="r5b"></a>r5b</h3><p>The <code>r5b</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="r5d"><a href="#r5d" class="headerlink" title="r5d"></a>r5d</h3><p>The <code>r5d</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r5dn"><a href="#r5dn" class="headerlink" title="r5dn"></a>r5dn</h3><p>The <code>r5dn</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-3</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="r5n"><a href="#r5n" class="headerlink" title="r5n"></a>r5n</h3><p>The <code>r5n</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r6a"><a href="#r6a" class="headerlink" title="r6a"></a>r6a</h3><p>The <code>r6a</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r6g"><a href="#r6g" class="headerlink" title="r6g"></a>r6g</h3><p>The <code>r6g</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r6gd"><a href="#r6gd" class="headerlink" title="r6gd"></a>r6gd</h3><p>The <code>r6gd</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r6i"><a href="#r6i" class="headerlink" title="r6i"></a>r6i</h3><p>The <code>r6i</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r6id"><a href="#r6id" class="headerlink" title="r6id"></a>r6id</h3><p>The <code>r6id</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>il-central-1</code>, <code>mx-central-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="r6idn"><a href="#r6idn" class="headerlink" title="r6idn"></a>r6idn</h3><p>The <code>r6idn</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="r6in"><a href="#r6in" class="headerlink" title="r6in"></a>r6in</h3><p>The <code>r6in</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="r7a"><a href="#r7a" class="headerlink" title="r7a"></a>r7a</h3><p>The <code>r7a</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="r7g"><a href="#r7g" class="headerlink" title="r7g"></a>r7g</h3><p>The <code>r7g</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r7gd"><a href="#r7gd" class="headerlink" title="r7gd"></a>r7gd</h3><p>The <code>r7gd</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>mx-central-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r7i"><a href="#r7i" class="headerlink" title="r7i"></a>r7i</h3><p>The <code>r7i</code> instance family is supported in the following regions.</p><p><code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r7iz"><a href="#r7iz" class="headerlink" title="r7iz"></a>r7iz</h3><p>The <code>r7iz</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="r8a"><a href="#r8a" class="headerlink" title="r8a"></a>r8a</h3><p>The <code>r8a</code> instance family is supported in the following regions.</p><p><code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="r8g"><a href="#r8g" class="headerlink" title="r8g"></a>r8g</h3><p>The <code>r8g</code> instance family is supported in the following regions.</p><p><code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r8gb"><a href="#r8gb" class="headerlink" title="r8gb"></a>r8gb</h3><p>The <code>r8gb</code> instance family is supported in the following regions.</p><p><code>us-east-1</code>, <code>us-west-2</code></p><h3 id="r8gd"><a href="#r8gd" class="headerlink" title="r8gd"></a>r8gd</h3><p>The <code>r8gd</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-5</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="r8gn"><a href="#r8gn" class="headerlink" title="r8gn"></a>r8gn</h3><p>The <code>r8gn</code> instance family is supported in the following regions.</p><p><code>us-east-1</code>, <code>us-west-2</code></p><h3 id="r8i"><a href="#r8i" class="headerlink" title="r8i"></a>r8i</h3><p>The <code>r8i</code> instance family is supported in the following regions.</p><p><code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-5</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-south-2</code>, <code>eu-west-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="r8i-flex"><a href="#r8i-flex" class="headerlink" title="r8i-flex"></a>r8i-flex</h3><p>The <code>r8i-flex</code> instance family is supported in the following regions.</p><p><code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-5</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-south-2</code>, <code>eu-west-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="t1"><a href="#t1" class="headerlink" title="t1"></a>t1</h3><p>The <code>t1</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-west-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="t2"><a href="#t2" class="headerlink" title="t2"></a>t2</h3><p>The <code>t2</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="t3"><a href="#t3" class="headerlink" title="t3"></a>t3</h3><p>The <code>t3</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="t3a"><a href="#t3a" class="headerlink" title="t3a"></a>t3a</h3><p>The <code>t3a</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="t4g"><a href="#t4g" class="headerlink" title="t4g"></a>t4g</h3><p>The <code>t4g</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-east-2</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-6</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>ca-west-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>mx-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="trn1"><a href="#trn1" class="headerlink" title="trn1"></a>trn1</h3><p>The <code>trn1</code> instance family is supported in the following regions.</p><p><code>ap-south-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-4</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="trn1n"><a href="#trn1n" class="headerlink" title="trn1n"></a>trn1n</h3><p>The <code>trn1n</code> instance family is supported in the following regions.</p><p><code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="trn2"><a href="#trn2" class="headerlink" title="trn2"></a>trn2</h3><p>The <code>trn2</code> instance family is supported in the following regions.</p><p><code>ap-southeast-4</code>, <code>sa-east-1</code>, <code>us-east-2</code></p><h3 id="u-3tb1"><a href="#u-3tb1" class="headerlink" title="u-3tb1"></a>u-3tb1</h3><p>The <code>u-3tb1</code> instance family is supported in the following regions.</p><p><code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-south-1</code>, <code>eu-west-1</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="u-6tb1"><a href="#u-6tb1" class="headerlink" title="u-6tb1"></a>u-6tb1</h3><p>The <code>u-6tb1</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="u7i-12tb"><a href="#u7i-12tb" class="headerlink" title="u7i-12tb"></a>u7i-12tb</h3><p>The <code>u7i-12tb</code> instance family is supported in the following regions.</p><p><code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-west-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="u7i-6tb"><a href="#u7i-6tb" class="headerlink" title="u7i-6tb"></a>u7i-6tb</h3><p>The <code>u7i-6tb</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="u7i-8tb"><a href="#u7i-8tb" class="headerlink" title="u7i-8tb"></a>u7i-8tb</h3><p>The <code>u7i-8tb</code> instance family is supported in the following regions.</p><p><code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>us-east-1</code>, <code>us-west-2</code></p><h3 id="u7in-16tb"><a href="#u7in-16tb" class="headerlink" title="u7in-16tb"></a>u7in-16tb</h3><p>The <code>u7in-16tb</code> instance family is supported in the following regions.</p><p><code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-west-2</code></p><h3 id="u7in-24tb"><a href="#u7in-24tb" class="headerlink" title="u7in-24tb"></a>u7in-24tb</h3><p>The <code>u7in-24tb</code> instance family is supported in the following regions.</p><p><code>eu-central-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="u7in-32tb"><a href="#u7in-32tb" class="headerlink" title="u7in-32tb"></a>u7in-32tb</h3><p>The <code>u7in-32tb</code> instance family is supported in the following regions.</p><p><code>us-east-1</code>, <code>us-west-2</code></p><h3 id="vt1"><a href="#vt1" class="headerlink" title="vt1"></a>vt1</h3><p>The <code>vt1</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>eu-west-1</code>, <code>us-east-1</code>, <code>us-west-2</code></p><h3 id="x1"><a href="#x1" class="headerlink" title="x1"></a>x1</h3><p>The <code>x1</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-east-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="x1e"><a href="#x1e" class="headerlink" title="x1e"></a>x1e</h3><p>The <code>x1e</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="x2gd"><a href="#x2gd" class="headerlink" title="x2gd"></a>x2gd</h3><p>The <code>x2gd</code> instance family is supported in the following regions.</p><p><code>eu-west-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="x2idn"><a href="#x2idn" class="headerlink" title="x2idn"></a>x2idn</h3><p>The <code>x2idn</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-4</code>, <code>ap-southeast-5</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-central-2</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>il-central-1</code>, <code>me-central-1</code>, <code>me-south-1</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="x2iedn"><a href="#x2iedn" class="headerlink" title="x2iedn"></a>x2iedn</h3><p>The <code>x2iedn</code> instance family is supported in the following regions.</p><p><code>af-south-1</code>, <code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-northeast-3</code>, <code>ap-south-1</code>, <code>ap-south-2</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>ap-southeast-3</code>, <code>ap-southeast-5</code>, <code>ap-southeast-7</code>, <code>ca-central-1</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>eu-south-1</code>, <code>eu-south-2</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>eu-west-3</code>, <code>sa-east-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h3 id="x2iezn"><a href="#x2iezn" class="headerlink" title="x2iezn"></a>x2iezn</h3><p>The <code>x2iezn</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>eu-west-1</code>, <code>me-central-1</code>, <code>us-east-1</code>, <code>us-west-2</code></p><h3 id="x8aedz"><a href="#x8aedz" class="headerlink" title="x8aedz"></a>x8aedz</h3><p>The <code>x8aedz</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>us-west-2</code></p><h3 id="x8g"><a href="#x8g" class="headerlink" title="x8g"></a>x8g</h3><p>The <code>x8g</code> instance family is supported in the following regions.</p><p><code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-north-1</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-2</code></p><h3 id="z1d"><a href="#z1d" class="headerlink" title="z1d"></a>z1d</h3><p>The <code>z1d</code> instance family is supported in the following regions.</p><p><code>ap-northeast-1</code>, <code>ap-northeast-2</code>, <code>ap-south-1</code>, <code>ap-southeast-1</code>, <code>ap-southeast-2</code>, <code>eu-central-1</code>, <code>eu-west-1</code>, <code>eu-west-2</code>, <code>us-east-1</code>, <code>us-east-2</code>, <code>us-west-1</code>, <code>us-west-2</code></p><h2 id="Missing-Instance-Types"><a href="#Missing-Instance-Types" class="headerlink" title="Missing Instance Types"></a>Missing Instance Types</h2><p>Be warned, even when a region supports an instance family it might not support all the instance types. Here is a list with regions that do not support all instance types of the supported instance families.</p><table class="table table-striped table-responsive table-no-wrap"><thead><tr><th>Region</th><th>Missing Instance Types</th></tr></thead><tbody><tr><td>ap-south-1</td><td>trn1.2xlarge</td></tr><tr><td>me-central-1</td><td>c8gn.metal-24xl, c8gn.metal-48xl</td></tr><tr><td>il-central-1</td><td>i4i.24xlarge, i4i.12xlarge</td></tr><tr><td>ca-central-1</td><td>p5.4xlarge</td></tr><tr><td>ap-east-2</td><td>c6i.32xlarge, c6i.24xlarge, c6i.metal, m6i.24xlarge, m6i.metal, m6i.32xlarge, r6i.24xlarge, r6i.32xlarge, r6i.metal</td></tr><tr><td>eu-central-1</td><td>c8gn.metal-24xl, c8gn.metal-48xl, f1.16xlarge, i8g.48xlarge</td></tr><tr><td>us-west-1</td><td>c8gn.metal-24xl, c8gn.metal-48xl, p5.4xlarge</td></tr><tr><td>us-west-2</td><td>c8gn.metal-24xl, c8gn.metal-48xl, r8gn.metal-48xl, r8gn.metal-24xl, r8gb.metal-24xl, c8gb.metal-24xl</td></tr><tr><td>af-south-1</td><td>inf1.6xlarge, inf1.2xlarge</td></tr><tr><td>eu-north-1</td><td>c8gn.metal-24xl, c8gn.metal-48xl, p5.4xlarge</td></tr><tr><td>eu-west-3</td><td>c5d.12xlarge, c5d.24xlarge, c5d.metal</td></tr><tr><td>eu-west-2</td><td>f1.16xlarge</td></tr><tr><td>eu-west-1</td><td>m5n.metal</td></tr><tr><td>ap-northeast-3</td><td>m4.10xlarge</td></tr><tr><td>ap-northeast-2</td><td>inf2.48xlarge, inf2.8xlarge, inf2.24xlarge</td></tr><tr><td>me-south-1</td><td>c5d.12xlarge, c5d.24xlarge, c5d.metal</td></tr><tr><td>sa-east-1</td><td>trn2.48xlarge</td></tr><tr><td>ap-east-1</td><td>c5d.12xlarge, c5d.24xlarge, c5d.metal</td></tr><tr><td>ca-west-1</td><td>i4i.24xlarge, i4i.12xlarge</td></tr><tr><td>ap-southeast-1</td><td>c8gn.metal-24xl, c8gn.metal-48xl</td></tr><tr><td>ap-southeast-2</td><td>trn1.2xlarge, c8gn.metal-24xl, c8gn.metal-48xl</td></tr><tr><td>ap-southeast-3</td><td>m7i-flex.12xlarge, m7i-flex.16xlarge, g5g.2xlarge, g5g.16xlarge, g5g.8xlarge, g5g.4xlarge, g5g.xlarge</td></tr><tr><td>ap-southeast-4</td><td>trn2.48xlarge, trn1.2xlarge</td></tr><tr><td>ap-southeast-5</td><td>c8gn.metal-24xl, c8gn.metal-48xl</td></tr><tr><td>ap-southeast-6</td><td>c6i.32xlarge, c6i.24xlarge, c6i.metal, m6i.24xlarge, m6i.metal, m6i.32xlarge, r6i.24xlarge, r6i.32xlarge, r6i.metal</td></tr><tr><td>us-east-2</td><td>c8gn.metal-24xl, c8gn.metal-48xl, trn2.3xlarge</td></tr><tr><td>ap-southeast-7</td><td>c8gn.metal-24xl, c8gn.metal-48xl</td></tr></tbody></table><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Consider which instance types are available when choosing a region to deploy your workload. If you plan to roll out a workload to many regions in parallel, keep in mind that not all instance types are available in all regions.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to monitor container workloads running on ECS and Fargate?</title>
      <link>https://cloudonaut.io/how-to-monitor-container-workloads-running-on-ecs-and-fargate/</link>
      <description>
        <![CDATA[<p>How do you monitor a container workload running on ECS (Elastic Container Service) and Fargate with on-board resources? Here are the prio]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-monitor-container-workloads-running-on-ecs-and-fargate/</guid>
      <pubDate>Wed, 18 Oct 2023 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>How do you monitor a container workload running on ECS (Elastic Container Service) and Fargate with on-board resources? Here are the prioritized aspects when it comes to monitoring containers on AWS.</p><ol><li>Event-driven monitoring with EventBridge</li><li>Monitoring entry points like ALB, SQS, and Kinesis</li><li>Monitoring inter-service communication (Service Connect)</li><li>Observing container utilization</li><li>Collecting and analyzing container logs</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/10/monitoring-ecs-fargate-title@730w.webp 730w, /images/2023/10/monitoring-ecs-fargate-title@730w2x.webp 1460w, /images/2023/10/monitoring-ecs-fargate-title@610w.webp 610w, /images/2023/10/monitoring-ecs-fargate-title@610w2x.webp 1220w, /images/2023/10/monitoring-ecs-fargate-title@450w.webp 450w, /images/2023/10/monitoring-ecs-fargate-title@450w2x.webp 900w, /images/2023/10/monitoring-ecs-fargate-title@330w.webp 330w, /images/2023/10/monitoring-ecs-fargate-title@330w2x.webp 660w, /images/2023/10/monitoring-ecs-fargate-title@545w.webp 545w, /images/2023/10/monitoring-ecs-fargate-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/10/monitoring-ecs-fargate-title@730w.jpg 730w, /images/2023/10/monitoring-ecs-fargate-title@730w2x.jpg 1460w, /images/2023/10/monitoring-ecs-fargate-title@610w.jpg 610w, /images/2023/10/monitoring-ecs-fargate-title@610w2x.jpg 1220w, /images/2023/10/monitoring-ecs-fargate-title@450w.jpg 450w, /images/2023/10/monitoring-ecs-fargate-title@450w2x.jpg 900w, /images/2023/10/monitoring-ecs-fargate-title@330w.jpg 330w, /images/2023/10/monitoring-ecs-fargate-title@330w2x.jpg 660w, /images/2023/10/monitoring-ecs-fargate-title@545w.jpg 545w, /images/2023/10/monitoring-ecs-fargate-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/10/monitoring-ecs-fargate-title.jpg" alt="How to monitor container workloads running on ECS and Fargate?" title="How to monitor container workloads running on ECS and Fargate?"></picture></p><h2 id="Event-driven-monitoring-with-EventBridge"><a href="#Event-driven-monitoring-with-EventBridge" class="headerlink" title="Event-driven monitoring with EventBridge"></a>Event-driven monitoring with EventBridge</h2><p>Most importantly, ensure that you are not missing ECS failure events. Like many AWS services, ECS sends events to EventBridge. Monitoring those events by creating EventBridge rules is crucial to get informed about container-related issues.</p><p>For example, the following pattern filters events indicating that an ECS task stopped because one of the essential containers exited with an error.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;source&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span> </span><br><span class="line">    <span class="string">&quot;aws.ecs&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;detail-type&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;ECS Task State Change&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;detail&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;group&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">&#123;</span><span class="attr">&quot;anything-but&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="attr">&quot;prefix&quot;</span><span class="punctuation">:</span> <span class="string">&quot;service:&quot;</span><span class="punctuation">&#125;</span><span class="punctuation">&#125;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lastStatus&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;STOPPED&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;stopCode&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;EssentialContainerExited&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;containers&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;exitCode&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">&#123;</span><span class="attr">&quot;anything-but&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">&#125;</span><span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Besides that, an EventBridge rule with the following pattern will watch for failed ECS deployments.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;source&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span> </span><br><span class="line">    <span class="string">&quot;aws.ecs&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;detail-type&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;ECS Deployment State Change&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;detail&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;eventName&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;SERVICE_DEPLOYMENT_FAILED&quot;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>On top of that, use EventBridge rules to monitor tasks that are failing when starting, failed ECS service actions, or ECS tasks stopping due to Fargate Spot interruption.</p><blockquote><p><a href="https://marbot.io/" target="_blank" rel="noopener">marbot</a> - our AWS monitoring solution - deploys the necessary EventBridge rules to all your AWS accounts automatically and delivers alerts or notifications to Slack or Microsoft Teams.</p></blockquote><h2 id="Monitoring-entry-points-like-ALB-SQS-and-Kinesis"><a href="#Monitoring-entry-points-like-ALB-SQS-and-Kinesis" class="headerlink" title="Monitoring entry points like ALB, SQS, and Kinesis"></a>Monitoring entry points like ALB, SQS, and Kinesis</h2><p>Monitoring ECS events in real-time is a good start. But monitoring entry points like the ALB (Application Load Balancer) or SQS (Simple Queue Service) is essential.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/10/ecs-monitor-entrypoints@730w.webp 730w, /images/2023/10/ecs-monitor-entrypoints@730w2x.webp 1460w, /images/2023/10/ecs-monitor-entrypoints@610w.webp 610w, /images/2023/10/ecs-monitor-entrypoints@610w2x.webp 1220w, /images/2023/10/ecs-monitor-entrypoints@450w.webp 450w, /images/2023/10/ecs-monitor-entrypoints@450w2x.webp 900w, /images/2023/10/ecs-monitor-entrypoints@330w.webp 330w, /images/2023/10/ecs-monitor-entrypoints@330w2x.webp 660w, /images/2023/10/ecs-monitor-entrypoints@545w.webp 545w, /images/2023/10/ecs-monitor-entrypoints@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/10/ecs-monitor-entrypoints@730w.png 730w, /images/2023/10/ecs-monitor-entrypoints@730w2x.png 1460w, /images/2023/10/ecs-monitor-entrypoints@610w.png 610w, /images/2023/10/ecs-monitor-entrypoints@610w2x.png 1220w, /images/2023/10/ecs-monitor-entrypoints@450w.png 450w, /images/2023/10/ecs-monitor-entrypoints@450w2x.png 900w, /images/2023/10/ecs-monitor-entrypoints@330w.png 330w, /images/2023/10/ecs-monitor-entrypoints@330w2x.png 660w, /images/2023/10/ecs-monitor-entrypoints@545w.png 545w, /images/2023/10/ecs-monitor-entrypoints@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/10/ecs-monitor-entrypoints.png" alt="Monitoring entrypoints like ALB, SQS, and Kinesis" title="Monitoring entrypoints like ALB, SQS, and Kinesis"></picture></p><p>To do so, create CloudWatch alarms monitoring the following metrics.</p><ul><li>ALB<ul><li><code>HTTPCode_ELB_5XX_Count</code> to monitor 5XX errors sent from ALB to the client.</li><li><code>TargetResponseTime</code> to monitor the response latency.</li></ul></li><li>SQS<ul><li><code>ApproximateAgeOfOldestMessage</code> to monitor for messages that are not getting processed.</li><li><code>ApproximateNumberOfMessagesVisible</code> to monitor for messages piling up in the queue.</li></ul></li><li>Kinesis Data Stream<ul><li><code>GetRecords.IteratorAgeMilliseconds</code> to monitor for shards not getting processed.</li></ul></li></ul><p>Monitoring those metrics ensures that you get notified as soon as users experience issues but do not create too many notifications, causing alert fatigue.</p><h2 id="Monitoring-inter-service-communication-Service-Connect"><a href="#Monitoring-inter-service-communication-Service-Connect" class="headerlink" title="Monitoring inter-service communication (Service Connect)"></a>Monitoring inter-service communication (Service Connect)</h2><p>ECS has three different inter-service communication options: Service Discovery, Service Connect, and App Mesh.</p><p>With service discovery, there is no built-in mechanism for monitoring. Service Connect provides CloudWatch metrics. App Mesh uses Envoy under the hood, which provides metrics but does not integrate with CloudWatch by default.</p><p>If Service Connect is used for inter-service communication within an ECS cluster, monitor the following CloudWatch metrics.</p><ul><li><code>HTTPCode_Target_5XX_Count</code> The number of responses with 5XX error code.</li><li><code>TargetResponseTime</code> The time elapsed (milliseconds) after the request reached the Service Connect proxy in the target task until the proxy receives a response from the target container.</li></ul><h2 id="Observing-container-utilization"><a href="#Observing-container-utilization" class="headerlink" title="Observing container utilization"></a>Observing container utilization</h2><p>By default, ECS provides the following utilization metrics for an ECS service.</p><ul><li><code>CPUUtilization</code> The CPU utilization among all tasks belonging to the service.</li><li><code>MemoryUtilization</code> The memory utilization among all tasks belonging to the service.</li></ul><p>ECS records additional metrics after enabling <em>Container Insights</em> for a cluster. Among them are the following utilization metrics.</p><ul><li><code>EphemeralStorageReserved</code> and <code>EphemeralStorageUtilized</code> to get insights into the storage utilization (only available for Fargate tasks&#x2F;containers).</li><li><code>StorageReadBytes</code> and <code>StorageWriteBytes</code> to get insights into the storage throughput.</li><li><code>NetworkRxBytes</code> and <code>NetworkTxBytes</code> to get insights into the networking throughput.</li></ul><h2 id="Collecting-and-analyzing-container-logs"><a href="#Collecting-and-analyzing-container-logs" class="headerlink" title="Collecting and analyzing container logs"></a>Collecting and analyzing container logs</h2><p>By default, ECS ships log messages to CloudWatch Logs. Compared to other solutions, CloudWatch Logs comes with zero operations and maintenance effort. With CloudWatch Logs Insights, the capabilities to analyze log messages for debugging come close to other solutions like the Elastic stack.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/10/cloudwatch-logs-insights@730w.webp 730w, /images/2023/10/cloudwatch-logs-insights@730w2x.webp 1460w, /images/2023/10/cloudwatch-logs-insights@610w.webp 610w, /images/2023/10/cloudwatch-logs-insights@610w2x.webp 1220w, /images/2023/10/cloudwatch-logs-insights@450w.webp 450w, /images/2023/10/cloudwatch-logs-insights@450w2x.webp 900w, /images/2023/10/cloudwatch-logs-insights@330w.webp 330w, /images/2023/10/cloudwatch-logs-insights@330w2x.webp 660w, /images/2023/10/cloudwatch-logs-insights@545w.webp 545w, /images/2023/10/cloudwatch-logs-insights@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/10/cloudwatch-logs-insights@730w.png 730w, /images/2023/10/cloudwatch-logs-insights@730w2x.png 1460w, /images/2023/10/cloudwatch-logs-insights@610w.png 610w, /images/2023/10/cloudwatch-logs-insights@610w2x.png 1220w, /images/2023/10/cloudwatch-logs-insights@450w.png 450w, /images/2023/10/cloudwatch-logs-insights@450w2x.png 900w, /images/2023/10/cloudwatch-logs-insights@330w.png 330w, /images/2023/10/cloudwatch-logs-insights@330w2x.png 660w, /images/2023/10/cloudwatch-logs-insights@545w.png 545w, /images/2023/10/cloudwatch-logs-insights@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/10/cloudwatch-logs-insights.png" alt="Analyzing log messages with CloudWatch Logs Insights" title="Analyzing log messages with CloudWatch Logs Insights"></picture></p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>To avoid blind spots when monitoring container workloads running on ECS and Fargate, consider the following aspects:</p><ol><li>Event-driven monitoring with EventBridge</li><li>Monitoring entry points like ALB, SQS, and Kinesis</li><li>Monitoring inter-service communication (Service Connect)</li><li>Observing container utilization</li><li>Collecting and analyzing container logs</li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>Using DynamoDB Entity Store for cleaner TypeScript code</title>
      <link>https://cloudonaut.io/dynamodb-entity-store-cleaner-typescript-code/</link>
      <description>
        <![CDATA[<p>DynamoDB is a cloud-hosted NoSQL database from Amazon Web Services (AWS). DynamoDB is popular for two main reasons:</p>
<ul>
<li>It scale]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/dynamodb/">dynamodb</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/dynamodb-entity-store-cleaner-typescript-code/</guid>
      <pubDate>Thu, 12 Oct 2023 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>DynamoDB is a cloud-hosted NoSQL database from Amazon Web Services (AWS). DynamoDB is popular for two main reasons:</p><ul><li>It scales extremely effectively with little operational effort</li><li>Since it is a serverless service it is also cheap, simple, and quick to run for lower throughput applications</li></ul><p>I’ve worked with companies where the scaling behavior has been crucial, but most of the time what I like about DynamoDB is the second point: no complicated VPC networking, no clusters, no minimum monthly costs.</p><p>However there are also often at least two concerns with using DynamoDB:</p><ul><li>Getting DynamoDB table design correct is hard since it is very different from working with relational databases</li><li>There are no “standard” libraries that provide a high level programming interface</li></ul><p>There’s no getting around the first of these points - although Alex DeBrie is doing his best <a href="https://www.dynamodbbook.com/" target="_blank" rel="noopener">to educate the industry</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/10/dynamodb-entity-store-title@730w.webp 730w, /images/2023/10/dynamodb-entity-store-title@730w2x.webp 1460w, /images/2023/10/dynamodb-entity-store-title@610w.webp 610w, /images/2023/10/dynamodb-entity-store-title@610w2x.webp 1220w, /images/2023/10/dynamodb-entity-store-title@450w.webp 450w, /images/2023/10/dynamodb-entity-store-title@450w2x.webp 900w, /images/2023/10/dynamodb-entity-store-title@330w.webp 330w, /images/2023/10/dynamodb-entity-store-title@330w2x.webp 660w, /images/2023/10/dynamodb-entity-store-title@545w.webp 545w, /images/2023/10/dynamodb-entity-store-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/10/dynamodb-entity-store-title@730w.jpg 730w, /images/2023/10/dynamodb-entity-store-title@730w2x.jpg 1460w, /images/2023/10/dynamodb-entity-store-title@610w.jpg 610w, /images/2023/10/dynamodb-entity-store-title@610w2x.jpg 1220w, /images/2023/10/dynamodb-entity-store-title@450w.jpg 450w, /images/2023/10/dynamodb-entity-store-title@450w2x.jpg 900w, /images/2023/10/dynamodb-entity-store-title@330w.jpg 330w, /images/2023/10/dynamodb-entity-store-title@330w2x.jpg 660w, /images/2023/10/dynamodb-entity-store-title@545w.jpg 545w, /images/2023/10/dynamodb-entity-store-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/10/dynamodb-entity-store-title.jpg" alt="Cleaner TypeScript code using DynamoDB Entity Store" title="Cleaner TypeScript code using DynamoDB Entity Store"></picture></p><p>As for the second point, AWS does a great job at providing solid cloud services, with extensive APIs. However these APIs are fairly low-level. Even the AWS language-specific Software Development Kits - like the <a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/" target="_blank" rel="noopener">AWS JavaScript SDK</a> - only do a small amount of work to abstract the underlying HTTP API semantics for an application developer.</p><p>This is where my new library - <a href="https://github.com/symphoniacloud/dynamodb-entity-store" target="_blank" rel="noopener">DynamoDB Entity Store</a> - comes in. It provides a more developer-friendly interface to DynamoDB for TypeScript and JavaScript developers. </p><p>This article gives a brief introduction to DynamoDB Entity Store to show why you may want to consider it for your own projects. First though I show what life is like when you just use the AWS libraries.</p><h2 id="Using-the-standard-AWS-SDK-for-DynamoDB"><a href="#Using-the-standard-AWS-SDK-for-DynamoDB" class="headerlink" title="Using the standard AWS SDK for DynamoDB"></a>Using the standard AWS SDK for DynamoDB</h2><p>Let’s say you’re writing an application using TypeScript, and you’re using the AWS SDK V3 for your interactions with DynamoDB. And for the sake of this example let’s say you’re modeling animals on a farm.</p><p>Your business logic may have a domain type that looks like this:</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Sheep</span> &#123;</span><br><span class="line">  <span class="attr">breed</span>: <span class="built_in">string</span></span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span></span><br><span class="line">  <span class="attr">ageInYears</span>: <span class="built_in">number</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>And you may instantiate a particular object of this type like this:</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">shaun</span>: <span class="title class_">Sheep</span> = &#123;</span><br><span class="line">  <span class="attr">breed</span>: <span class="string">&#x27;merino&#x27;</span>,</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;shaun&#x27;</span>,</span><br><span class="line">  <span class="attr">ageInYears</span>: <span class="number">3</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Now you want to save this item to DynamoDB. If you’ve read Alex’s book I mentioned earlier you’ll know that you may want to use key <em>attributes</em> that are <strong>derived from</strong> the fields of the item you want to save, rather than being specific fields of that object. In other words to save shaun to DynamoDB using SDK V3 your code might look like this:</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">await</span> documentClient.<span class="title function_">send</span>(</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">PutCommand</span>(&#123;</span><br><span class="line">    <span class="title class_">TableName</span>: <span class="string">&#x27;AnimalsTable&#x27;</span>,</span><br><span class="line">    <span class="title class_">Item</span>: &#123;</span><br><span class="line">      <span class="attr">PK</span>: <span class="string">`SHEEP#BREED#<span class="subst">$&#123;shaun.breed&#125;</span>`</span>,</span><br><span class="line">      <span class="attr">SK</span>: <span class="string">`NAME#<span class="subst">$&#123;shaun.name&#125;</span>`</span>,</span><br><span class="line">      ...shaun</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p><code>documentClient</code> here is the interface to DynamoDB that’s been pre-configured for credentials, etc. <code>AnimalsTable</code> is the actual name in DynamoDB of the table you’re writing to. PK and SK are the Partition Key and Sort Key attributes of your table, which together form the unique key of each item. </p><p>Some time later you want to read shaun back from the database. To do that your code might look like this:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> shaunKey = &#123;</span><br><span class="line">  <span class="attr">breed</span>: <span class="string">&#x27;merino&#x27;</span>,</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&#x27;shaun&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> response = <span class="keyword">await</span> documentClient.<span class="title function_">send</span>(</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">GetCommand</span>(&#123;</span><br><span class="line">    <span class="title class_">TableName</span>: <span class="string">&#x27;AnimalsTable&#x27;</span>,</span><br><span class="line">    <span class="title class_">Key</span>: &#123;</span><br><span class="line">      <span class="attr">PK</span>: <span class="string">`SHEEP#BREED#<span class="subst">$&#123;shaunKey.breed&#125;</span>`</span>,</span><br><span class="line">      <span class="attr">SK</span>: <span class="string">`NAME#<span class="subst">$&#123;shaunKey.name&#125;</span>`</span>,</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">shaun</span>: <span class="title class_">Sheep</span> | <span class="literal">undefined</span> =</span><br><span class="line">  response.<span class="property">Item</span>?.<span class="property">breed</span> !== <span class="literal">undefined</span> &amp;&amp;</span><br><span class="line">  response.<span class="property">Item</span>?.<span class="property">name</span> !== <span class="literal">undefined</span> &amp;&amp;</span><br><span class="line">  response.<span class="property">Item</span>?.<span class="property">ageInYears</span> !== <span class="literal">undefined</span></span><br><span class="line">    ? &#123;</span><br><span class="line">        <span class="attr">breed</span>: response.<span class="property">Item</span>.<span class="property">breed</span>,</span><br><span class="line">        <span class="attr">name</span>: response.<span class="property">Item</span>.<span class="property">name</span>,</span><br><span class="line">        <span class="attr">ageInYears</span>: response.<span class="property">Item</span>.<span class="property">ageInYears</span></span><br><span class="line">      &#125;</span><br><span class="line">    : <span class="literal">undefined</span></span><br></pre></td></tr></table></figure><h2 id="The-problems-with-using-the-standard-AWS-SDK"><a href="#The-problems-with-using-the-standard-AWS-SDK" class="headerlink" title="The problems with using the standard AWS SDK"></a>The problems with using the standard AWS SDK</h2><p>The code above will work, but it has some aspects that aren’t great:</p><ul><li>Operational concerns - like the actual table’s name and key attribute names - are mixed up with domain concerns - like the fields of your Sheep type</li><li>The code for generating key attribute values from domain values is repeated, and is not clearly delineated</li><li>Messy code to perform validation, and to provide a correctly typed object for use by subsequent logic</li></ul><p>… and that’s just for a simple put and get operation. As soon as you start dealing with things like collection responses (from queries &amp; scans), condition &#x2F; update expressions, Time-to-Live fields, and more, then the mixing of DynamoDB concerns, domain logic concerns, and typing concerns, can make for spaghetti code. </p><p>Most projects of any size will end up dealing with this by introducing their own “helper code” to clean some of these issues up. That’s what I’ve done over the last few years on a few projects. But wouldn’t it be nice to be able to use a library that’s already written this helper code for you?</p><h2 id="How-DynamoDB-Entity-Store-helps"><a href="#How-DynamoDB-Entity-Store-helps" class="headerlink" title="How DynamoDB Entity Store helps"></a>How DynamoDB Entity Store helps</h2><p>There are at least a couple of other third party open source libraries that provide a higher abstraction for DynamoDB - <a href="https://github.com/sensedeep/dynamodb-onetable" target="_blank" rel="noopener">OneTable</a>, and <a href="https://github.com/jeremydaly/dynamodb-toolbox" target="_blank" rel="noopener">DynamoDB Toolbox</a>. When I was looking for something a couple of years ago neither of these called out to me, and so I wrote my own helper code.</p><p>I’ve evolved this code since then, and have wrapped it all up as a new library named <em><a href="https://github.com/symphoniacloud/dynamodb-entity-store" target="_blank" rel="noopener">DynamoDB Entity Store</a></em>. With this library my domain code for working with sheep looks as follows.</p><p>Writing my sheep record looks like this:</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">await</span> store.<span class="title function_">for</span>(<span class="variable constant_">SHEEP_ENTITY</span>).<span class="title function_">put</span>(shaun)</span><br></pre></td></tr></table></figure><p>And reading it looks like this:</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">shaun</span>: <span class="title class_">Sheep</span> | <span class="literal">undefined</span> = <span class="keyword">await</span> store</span><br><span class="line">  .<span class="title function_">for</span>(<span class="variable constant_">SHEEP_ENTITY</span>)</span><br><span class="line">  .<span class="title function_">getOrUndefined</span>(&#123; <span class="attr">breed</span>: <span class="string">&#x27;merino&#x27;</span>, <span class="attr">name</span>: <span class="string">&#x27;shaun&#x27;</span>&#125;)</span><br></pre></td></tr></table></figure><p>Isn’t that cleaner? Obviously there are a few things going on here.</p><p>First up, <code>store</code> is a wrapper around the SDK v3 Document Client, but it also includes operational configuration, like the table name and key attribute names, so that you don’t have to mess up domain code with those concerns.</p><p>Second - <code>SHEEP_ENTITY</code> is a specific object that defines the relationship between a domain object, and its persisted representation in DynamoDB. Keeping this code in a separate object means that you can perform several different types of operation on one Entity without having to repeat things like “how to calculate key attribute values”.</p><p>Your Entity code for <code>SHEEP_ENTITY</code> looks like this:</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="attr">SHEEP_ENTITY</span>: <span class="title class_">Entity</span>&lt;<span class="title class_">Sheep</span>, <span class="title class_">Pick</span>&lt;<span class="title class_">Sheep</span>, <span class="string">&#x27;breed&#x27;</span>&gt;, <span class="title class_">Pick</span>&lt;<span class="title class_">Sheep</span>, <span class="string">&#x27;name&#x27;</span>&gt;&gt; = &#123;</span><br><span class="line">  <span class="attr">type</span>: <span class="string">&#x27;sheep&#x27;</span>,</span><br><span class="line">  <span class="attr">parse</span>: <span class="title function_">typePredicateParser</span>(isSheep, <span class="string">&#x27;sheep&#x27;</span>),</span><br><span class="line">  <span class="title function_">pk</span>(<span class="params">&#123; breed &#125;: <span class="title class_">Pick</span>&lt;<span class="title class_">Sheep</span>, <span class="string">&#x27;breed&#x27;</span>&gt;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`SHEEP#BREED#<span class="subst">$&#123;breed&#125;</span>`</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title function_">sk</span>(<span class="params">&#123; name &#125;: <span class="title class_">Pick</span>&lt;<span class="title class_">Sheep</span>, <span class="string">&#x27;name&#x27;</span>&gt;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`NAME#<span class="subst">$&#123;name&#125;</span>`</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// TypeScript Type Predicate</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">isSheep</span>(<span class="params"><span class="attr">x</span>: <span class="title class_">DynamoDBValues</span></span>): x is <span class="title class_">Sheep</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> candidate = x <span class="keyword">as</span> <span class="title class_">Sheep</span></span><br><span class="line">  <span class="keyword">return</span> candidate.<span class="property">breed</span> !== <span class="literal">undefined</span> </span><br><span class="line">    &amp;&amp; candidate.<span class="property">name</span> !== <span class="literal">undefined</span> &amp;&amp; candidate.<span class="property">ageInYears</span> !== <span class="literal">undefined</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>There’s some verbosity here for all of the type-related code, but at least you can get all of that cruft in one place.</p><p>This example so far has just shown a simple put and get, but <em>DynamoDB Entity Store</em> helps with pretty much all the operations you can perform with DynamoDB - updates, queries, scans, batch commands, transactions, and more. Here’s an example of a query, using the same <code>SHEEP_ENTITY</code> object:</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> allMerinoSheep = <span class="keyword">await</span> store</span><br><span class="line">  .<span class="title function_">for</span>(<span class="variable constant_">SHEEP_ENTITY</span>)</span><br><span class="line">  .<span class="title function_">queryAllByPk</span>(&#123; <span class="attr">breed</span>: <span class="string">&#x27;merino&#x27;</span> &#125;)</span><br></pre></td></tr></table></figure><p>This code uses the same table configuration, key generation, validation, and parsing code as the put and get operations, allowing the specific elements of the query request to be kept clean.</p><p>While DynamoDB Entity Store defaults to a “single table design”, you can also use multiple tables, and if you’re using domain-object fields for key attributes - rather than generated attributes - then that’s supported too.</p><p>To see more examples checkout <a href="https://github.com/symphoniacloud/dynamodb-entity-store/blob/main/README.md" target="_blank" rel="noopener">the project’s README</a>, or the <a href="https://github.com/symphoniacloud/dynamodb-entity-store/blob/main/documentation/README.md" target="_blank" rel="noopener">documentation</a>.</p><p>I’ve written DynamoDB Entity Store because it fits my style of coding, hopefully it might work for some of you too. If it does, or doesn’t (!), then please let me know at <a href="mailto:&#109;&#x69;&#107;&#101;&#64;&#x73;&#121;&#109;&#112;&#x68;&#x6f;&#110;&#105;&#97;&#46;&#x69;&#x6f;">mike@symphonia.io</a>, or in the GitHub project issues.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>The Lambda monitoring blind spot</title>
      <link>https://cloudonaut.io/aws-lambda-monitoring-blind-spot/</link>
      <description>
        <![CDATA[<p>After a customer complained that a feature of <a href="https://marbot.io/" target="_blank" rel="noopener">marbot, our monitoring solution]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-lambda-monitoring-blind-spot/</guid>
      <pubDate>Wed, 04 Oct 2023 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>After a customer complained that a feature of <a href="https://marbot.io/" target="_blank" rel="noopener">marbot, our monitoring solution for AWS</a> was not working as expected, I started debugging the issue. First, I checked the CloudWatch alarms we use to monitor all Lambda functions. All CloudWatch alarms were in status <code>OK,</code> and we also had not received any alerts via Slack. Next, I analyzed the CloudWatch logs. To my surprise, I found out that one of our Lambda functions failed from time to time. I was shocked about the blind spot in our monitoring configuration.</p><p>Are you using CloudWatch alarms for Lambda function monitoring as well? Read on to ensure you avoid making the same mistake we did.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/10/blind-spot-title@730w.webp 730w, /images/2023/10/blind-spot-title@730w2x.webp 1460w, /images/2023/10/blind-spot-title@610w.webp 610w, /images/2023/10/blind-spot-title@610w2x.webp 1220w, /images/2023/10/blind-spot-title@450w.webp 450w, /images/2023/10/blind-spot-title@450w2x.webp 900w, /images/2023/10/blind-spot-title@330w.webp 330w, /images/2023/10/blind-spot-title@330w2x.webp 660w, /images/2023/10/blind-spot-title@545w.webp 545w, /images/2023/10/blind-spot-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/10/blind-spot-title@730w.jpg 730w, /images/2023/10/blind-spot-title@730w2x.jpg 1460w, /images/2023/10/blind-spot-title@610w.jpg 610w, /images/2023/10/blind-spot-title@610w2x.jpg 1220w, /images/2023/10/blind-spot-title@450w.jpg 450w, /images/2023/10/blind-spot-title@450w2x.jpg 900w, /images/2023/10/blind-spot-title@330w.jpg 330w, /images/2023/10/blind-spot-title@330w2x.jpg 660w, /images/2023/10/blind-spot-title@545w.jpg 545w, /images/2023/10/blind-spot-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/10/blind-spot-title.jpg" alt="The Lambda monitoring blind spot" title="The Lambda monitoring blind spot"></picture></p><h2 id="Problem"><a href="#Problem" class="headerlink" title="Problem"></a>Problem</h2><p>For some reason, the CloudWatch alarms we configured to get notified about failed executions of Lambda functions did not work correctly. Here is an excerpt from our CloudFormation code to configure CloudWatch alarms.</p><p>The <code>ErrorsAlarm</code> monitors the <code>Error</code> metric of the <code>LambdaFunction</code>. As soon as the number of errors within the past 5 minutes exceeds 0, the alarm flips to state <code>ALARM</code>.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">LambdaFunction:</span></span><br><span class="line"> <span class="attr">Type:</span> <span class="string">&#x27;AWS::Lambda::Function&#x27;</span></span><br><span class="line"> <span class="attr">Properties:</span></span><br><span class="line"> <span class="attr">Architectures:</span> [<span class="string">&#x27;arm64&#x27;</span>]</span><br><span class="line"> <span class="attr">Handler:</span> <span class="string">&#x27;index.handler&#x27;</span></span><br><span class="line"> <span class="attr">Runtime:</span> <span class="string">&#x27;nodejs18.x&#x27;</span></span><br><span class="line"> <span class="attr">MemorySize:</span> <span class="number">1536</span></span><br><span class="line"> <span class="attr">Timeout:</span> <span class="number">900</span> <span class="comment"># 15 min</span></span><br><span class="line"> <span class="comment"># ...</span></span><br><span class="line"><span class="attr">ErrorsAlarm:</span></span><br><span class="line"> <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line"> <span class="attr">Properties:</span></span><br><span class="line"> <span class="attr">AlarmDescription:</span> <span class="string">&#x27;An error occurred while executing the Lambda function.&#x27;</span></span><br><span class="line"> <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/Lambda&#x27;</span></span><br><span class="line"> <span class="attr">MetricName:</span> <span class="string">Errors</span></span><br><span class="line"> <span class="attr">Dimensions:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">FunctionName</span></span><br><span class="line"> <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">LambdaFunction</span></span><br><span class="line"> <span class="attr">Statistic:</span> <span class="string">Sum</span></span><br><span class="line"> <span class="attr">Period:</span> <span class="number">300</span> <span class="comment"># 5 min</span></span><br><span class="line"> <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line"> <span class="attr">Threshold:</span> <span class="number">0</span></span><br><span class="line"> <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br></pre></td></tr></table></figure><p>Sounds fine. Here is the catch.</p><blockquote><p>“The timestamp on a metric reflects when the function was invoked. Depending on the duration of the invocation, this can be several minutes before the metric is emitted. For example, if your function has a 10-minute timeout, then look more than 10 minutes in the past for accurate metrics.” (see <a href="https://docs.aws.amazon.com/lambda/latest/dg/monitoring-metrics.html#" target="_blank" rel="noopener">Working with Lambda function metrics</a>)</p></blockquote><p>The following figure illustrates that when Lambda writes metric data, it uses the timestamp of the function invocation (start).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/10/cloudwatch-alarm-lambda-function@730w.webp 730w, /images/2023/10/cloudwatch-alarm-lambda-function@730w2x.webp 1460w, /images/2023/10/cloudwatch-alarm-lambda-function@610w.webp 610w, /images/2023/10/cloudwatch-alarm-lambda-function@610w2x.webp 1220w, /images/2023/10/cloudwatch-alarm-lambda-function@450w.webp 450w, /images/2023/10/cloudwatch-alarm-lambda-function@450w2x.webp 900w, /images/2023/10/cloudwatch-alarm-lambda-function@330w.webp 330w, /images/2023/10/cloudwatch-alarm-lambda-function@330w2x.webp 660w, /images/2023/10/cloudwatch-alarm-lambda-function@545w.webp 545w, /images/2023/10/cloudwatch-alarm-lambda-function@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/10/cloudwatch-alarm-lambda-function@730w.png 730w, /images/2023/10/cloudwatch-alarm-lambda-function@730w2x.png 1460w, /images/2023/10/cloudwatch-alarm-lambda-function@610w.png 610w, /images/2023/10/cloudwatch-alarm-lambda-function@610w2x.png 1220w, /images/2023/10/cloudwatch-alarm-lambda-function@450w.png 450w, /images/2023/10/cloudwatch-alarm-lambda-function@450w2x.png 900w, /images/2023/10/cloudwatch-alarm-lambda-function@330w.png 330w, /images/2023/10/cloudwatch-alarm-lambda-function@330w2x.png 660w, /images/2023/10/cloudwatch-alarm-lambda-function@545w.png 545w, /images/2023/10/cloudwatch-alarm-lambda-function@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/10/cloudwatch-alarm-lambda-function.png" alt="CloudWatch alarm monitoring a Lambda function: CloudWatch Evaluation Period must cover at least the Function Timeout Period" title="CloudWatch alarm monitoring a Lambda function: CloudWatch Evaluation Period must cover at least the Function Timeout Period"></picture></p><p>In our case, we set the timeout of the <code>LambdaFunction</code> to a maximum of 15 minutes. But the CloudWatch alarm looks back only 5 minutes. As the invocation timestamp is used when inserting a metric point into the <code>Errors</code> metric, the CloudWatch alarm misses errors from invocations longer than 5 minutes.</p><h2 id="Solution"><a href="#Solution" class="headerlink" title="Solution"></a>Solution</h2><p>To avoid blind spots when monitoring Lambda functions with CloudWatch alarms, stick to the following rule.</p><figure class="highlight ada"><table><tr><td class="code"><pre><span class="line">CloudWatch Evaluation Period &gt; Lambda <span class="keyword">Function</span> <span class="title">Timeout</span></span><br></pre></td></tr></table></figure><p>Back to our case, we increased the evaluation period of the <code>ErrorsAlarm</code> to 20 minutes by increasing the evaluation periods from 1 to 4.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">LambdaFunction:</span></span><br><span class="line"> <span class="attr">Type:</span> <span class="string">&#x27;AWS::Lambda::Function&#x27;</span></span><br><span class="line"> <span class="attr">Properties:</span></span><br><span class="line"> <span class="attr">Architectures:</span> [<span class="string">&#x27;arm64&#x27;</span>]</span><br><span class="line"> <span class="attr">Handler:</span> <span class="string">&#x27;index.handler&#x27;</span></span><br><span class="line"> <span class="attr">Runtime:</span> <span class="string">&#x27;nodejs18.x&#x27;</span></span><br><span class="line"> <span class="attr">MemorySize:</span> <span class="number">1536</span></span><br><span class="line"> <span class="attr">Timeout:</span> <span class="number">900</span> <span class="comment"># 15 min</span></span><br><span class="line"> <span class="comment"># ...</span></span><br><span class="line"><span class="attr">ErrorsAlarm:</span></span><br><span class="line"> <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line"> <span class="attr">Properties:</span></span><br><span class="line"> <span class="attr">AlarmDescription:</span> <span class="string">&#x27;An error occurred while executing the Lambda function.&#x27;</span></span><br><span class="line"> <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/Lambda&#x27;</span></span><br><span class="line"> <span class="attr">MetricName:</span> <span class="string">Errors</span></span><br><span class="line"> <span class="attr">Dimensions:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">FunctionName</span></span><br><span class="line"> <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">LambdaFunction</span></span><br><span class="line"> <span class="attr">Statistic:</span> <span class="string">Sum</span></span><br><span class="line"> <span class="attr">Period:</span> <span class="number">300</span> <span class="comment"># 5 min</span></span><br><span class="line"> <span class="attr">EvaluationPeriods:</span> <span class="number">4</span> <span class="comment"># 4 x 5 min = 20 minutes</span></span><br><span class="line"> <span class="attr">Threshold:</span> <span class="number">0</span></span><br><span class="line"> <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br></pre></td></tr></table></figure><p>So, check the configuration of your CloudWatch alarms monitoring Lambda functions!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>A future-proof Terraform provider definition</title>
      <link>https://cloudonaut.io/future-proof-terraform-provider-definition/</link>
      <description>
        <![CDATA[<p>When defining the version of a Terraform provider, do not use <code>&gt;</code> or <code>=&gt;</code> conditions. You will run into troub]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/terraform/">terraform</category>
      <guid isPermaLink="true">https://cloudonaut.io/future-proof-terraform-provider-definition/</guid>
      <pubDate>Wed, 27 Sep 2023 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>When defining the version of a Terraform provider, do not use <code>&gt;</code> or <code>=&gt;</code> conditions. You will run into troubles caused by breaking changes with the next major release. Instead, lock the major version of the Terraform provider by using a <code>~&gt;</code> condition.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/09/right-way-title@730w.webp 730w, /images/2023/09/right-way-title@730w2x.webp 1460w, /images/2023/09/right-way-title@610w.webp 610w, /images/2023/09/right-way-title@610w2x.webp 1220w, /images/2023/09/right-way-title@450w.webp 450w, /images/2023/09/right-way-title@450w2x.webp 900w, /images/2023/09/right-way-title@330w.webp 330w, /images/2023/09/right-way-title@330w2x.webp 660w, /images/2023/09/right-way-title@545w.webp 545w, /images/2023/09/right-way-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/09/right-way-title@730w.jpg 730w, /images/2023/09/right-way-title@730w2x.jpg 1460w, /images/2023/09/right-way-title@610w.jpg 610w, /images/2023/09/right-way-title@610w2x.jpg 1220w, /images/2023/09/right-way-title@450w.jpg 450w, /images/2023/09/right-way-title@450w2x.jpg 900w, /images/2023/09/right-way-title@330w.jpg 330w, /images/2023/09/right-way-title@330w2x.jpg 660w, /images/2023/09/right-way-title@545w.jpg 545w, /images/2023/09/right-way-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/09/right-way-title.jpg" alt="How to define the Terraform provider version in the right way" title="How to define the Terraform provider version in the right way"></picture></p><p>But let’s start at the beginning.</p><h2 id="Problem"><a href="#Problem" class="headerlink" title="Problem"></a>Problem</h2><p>When running <code>terraform apply</code> to deploy a small change to a code base that I had not touched for a while, I ran into the following error.</p><figure class="highlight haxe"><table><tr><td class="code"><pre><span class="line">An argument named <span class="string">&quot;enable_classiclink&quot;</span> <span class="keyword">is</span> not expected <span class="keyword">here</span>.</span><br></pre></td></tr></table></figure><p>While debugging the issue, I learned a lot about Terraform version constraints that you should also be aware of to avoid unpleasant surprises.</p><p>What happened?</p><p>I wrote the following code a few years ago to create a VPC and some other resources.</p><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="section">terraform</span> &#123;</span><br><span class="line">  <span class="section">required_providers</span> &#123;</span><br><span class="line">    <span class="attribute">aws</span> = &#123;</span><br><span class="line">      <span class="attribute">source</span> = <span class="string">&quot;hashicorp/aws&quot;</span></span><br><span class="line">      version = <span class="string">&quot;3.76.0&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">provider <span class="string">&quot;aws&quot;</span> &#123;</span><br><span class="line">  <span class="attribute">region</span> = <span class="string">&quot;eu-west-1&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">module <span class="string">&quot;vpc&quot;</span> &#123;</span><br><span class="line">  <span class="attribute">source</span> = <span class="string">&quot;terraform-aws-modules/vpc/aws&quot;</span></span><br><span class="line">  version = <span class="string">&quot;3.19.0&quot;</span></span><br><span class="line"></span><br><span class="line">  name = <span class="string">&quot;demo&quot;</span></span><br><span class="line">  cidr = <span class="string">&quot;10.0.0.0/16&quot;</span></span><br><span class="line"></span><br><span class="line">  azs = [<span class="string">&quot;eu-west-1a&quot;</span>, <span class="string">&quot;eu-west-1b&quot;</span>, <span class="string">&quot;eu-west-1c&quot;</span>]</span><br><span class="line">  private_subnets = [<span class="string">&quot;10.0.1.0/24&quot;</span>, <span class="string">&quot;10.0.2.0/24&quot;</span>, <span class="string">&quot;10.0.3.0/24&quot;</span>]</span><br><span class="line">  public_subnets = [<span class="string">&quot;10.0.101.0/24&quot;</span>, <span class="string">&quot;10.0.102.0/24&quot;</span>, <span class="string">&quot;10.0.103.0/24&quot;</span>]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># ...</span></span><br></pre></td></tr></table></figure><p>But, after updating to the latest AWS provider version, I was stuck with an <code>An argument named &quot;enable_classiclink&quot; is not expected here.</code> error.</p><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="section">terraform</span> &#123;</span><br><span class="line">  <span class="section">required_providers</span> &#123;</span><br><span class="line">    <span class="attribute">aws</span> = &#123;</span><br><span class="line">      <span class="attribute">source</span> = <span class="string">&quot;hashicorp/aws&quot;</span></span><br><span class="line">      version = <span class="string">&quot;5.9.0&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">provider <span class="string">&quot;aws&quot;</span> &#123;</span><br><span class="line">  <span class="attribute">region</span> = <span class="string">&quot;eu-west-1&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">module <span class="string">&quot;vpc&quot;</span> &#123;</span><br><span class="line">  <span class="attribute">source</span> = <span class="string">&quot;terraform-aws-modules/vpc/aws&quot;</span></span><br><span class="line">  version = <span class="string">&quot;3.19.0&quot;</span></span><br><span class="line"></span><br><span class="line">  name = <span class="string">&quot;demo&quot;</span></span><br><span class="line">  cidr = <span class="string">&quot;10.0.0.0/16&quot;</span></span><br><span class="line"></span><br><span class="line">  azs = [<span class="string">&quot;eu-west-1a&quot;</span>, <span class="string">&quot;eu-west-1b&quot;</span>, <span class="string">&quot;eu-west-1c&quot;</span>]</span><br><span class="line">  private_subnets = [<span class="string">&quot;10.0.1.0/24&quot;</span>, <span class="string">&quot;10.0.2.0/24&quot;</span>, <span class="string">&quot;10.0.3.0/24&quot;</span>]</span><br><span class="line">  public_subnets = [<span class="string">&quot;10.0.101.0/24&quot;</span>, <span class="string">&quot;10.0.102.0/24&quot;</span>, <span class="string">&quot;10.0.103.0/24&quot;</span>]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># ...</span></span><br></pre></td></tr></table></figure><p>After a while, I found out that the major release <a href="https://github.com/hashicorp/terraform-provider-aws/releases/tag/v5.0.0" target="_blank" rel="noopener">5.0.0</a> of the AWS provider introduced a few breaking changes. The change log includes the following entry.</p><figure class="highlight livecodeserver"><table><tr><td class="code"><pre><span class="line">resource/aws_default_vpc: With <span class="keyword">the</span> retirement <span class="keyword">of</span> EC2-Classic <span class="keyword">the</span> enable_classiclink <span class="keyword">and</span> enable_classiclink_dns_support attributes have been removed (<span class="comment">#30966)</span></span><br></pre></td></tr></table></figure><p>But, the <code>terraform-aws-modules/vpc/aws</code> module in version <code>3.19.0</code> still used the <code>enable_classiclink_dns_support</code> attribute, which caused the error.</p><p>It turns out that the <a href="https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest" target="_blank" rel="noopener">terraform-aws-modules&#x2F;vpc&#x2F;aws</a> module follows the major release cycle of the AWS provider. However, the module does not enforce using an AWS provider with the supported major version.</p><p>Here is how the <code>terraform-aws-modules/vpc/aws</code> module specifies the AWS provider version.</p><figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">terraform &#123;</span><br><span class="line">  required_version <span class="operator">=</span> <span class="string">&quot;&gt;= 0.13.1&quot;</span></span><br><span class="line"></span><br><span class="line">  required_providers &#123;</span><br><span class="line">    aws <span class="operator">=</span> &#123;</span><br><span class="line">      source <span class="operator">=</span> <span class="string">&quot;hashicorp/aws&quot;</span></span><br><span class="line">      version <span class="operator">=</span> <span class="string">&quot;&gt;= 3.73&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The version condition states that the module works with any AWS provider version greater or equal to <code>3.73</code>. But that is not the case because each major version of the AWS provider introduces breaking changes.</p><h2 id="Solution"><a href="#Solution" class="headerlink" title="Solution"></a>Solution</h2><p>When defining the required version, use the <code>~&gt;</code> condition instead, which the Terraform documentation describes as follows:</p><p>“Allows only the rightmost version component to increment. For example, to allow new patch releases within a specific minor release, use the full version number: ~&gt; 1.0.4 will allow installation of 1.0.5 and 1.0.10 but not 1.1.0. This is usually called the pessimistic constraint operator.” (see <a href="https://developer.hashicorp.com/terraform/language/expressions/version-constraints" target="_blank" rel="noopener">Terraform: Version Constraints</a>)</p><p>Back to the example from above, the <code>terraform-aws-modules/vpc/aws</code> module should specify the AWS provider as follows.</p><figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">terraform &#123;</span><br><span class="line">  required_version <span class="operator">=</span> <span class="string">&quot;&gt;= 0.13.1&quot;</span></span><br><span class="line"></span><br><span class="line">  required_providers &#123;</span><br><span class="line">    aws <span class="operator">=</span> &#123;</span><br><span class="line">      source <span class="operator">=</span> <span class="string">&quot;hashicorp/aws&quot;</span></span><br><span class="line">      version <span class="operator">=</span> <span class="string">&quot;~&gt; 3.0&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Doing so would pick the latest version of the AWS provider with major version 3. Newer major versions (4 or 5) with potential breaking changes are not supported.</p><p>Note that this approach requires all parts of your Terraform configuration -including all the modules you use- to use the same major provider versions. Alternatively, you could use tools like <a href="https://terragrunt.gruntwork.io/" target="_blank" rel="noopener">terragrunt</a> that execute modules separately.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Migrating to AWS JavaScript SDK v3: Lessons Learned</title>
      <link>https://cloudonaut.io/migrating-to-aws-sdk-js-v3-lessons-learned/</link>
      <description>Learn about the stumbling blocks when migrating from v2 to v3 of the AWS JavaScript SDK.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/sdk/">sdk</category>
      <guid isPermaLink="true">https://cloudonaut.io/migrating-to-aws-sdk-js-v3-lessons-learned/</guid>
      <pubDate>Wed, 20 Sep 2023 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>There’s work coming your way! <a href="https://nodejs.org/en/blog/announcements/nodejs16-eol" target="_blank" rel="noopener">Node.js 16 reached end-of-life on September 11th, 2023</a>. Also, the <a href="https://aws.amazon.com/blogs/compute/node-js-18-x-runtime-now-available-in-aws-lambda/" target="_blank" rel="noopener">AWS Lambda runtime environment for Node.js 18 upgraded to v3 of the AWS SDK for JavaScript</a>. So to upgrade Lambda functions from Node.js 16 to 18, you have to migrate to AWS JavaScript SDK to <code>v3</code> as well. Unfortunately, <code>v3</code> is not backward compatible with <code>v2</code>. In the following, I will share what I stumbled upon while upgrading many Lambda functions to <code>v3</code>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/09/aws-js-sdk-v3-title@730w.webp 730w, /images/2023/09/aws-js-sdk-v3-title@730w2x.webp 1460w, /images/2023/09/aws-js-sdk-v3-title@610w.webp 610w, /images/2023/09/aws-js-sdk-v3-title@610w2x.webp 1220w, /images/2023/09/aws-js-sdk-v3-title@450w.webp 450w, /images/2023/09/aws-js-sdk-v3-title@450w2x.webp 900w, /images/2023/09/aws-js-sdk-v3-title@330w.webp 330w, /images/2023/09/aws-js-sdk-v3-title@330w2x.webp 660w, /images/2023/09/aws-js-sdk-v3-title@545w.webp 545w, /images/2023/09/aws-js-sdk-v3-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/09/aws-js-sdk-v3-title@730w.jpg 730w, /images/2023/09/aws-js-sdk-v3-title@730w2x.jpg 1460w, /images/2023/09/aws-js-sdk-v3-title@610w.jpg 610w, /images/2023/09/aws-js-sdk-v3-title@610w2x.jpg 1220w, /images/2023/09/aws-js-sdk-v3-title@450w.jpg 450w, /images/2023/09/aws-js-sdk-v3-title@450w2x.jpg 900w, /images/2023/09/aws-js-sdk-v3-title@330w.jpg 330w, /images/2023/09/aws-js-sdk-v3-title@330w2x.jpg 660w, /images/2023/09/aws-js-sdk-v3-title@545w.jpg 545w, /images/2023/09/aws-js-sdk-v3-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/09/aws-js-sdk-v3-title.jpg" alt="Migrating to AWS JavaScript SDK v3: Lessons Learned" title="Migrating to AWS JavaScript SDK v3: Lessons Learned"></picture></p><p>When upgrading the AWS JavaScript SDK from <code>v2</code> to <code>v3</code>, you should bookmark the following pages:</p><ul><li><a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/introduction/" target="_blank" rel="noopener">API Reference</a></li><li><a href="https://github.com/aws/aws-sdk-js-v3/blob/main/UPGRADING.md" target="_blank" rel="noopener">Upgrading Notes (2.x to 3.x)</a></li></ul><h2 id="Import-and-Client"><a href="#Import-and-Client" class="headerlink" title="Import and Client"></a>Import and Client</h2><p>The first step is to import the SDK and initialize a client.</p><h3 id="Old-v2"><a href="#Old-v2" class="headerlink" title="Old (v2)"></a>Old (v2)</h3><p><code>v2</code> provided CommonJS modules only. This was how to import the SDK, the SQS client in this example.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> sqs = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title function_">SQS</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-11-05&#x27;</span>&#125;);</span><br></pre></td></tr></table></figure><h3 id="New-v3"><a href="#New-v3" class="headerlink" title="New (v3)"></a>New (v3)</h3><p>With <code>v3</code>, there are two options to import the SDK. Here is how to import the SQS client using ES modules.</p><blockquote><p>Native JavaScript modules, or ES modules, are the modern approach to split JavaScript programs into separate modules. <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#a_background_on_modules" target="_blank" rel="noopener">Learn more!</a></p></blockquote><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">SQSClient</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-sdk/client-sqs&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> sqs = <span class="keyword">new</span> <span class="title class_">SQSClient</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-11-05&#x27;</span>&#125;);</span><br></pre></td></tr></table></figure><blockquote><p>By default, Lambda functions use CommonJS modules. To use ES modules, use the file suffix <code>.mjs</code> instead of <code>.js</code> or set <code>type</code> to <code>module</code> in the <code>package.json</code>. <a href="https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level-await-in-aws-lambda/" target="_blank" rel="noopener">Learn more!</a></p></blockquote><p>In case you want to stick with CommonJS modules to avoid having to rewrite larger parts of your code, this is how to import the SQS client, for example.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">SQSClient</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;@aws-sdk/client-sqs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> sqs = <span class="keyword">new</span> <span class="title class_">SQSClient</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-11-05&#x27;</span>&#125;);</span><br></pre></td></tr></table></figure><h2 id="Commands-instead-of-methods"><a href="#Commands-instead-of-methods" class="headerlink" title="Commands instead of methods"></a>Commands instead of methods</h2><p>AWS decided to use a command-style approach for <code>v3</code> of the AWS JS SDK. So, it’s sending commands instead of calling methods. Unfortunately, this requires to rewrite a lot of code.</p><h3 id="Old-v2-1"><a href="#Old-v2-1" class="headerlink" title="Old (v2)"></a>Old (v2)</h3><p>Instead of calling <code>listContainerInstances(...)</code> …</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> ecs = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title function_">ECS</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2014-11-13&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line">ecs.<span class="title function_">listContainerInstances</span>(&#123;</span><br><span class="line"> <span class="attr">cluster</span>: <span class="string">&#x27;demo&#x27;</span>,</span><br><span class="line"> <span class="attr">status</span>: <span class="string">&#x27;ACTIVE&#x27;</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="New-v3-1"><a href="#New-v3-1" class="headerlink" title="New (v3)"></a>New (v3)</h3><p>… send a <code>ListContainerInstancesCommand</code> command. Luckily, the parameters stay the same.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">ECSClient</span>, <span class="title class_">ListContainerInstancesCommand</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;@aws-sdk/client-ecs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> ecs = <span class="keyword">new</span> <span class="title class_">ECSClient</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2014-11-13&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line">ecs.<span class="title function_">send</span>(<span class="keyword">new</span> <span class="title class_">ListContainerInstancesCommand</span>(&#123;</span><br><span class="line"> <span class="attr">cluster</span>: <span class="string">&#x27;demo&#x27;</span>,</span><br><span class="line"> <span class="attr">status</span>: <span class="string">&#x27;ACTIVE&#x27;</span></span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure><h2 id="Promise"><a href="#Promise" class="headerlink" title="Promise"></a>Promise</h2><p>How to wait for results from AWS? I prefer using promises with the help of the <code>async/await</code> syntax.</p><h3 id="Old-v2-2"><a href="#Old-v2-2" class="headerlink" title="Old (v2)"></a>Old (v2)</h3><p><code>v2</code> uses callbacks by default. Therefore, it was necessary to append <code>promise()</code> to every method call.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> s3 = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title function_">S3</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2006-03-01&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="keyword">async</span> <span class="title function_">handler</span>(<span class="params"></span>) &#123;</span><br><span class="line"> <span class="keyword">await</span> s3.<span class="title function_">getObject</span>(&#123;</span><br><span class="line"> <span class="title class_">Bucket</span>: <span class="string">&#x27;demo&#x27;</span>,</span><br><span class="line"> <span class="title class_">Key</span>: <span class="string">&#x27;hello.txt&#x27;</span></span><br><span class="line"> &#125;).<span class="title function_">promise</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="New-v3-2"><a href="#New-v3-2" class="headerlink" title="New (v3)"></a>New (v3)</h3><p><code>v3</code> uses promises by default.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; S3Client, <span class="title class_">GetObjectCommand</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;@aws-sdk/client-s3&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> s3 = <span class="keyword">new</span> <span class="title function_">S3Client</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2006-03-01&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="keyword">async</span> <span class="title function_">handler</span>(<span class="params"></span>) &#123;</span><br><span class="line"> <span class="keyword">await</span> s3.<span class="title function_">send</span>(<span class="keyword">new</span> <span class="title class_">GetObjectCommand</span>(&#123;</span><br><span class="line"> <span class="title class_">Bucket</span>: <span class="string">&#x27;demo&#x27;</span>,</span><br><span class="line"> <span class="title class_">Key</span>: <span class="string">&#x27;hello.txt&#x27;</span></span><br><span class="line"> &#125;));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Callback"><a href="#Callback" class="headerlink" title="Callback"></a>Callback</h2><p>Do you prefer callbacks? Or do you want to avoid rewriting code?</p><h3 id="Old-v2-3"><a href="#Old-v2-3" class="headerlink" title="Old (v2)"></a>Old (v2)</h3><p>As mentioned above, <code>v2</code> defaults to callbacks. </p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> iam = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title function_">IAM</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2010-05-08&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line">iam.<span class="title function_">deleteAccountPasswordPolicy</span>(&#123;&#125;, <span class="function">(<span class="params">res, err</span>) =&gt;</span> &#123;</span><br><span class="line"> <span class="keyword">if</span> (err) &#123;</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line"> &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="New-v3-3"><a href="#New-v3-3" class="headerlink" title="New (v3)"></a>New (v3)</h3><p>But using callbacks is quite simple with <code>v3</code> as well. The <code>send(...)</code> method accepts a callback function as the 2nd parameter.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">IAMClient</span>, <span class="title class_">DeleteAccountPasswordPolicyCommand</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;@aws-sdk/client-iam&#x27;</span>);</span><br><span class="line"></span><br><span class="line">iam.<span class="title function_">send</span>(<span class="keyword">new</span> <span class="title class_">DeleteAccountPasswordPolicyCommand</span>(&#123;&#125;), <span class="function">(<span class="params">err, res</span>) =&gt;</span> &#123;</span><br><span class="line"> <span class="keyword">if</span> (err) &#123;</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line"> &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="Error-handling"><a href="#Error-handling" class="headerlink" title="Error handling"></a>Error handling</h2><p>When things go wrong, handling errors is critical.</p><h3 id="Old-v2-4"><a href="#Old-v2-4" class="headerlink" title="Old (v2)"></a>Old (v2)</h3><p>The <code>code</code> property of the error includes the error code.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> s3 = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title function_">S3</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2006-03-01&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line"> <span class="keyword">await</span> s3.<span class="title function_">getObject</span>(&#123;</span><br><span class="line"> <span class="title class_">Bucket</span>: <span class="string">&#x27;demo&#x27;</span>,</span><br><span class="line"> <span class="title class_">Key</span>: <span class="string">&#x27;hello.txt&#x27;</span></span><br><span class="line"> &#125;).<span class="title function_">promise</span>();</span><br><span class="line">&#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line"> <span class="keyword">if</span> (err.<span class="property">code</span> === <span class="string">&#x27;NoSuchKey&#x27;</span>) &#123;</span><br><span class="line"> <span class="comment">// object not found</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="New-v3-4"><a href="#New-v3-4" class="headerlink" title="New (v3)"></a>New (v3)</h3><p>With <code>v3</code> use the <code>name</code> property of the error.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; S3Client, <span class="title class_">GetObjectCommand</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;@aws-sdk/client-s3&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> s3 = <span class="keyword">new</span> <span class="title function_">S3Client</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2006-03-01&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line"> <span class="keyword">await</span> s3.<span class="title function_">send</span>(<span class="keyword">new</span> <span class="title class_">GetObjectCommand</span>(&#123;</span><br><span class="line"> <span class="title class_">Bucket</span>: <span class="string">&#x27;demo&#x27;</span>,</span><br><span class="line"> <span class="title class_">Key</span>: <span class="string">&#x27;hello.txt&#x27;</span></span><br><span class="line"> &#125;));</span><br><span class="line">&#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line"> <span class="keyword">if</span> (err.<span class="property">name</span> === <span class="string">&#x27;NoSuchKey&#x27;</span>) &#123;</span><br><span class="line"> <span class="comment">// object not found</span></span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="S3-multi-part-upload"><a href="#S3-multi-part-upload" class="headerlink" title="S3 multi-part upload"></a>S3 multi-part upload</h2><p>Splitting large files into multiple parts when uploading them to S3 is essential to improve performance.</p><h3 id="Old-v2-5"><a href="#Old-v2-5" class="headerlink" title="Old (v2)"></a>Old (v2)</h3><p>The S3 client shipped with the high-level method <code>upload(...)</code>, which handles multi-part uploads.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> s3 = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title function_">S3</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2006-03-01&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">await</span> s3.<span class="title function_">upload</span>(&#123;</span><br><span class="line"> <span class="title class_">Bucket</span>: <span class="string">&#x27;demo&#x27;</span>,</span><br><span class="line"> <span class="title class_">Key</span>: <span class="string">&#x27;heavy.tar&#x27;</span></span><br><span class="line"> <span class="title class_">Body</span>: body</span><br><span class="line">&#125;).<span class="title function_">promise</span>();</span><br></pre></td></tr></table></figure><h3 id="New-v3-5"><a href="#New-v3-5" class="headerlink" title="New (v3)"></a>New (v3)</h3><p>AWS moved that functionality from the S3 client to a separate module with <code>v3</code>.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; S3Client &#125; = <span class="built_in">require</span>(<span class="string">&#x27;@aws-sdk/client-s3&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">Upload</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;@aws-sdk/lib-storage&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> s3 = <span class="keyword">new</span> <span class="title function_">S3Client</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2006-03-01&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">await</span> <span class="keyword">new</span> <span class="title class_">Upload</span>(&#123;</span><br><span class="line"> <span class="attr">client</span>: s3,</span><br><span class="line"> <span class="attr">params</span>: &#123;</span><br><span class="line"> <span class="title class_">Bucket</span>: <span class="string">&#x27;demo&#x27;</span>,</span><br><span class="line"> <span class="title class_">Key</span>: <span class="string">&#x27;heavy.tar&#x27;</span></span><br><span class="line"> <span class="title class_">Body</span>: body</span><br><span class="line"> &#125;</span><br><span class="line">&#125;).<span class="title function_">done</span>();</span><br></pre></td></tr></table></figure><blockquote><p>The AWS JavaScript SDK v3 does still not support parallel byte-range fetches. Check out <a href="https://github.com/widdix/s3-getobject-accelerator" target="_blank" rel="noopener">widdix&#x2F;s3-getobject-accelerator</a> to accelerate fetching objects from S3.</p></blockquote><h2 id="Streaming-S3-results"><a href="#Streaming-S3-results" class="headerlink" title="Streaming S3 results"></a>Streaming S3 results</h2><p>When dealing with large files on S3, keeping them in memory is not an option. Make use of streams instead.</p><p>The following examples show how to download, transform, and upload an object.</p><h3 id="Old-v2-6"><a href="#Old-v2-6" class="headerlink" title="Old (v2)"></a>Old (v2)</h3><p>The <code>createReadStream(...)</code> method allows piping an object stored on S3 into a stream.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> zlib = <span class="built_in">require</span>(<span class="string">&#x27;zlib&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> stream = <span class="built_in">require</span>(<span class="string">&#x27;stream&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> s3 = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title function_">S3</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2006-03-01&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> body = stream.<span class="title function_">pipeline</span>(</span><br><span class="line"> s3.<span class="title function_">getObject</span>(&#123;</span><br><span class="line"> <span class="title class_">Bucket</span>: <span class="string">&#x27;demo&#x27;</span>,</span><br><span class="line"> <span class="title class_">Key</span>: <span class="string">&#x27;hello.txt&#x27;</span></span><br><span class="line"> &#125;).<span class="title function_">createReadStream</span>(),</span><br><span class="line"> zlib.<span class="title function_">createGzip</span>(),</span><br><span class="line"> <span class="function">() =&gt;</span> &#123;&#125;</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">await</span> s3.<span class="title function_">upload</span>(&#123;</span><br><span class="line"> <span class="title class_">Bucket</span>: <span class="string">&#x27;demo&#x27;</span>,</span><br><span class="line"> <span class="title class_">Key</span>: <span class="string">&#x27;hello.txt.gz&#x27;</span></span><br><span class="line"> <span class="title class_">Body</span>: body</span><br><span class="line">&#125;).<span class="title function_">promise</span>();</span><br></pre></td></tr></table></figure><h3 id="New-v3-6"><a href="#New-v3-6" class="headerlink" title="New (v3)"></a>New (v3)</h3><p>With <code>v3</code> the <code>Body</code> property of the <code>GetObjectCommand</code>, <code>PutObjectCommand</code> as well as the <code>Upload</code> functionality (see above) return or accept streams out-of-the-box.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> zlib = <span class="built_in">require</span>(<span class="string">&#x27;node:zlib&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> &#123; pipeline, <span class="title class_">Transform</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;node:stream&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> &#123; S3Client, <span class="title class_">GetObjectCommand</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;@aws-sdk/client-s3&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">Upload</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;@aws-sdk/lib-storage&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> s3 = <span class="keyword">new</span> <span class="title function_">S3Client</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2006-03-01&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> getObjectResponse = <span class="keyword">await</span> s3.<span class="title function_">send</span>(<span class="keyword">new</span> <span class="title class_">GetObjectCommand</span>(&#123;</span><br><span class="line"> <span class="title class_">Bucket</span>: <span class="string">&#x27;demo&#x27;</span>,</span><br><span class="line"> <span class="title class_">Key</span>: <span class="string">&#x27;hello.txt&#x27;</span></span><br><span class="line">&#125;));</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> bodyPipeline = <span class="title function_">pipeline</span>(</span><br><span class="line"> getObjectResponse.<span class="property">Body</span>,</span><br><span class="line"> zlib.<span class="title function_">createGzip</span>(),</span><br><span class="line"> <span class="function">() =&gt;</span> &#123;&#125;</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">await</span> <span class="keyword">new</span> <span class="title class_">Upload</span>(&#123;</span><br><span class="line"> <span class="attr">client</span>: s3,</span><br><span class="line"> <span class="attr">params</span>: &#123;</span><br><span class="line"> <span class="title class_">Bucket</span>: <span class="string">&#x27;demo&#x27;</span>,</span><br><span class="line"> <span class="title class_">Key</span>: <span class="string">&#x27;hello.txt.gz&#x27;</span></span><br><span class="line"> <span class="title class_">Body</span>: bodyPipeline</span><br><span class="line"> &#125;</span><br><span class="line">&#125;).<span class="title function_">done</span>();</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Due to breaking changes between <code>v2</code> and <code>v3</code> of the AWS JavaScript SDK, migrating incurs a lot of work. But there is no way out. AWS plans to deprecate <code>v2</code> soon. Also, the Node.js 18 environment for Lambda does ship with <code>v3</code> only.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Self-hosted GitHub runners on AWS</title>
      <link>https://cloudonaut.io/self-hosted-github-runners-on-aws/</link>
      <description>Three approaches to deploy self-hosted GitHub Runners on AWS: EC2 instance, Auto Scaling Group, Event-driven EC2 instances.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/github/">github</category>
      <guid isPermaLink="true">https://cloudonaut.io/self-hosted-github-runners-on-aws/</guid>
      <pubDate>Thu, 07 Sep 2023 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>GitHub Actions became my tool of choice for automating tasks around software development. To execute jobs, GitHub Actions relies on runners. By default, jobs run on GitHub-hosted runners. But there are good reasons to use self-hosted runners.</p><ul><li>Reducing costs by utilizing your cloud or on-premises infrastructure.</li><li>Accessing private networks (e.g., RDS connected to a VPC).</li><li>Customizing the environment by pre-installing libraries or tools.</li></ul><p>In the following, I will share three approaches to self-host GitHub runners on AWS and discuss their pros and cons.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/09/self-hosted_github_runner_on_aws_title@730w.webp 730w, /images/2023/09/self-hosted_github_runner_on_aws_title@730w2x.webp 1460w, /images/2023/09/self-hosted_github_runner_on_aws_title@610w.webp 610w, /images/2023/09/self-hosted_github_runner_on_aws_title@610w2x.webp 1220w, /images/2023/09/self-hosted_github_runner_on_aws_title@450w.webp 450w, /images/2023/09/self-hosted_github_runner_on_aws_title@450w2x.webp 900w, /images/2023/09/self-hosted_github_runner_on_aws_title@330w.webp 330w, /images/2023/09/self-hosted_github_runner_on_aws_title@330w2x.webp 660w, /images/2023/09/self-hosted_github_runner_on_aws_title@545w.webp 545w, /images/2023/09/self-hosted_github_runner_on_aws_title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/09/self-hosted_github_runner_on_aws_title@730w.jpg 730w, /images/2023/09/self-hosted_github_runner_on_aws_title@730w2x.jpg 1460w, /images/2023/09/self-hosted_github_runner_on_aws_title@610w.jpg 610w, /images/2023/09/self-hosted_github_runner_on_aws_title@610w2x.jpg 1220w, /images/2023/09/self-hosted_github_runner_on_aws_title@450w.jpg 450w, /images/2023/09/self-hosted_github_runner_on_aws_title@450w2x.jpg 900w, /images/2023/09/self-hosted_github_runner_on_aws_title@330w.jpg 330w, /images/2023/09/self-hosted_github_runner_on_aws_title@330w2x.jpg 660w, /images/2023/09/self-hosted_github_runner_on_aws_title@545w.jpg 545w, /images/2023/09/self-hosted_github_runner_on_aws_title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/09/self-hosted_github_runner_on_aws_title.jpg" alt="Self-hosted GitHub runners on AWS" title="Self-hosted GitHub runners on AWS"></picture></p><h2 id="Hosting-GitHub-runners-on-EC2-instances"><a href="#Hosting-GitHub-runners-on-EC2-instances" class="headerlink" title="Hosting GitHub runners on EC2 instances"></a>Hosting GitHub runners on EC2 instances</h2><p>The simplest way to host a GitHub runner on AWS is this.</p><ol><li>Launch an EC2 instance.</li><li>Install the runtime environment and tools required for jobs.</li><li>Install and configure the GitHub runner.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@730w.webp 730w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@730w2x.webp 1460w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@610w.webp 610w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@610w2x.webp 1220w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@450w.webp 450w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@450w2x.webp 900w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@330w.webp 330w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@330w2x.webp 660w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@545w.webp 545w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@730w.png 730w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@730w2x.png 1460w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@610w.png 610w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@610w2x.png 1220w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@450w.png 450w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@450w2x.png 900w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@330w.png 330w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@330w2x.png 660w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@545w.png 545w, /images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/09/self-hosted_github_runner_on_aws_single_ec2_instance.png" alt="Hosting GitHub runners on EC2 instances" title="Hosting GitHub runners on EC2 instances"></picture></p><p>The GitHub documentation describes how to <a href="https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners" target="_blank" rel="noopener">add self-hosted runners</a> in detail.</p><p>The approach comes with two downsides. First, the solution does not scale. During peeks, jobs pile up and slow down software development. Second, the concept is not secure. When projects or teams share a virtual machine to execute their jobs, there is a high risk of leaking sensitive information (e.g., AWS credentials).</p><h2 id="Scaling-GitHub-runners-with-auto-scaling"><a href="#Scaling-GitHub-runners-with-auto-scaling" class="headerlink" title="Scaling GitHub runners with auto-scaling"></a>Scaling GitHub runners with auto-scaling</h2><p>To avoid lengthy waiting times, scaling the number of EC2 instances running GitHub runners with the magic of auto-scaling is an obvious idea.</p><ul><li>An <strong>Auto Scaling Group</strong> launches and terminates EC2 instances based on an AMI with GitHub runner, the runtime environment, and tools pre-installed.</li><li>A <strong>CloudWatch alarm</strong> increases or decreases the desired capacity of the Auto Scaling Group based on a metric like the job queue length.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@730w.webp 730w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@730w2x.webp 1460w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@610w.webp 610w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@610w2x.webp 1220w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@450w.webp 450w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@450w2x.webp 900w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@330w.webp 330w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@330w2x.webp 660w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@545w.webp 545w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@730w.png 730w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@730w2x.png 1460w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@610w.png 610w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@610w2x.png 1220w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@450w.png 450w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@450w2x.png 900w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@330w.png 330w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@330w2x.png 660w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@545w.png 545w, /images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/09/self-hosted_github_runner_on_aws_autoscaling_ec2.png" alt="Scaling GitHub runners with auto-scaling" title="Scaling GitHub runners with auto-scaling"></picture></p><blockquote><p>A side note: it is not trivial to ensure the Auto Scaling Group does not terminate an EC2 instance that executes a long-running job (see <a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/lifecycle-hooks.html" target="_blank" rel="noopener">lifecycle hooks</a>). Also, finding the right metric to scale is tricky.</p></blockquote><p>While this approach addresses the scaling issue, it still has a major downside: jobs from different projects or event teams share the same virtual machine. There is a high risk of leaking sensitive information (e.g., AWS credentials).</p><h2 id="Event-driven-EC2-instances-for-GitHub-runners"><a href="#Event-driven-EC2-instances-for-GitHub-runners" class="headerlink" title="Event-driven EC2 instances for GitHub runners"></a>Event-driven EC2 instances for GitHub runners</h2><p>Here comes a simple approach that addresses both challenges: building a secure and scalable infrastructure for GitHub runners by executing each job on its own EC2 instance.</p><ol><li>The <strong>GitHub webhook</strong> sends events indicating that a job was queued and is waiting for a runner.</li><li>The <strong>API Gateway</strong> receives an event and invokes a Lambda function.</li><li>The <strong>Lambda function</strong> launches an EC2 instance and hands over a just-in-time runner registration via user data.</li><li>The <strong>EC2 instance</strong> starts the GitHub runner.</li><li>After the <strong>GitHub runner</strong> exits, the EC2 instance terminates itself.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@730w.webp 730w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@730w2x.webp 1460w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@610w.webp 610w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@610w2x.webp 1220w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@450w.webp 450w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@450w2x.webp 900w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@330w.webp 330w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@330w2x.webp 660w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@545w.webp 545w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@730w.png 730w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@730w2x.png 1460w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@610w.png 610w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@610w2x.png 1220w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@450w.png 450w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@450w2x.png 900w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@330w.png 330w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@330w2x.png 660w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@545w.png 545w, /images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/09/self-hosted_github_runner_on_aws_event_driven_ec2_instance.png" alt="Event-driven EC2 instances for GitHub runners" title="Event-driven EC2 instances for GitHub runners"></picture></p><p>This solution does offload the challenge of scaling an infrastructure to the on-demand capacity provided by AWS. And by the way, the approach is very cost-efficient as you are not paying for idle resources.</p><p>Besides that, as each job runs on its virtual machine, which guarantees high isolation and implements the <a href="https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-just-in-time-runners" target="_blank" rel="noopener">Security hardening for GitHub Actions</a> best practice of using just-in-time runners.</p><p>There is only one small catch: starting a new EC2 instance for each job adds a delay of ~1 minute for every job. In my opinion, a delay of 1 minute per job is worth the benefits in terms of scalability, cost, and safety.</p><blockquote><p>I’m happy to announce that we just released <a href="https://hyperenv.com/github-actions/" target="_blank" rel="noopener">HyperEnv for GitHub Actions: Self-hosted GitHub Runners on AWS</a>. This product implements the event-driven solution described above. <a href="https://aws.amazon.com/marketplace/pp/prodview-7bzr422ymm2hw" target="_blank" rel="noopener">HyperEnv for GitHub Actions is available at the AWS Marketplace</a>.</p></blockquote>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Security Monitoring in 2023: Untangle the chaos</title>
      <link>https://cloudonaut.io/2023-08-04-aws-security-monitoring/</link>
      <description>Discover Effective AWS Security Monitoring Strategies. Combining the right AWS services is key when setting up AWS Security Monitoring.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/securityhub/">securityhub</category>
      <guid isPermaLink="true">https://cloudonaut.io/2023-08-04-aws-security-monitoring/</guid>
      <pubDate>Fri, 04 Aug 2023 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>AWS security monitoring is a set of practices, tools, and processes designed to detect and respond to security threats and vulnerabilities within the Amazon Web Services (AWS) cloud environment. Sounds easy? In this blog post, I share how I use a variety (but not all) of AWS services to detect issues quickly and alert someone in charge to solve them.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/08/mess@730w.webp 730w, /images/2023/08/mess@730w2x.webp 1460w, /images/2023/08/mess@610w.webp 610w, /images/2023/08/mess@610w2x.webp 1220w, /images/2023/08/mess@450w.webp 450w, /images/2023/08/mess@450w2x.webp 900w, /images/2023/08/mess@330w.webp 330w, /images/2023/08/mess@330w2x.webp 660w, /images/2023/08/mess@545w.webp 545w, /images/2023/08/mess@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/08/mess@730w.jpg 730w, /images/2023/08/mess@730w2x.jpg 1460w, /images/2023/08/mess@610w.jpg 610w, /images/2023/08/mess@610w2x.jpg 1220w, /images/2023/08/mess@450w.jpg 450w, /images/2023/08/mess@450w2x.jpg 900w, /images/2023/08/mess@330w.jpg 330w, /images/2023/08/mess@330w2x.jpg 660w, /images/2023/08/mess@545w.jpg 545w, /images/2023/08/mess@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/08/mess.jpg" alt="Untangle the chaos" title="Untangle the chaos"></picture></p><h2 id="Available-AWS-services"><a href="#Available-AWS-services" class="headerlink" title="Available AWS services"></a>Available AWS services</h2><p>AWS is confusing. Multiple services are competing for your attention when you try to understand the basics of security monitoring. Let me untangle the chaos by grouping the related AWS services&#x2F;capabilities into three groups (visually and textually). After that, I will present you my selection of AWS services.</p><p>I came up with the following three groups:</p><ol><li>Sources of information: Provide raw security events or data that we can analyze. Usually, the volume is very high and overwhelming. Low-level findings also fall in this category.</li><li>Best practices and anomaly detection: Create higher-level findings by analyzing raw security events and data using predefined best practices or machine learning to detect unusual patterns.</li><li>Aggregation: Correlate and aggregate findings with all kinds of information and present them in a human-friendly way.</li></ol><p>Some AWS services belong to multiple groups. E.g., AWS Security Hub checks for best practices and aggregates events in a human-friendly way.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/08/aws_security_monitoring@730w.webp 730w, /images/2023/08/aws_security_monitoring@730w2x.webp 1460w, /images/2023/08/aws_security_monitoring@610w.webp 610w, /images/2023/08/aws_security_monitoring@610w2x.webp 1220w, /images/2023/08/aws_security_monitoring@450w.webp 450w, /images/2023/08/aws_security_monitoring@450w2x.webp 900w, /images/2023/08/aws_security_monitoring@330w.webp 330w, /images/2023/08/aws_security_monitoring@330w2x.webp 660w, /images/2023/08/aws_security_monitoring@545w.webp 545w, /images/2023/08/aws_security_monitoring@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/08/aws_security_monitoring@730w.png 730w, /images/2023/08/aws_security_monitoring@730w2x.png 1460w, /images/2023/08/aws_security_monitoring@610w.png 610w, /images/2023/08/aws_security_monitoring@610w2x.png 1220w, /images/2023/08/aws_security_monitoring@450w.png 450w, /images/2023/08/aws_security_monitoring@450w2x.png 900w, /images/2023/08/aws_security_monitoring@330w.png 330w, /images/2023/08/aws_security_monitoring@330w2x.png 660w, /images/2023/08/aws_security_monitoring@545w.png 545w, /images/2023/08/aws_security_monitoring@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/08/aws_security_monitoring.png" alt="Visual grouping of AWS security services and capabilities" title="Visual grouping of AWS security services and capabilities"></picture></p><p>The following <strong>sources of information</strong> can be used to monitor security-related activity in your AWS Account:</p><ul><li>AWS API (current configuration of each resource can be requested)</li><li>AWS Config configuration item</li><li>AWS CloudTrail management and data event</li><li>Amazon VPC Flow Logs</li><li>Amazon Route 53 DNS query logs</li><li>AWS Health event</li><li>Amazon Inspector vulnerability finding</li><li>Amazon Macie sensitive data finding</li><li>Amazon ECR Basic scanning finding</li><li>AWS IoT Device Defender Detect finding</li></ul><p>The following AWS services&#x2F;capabilities check the above sources against <strong>best practices</strong> or to <strong>detect anomalies</strong>. The following table lists the services and the sources that they use.</p><table class="table table-striped table-responsive table-sm"><thead><tr><th>AWS&nbsp;service/capability</th><th>AWS&nbsp;API</th><th>Config item</th><th>Config rule</th><th>CloudTrail event</th><th>VPC Flow&nbsp;Logs</th><th>Route&nbsp;53 DNS&nbsp;query&nbsp;logs</th></tr></thead><tbody><tr><td>AWS Config rule</td><td>no</td><td>yes</td><td>x</td><td>no</td><td>no</td><td>no</td></tr><tr><td>Amazon Macie policy finding</td><td>yes</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td></tr><tr><td>AWS Trusted Advisor check</td><td>yes</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td></tr><tr><td>AWS Security Hub control</td><td>no</td><td>no</td><td>yes</td><td>no</td><td>no</td><td>no</td></tr><tr><td>AWS CloudTrail Insights event</td><td>no</td><td>no</td><td>no</td><td>yes</td><td>no</td><td>no</td></tr><tr><td>Amazon GuardDuty</td><td>no</td><td>no</td><td>no</td><td>yes</td><td>yes</td><td>yes</td></tr><tr><td>AWS IAM Access Analyzer finding</td><td>yes</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td></tr><tr><td>AWS Firewall Manager finding</td><td>yes</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td></tr><tr><td>AWS IoT Device Defender Audit finding</td><td>yes</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td></tr></tbody></table><p>Last but not least, AWS <strong>aggregates</strong> all the information in different services. The following table lists the services and the sources that they use.</p><table class="table table-striped table-responsive table-sm"><thead><tr><th>AWS&nbsp;service/capability</th><th>Config rule</th><th>CloudTrail event</th><th>VPC Flow&nbsp;Logs</th><th>Route&nbsp;53 DNS&nbsp;query&nbsp;logs</th><th>Inspector findings</th><th>Macie finding</th><th>Security&nbsp;Hub finding</th><th>GuardDuty finding</th><th>Trusted&nbsp;Advisor check</th><th>Health event</th><th>Access&nbsp;Analyzer finding</th><th>Firewall&nbsp;Manager finding</th><th>IoT&nbsp;Device&nbsp;Defender finding</th></tr></thead><tbody><tr><td>AWS&nbsp;Security&nbsp;Hub</td><td>yes</td><td>no</td><td>no</td><td>no</td><td>yes</td><td>yes</td><td>x</td><td>yes</td><td>no</td><td>yes</td><td>yes</td><td>yes</td><td>yes</td></tr><tr><td>Amazon&nbsp;Security&nbsp;Lake</td><td>no</td><td>yes</td><td>yes</td><td>yes</td><td>no</td><td>no</td><td>yes</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td></tr><tr><td>Amazon&nbsp;Detective</td><td>no</td><td>yes</td><td>yes</td><td>no</td><td>no</td><td>no</td><td>yes</td><td>yes</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td></tr><tr><td>AWS&nbsp;Audit&nbsp;Manager</td><td>yes</td><td>yes</td><td>no</td><td>no</td><td>no</td><td>no</td><td>yes</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td></tr><tr><td>AWS&nbsp;Config Conformance&nbsp;Pack</td><td>yes</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td></tr><tr><td>AWS&nbsp;Trusted&nbsp;Advisor</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td><td>no</td><td>yes</td><td>no</td><td>yes</td><td>no</td><td>no</td><td>no</td><td>no</td></tr></tbody></table><h2 id="Selecting-the-right-AWS-services"><a href="#Selecting-the-right-AWS-services" class="headerlink" title="Selecting the right AWS services"></a>Selecting the right AWS services</h2><p>Many AWS services&#x2F;capabilities overlap in what information they analyze and the checks they perform. Examples:</p><ul><li>AWS Trusted Advisor, AWS Config Rules &#x2F; Conformance Packs, and AWS Security Hub Security standards all check if you follow predefined best practices in your AWS account. </li><li>Amazon Detective, AWS CloudTrail Insight, and Amazon GuardDuty analyze your CloudTrail data to find suspicious activity.</li></ul><p>If you mindlessly enable all the services, you pay for the same check multiple times. But the issue worsens: Each service creates a finding, but all refer to the same problem. For example, if you make AWS Trusted Advisor, AWS Config Rules &#x2F; Conformance Packs, AWS Security Hub Security standards, and Amazon Macie create an S3 bucket public, a firework of findings. They all tell you that the bucket is now public. But how do you manage this information overload? Amazon invented another service to aggregate this information again: Amazon Detective.</p><p>I suggest that you enable the following AWS services&#x2F;capabilities in your a delegated admin AWS Account (aka security account, a feature of AWS organizations) to check all your AWS accounts in one place:</p><ul><li>AWS Config with a retention period of 1 year in each region you use.</li><li>AWS Security Hub with the AWS Foundational Security Best Practices (FSBP) standard enabled in each region you use (AWS Security Hub requires AWS Config).</li><li>Optional: Amazon GuardDuty in each region you use.</li><li>Optional: Amazon Inspector in each region you use.</li></ul><p>By default, GuardDuty and Inspector send findings to Security Hub. Therefore, Security Hub is your central place to work with findings. A finding can be NEW, acknowledged (NOTIFIED), SUPPRESSED, or RESOLVED. To end goal is to keep the number of findings low.</p><blockquote><p>You can disable AWS Security Hub controls if you disagree with the “best practice.”</p></blockquote><p>Last, we must alert someone in charge to handle the finding.</p><h2 id="Incident-Response"><a href="#Incident-Response" class="headerlink" title="Incident Response"></a>Incident Response</h2><p>A finding is the beginning, not the end, of the security incident response process. Once a finding is created, the process starts:</p><ol><li>Alert the right person.</li><li>Analyse finding (research additional data).</li><li>Fix issue.</li><li>Resolve finding.</li></ol><p>To alert the right person, you have to understand how Security Hub publishes information about findings. Security Hub publishes an event to EventBridge whenever a finding is created or updated. If you followed my advice to use a delegated admin AWS Account, the event is published in the source (aka member) and delegated admin accounts. I recommend creating an EventBridge rule to listen to new findings in each AWS member account (not the delegated admin). Assuming that you use AWS accounts to isolate workloads, there should be a relationship between the AWS account and a team in charge that can be alerted when a new finding arrives. Remember to create an EventBridge rule in each region you use.</p><p>The following Terraform snippet creates an EventBridge rule connected to an SNS topic:</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_cloudwatch_event_rule&quot; &quot;security_hub_finding&quot; &#123;</span><br><span class="line">  name          = &quot;security-hub-finding&quot;</span><br><span class="line">  description   = &quot;Findings (severity &gt;= high) from AWS SecurityHub.&quot;</span><br><span class="line">  event_pattern = &lt;&lt;JSON</span><br><span class="line">&#123;</span><br><span class="line">  &quot;source&quot;: [ </span><br><span class="line">    &quot;aws.securityhub&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;detail-type&quot;: [</span><br><span class="line">    &quot;Security Hub Findings - Imported&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;detail&quot;: &#123;</span><br><span class="line">    &quot;findings&quot;: &#123;</span><br><span class="line">      &quot;Severity&quot;: &#123;</span><br><span class="line">        &quot;Normalized&quot;: [&#123;&quot;numeric&quot;: [&quot;&gt;=&quot;, 70]&#125;]</span><br><span class="line">      &#125;,</span><br><span class="line">      &quot;Workflow&quot;: &#123;</span><br><span class="line">        &quot;Status&quot;: [</span><br><span class="line">          &quot;NEW&quot;</span><br><span class="line">        ]</span><br><span class="line">      &#125;,</span><br><span class="line">      &quot;RecordState&quot;: [</span><br><span class="line">        &quot;ACTIVE&quot;</span><br><span class="line">      ]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">JSON</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_cloudwatch_event_target&quot; &quot;security_hub_finding&quot; &#123;</span><br><span class="line">  rule      = aws_cloudwatch_event_rule.security_hub_finding.name</span><br><span class="line">  target_id = &quot;sns&quot;</span><br><span class="line">  arn       = &quot;YOUR SNS TOPIC ARN&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>There is one issue with this approach. As long as the status is <code>NEW</code>, you will receive an EventBridge event per finding every day. If this feels too spammy, write a Lambda function to set the status to <code>NOTIFIED</code>. Alternatively, you can use marbot, our <a href="https://marbot.io/" target="_blank" rel="noopener">AWS Monitoring chatbot</a>. marbot creates the EventBridge rule for you and sets the status to <code>NOTIFIED</code> after you receive an alert in Slack or Microsoft Teams about the new finding.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Many AWS services and capabilities are relevant when talking about AWS Security Monitoring. They overlap in what information they analyze and the checks they perform. If you carefully select the right AWS services, you can avoid duplicate findings and costs while observing all the security-relevant sources. Use AWS Security Hub as your central place for AWS Security Monitoring. Optionally, use GuardDuty and Inspector to feed additional insights into Security Hub. Last, use EventBridge to forward Security Hub findings to the right team.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Show Me Your Architecture Vol. 2: Platform Engineering on AWS</title>
      <link>https://cloudonaut.io/platform-engineering-on-aws-show-me-your-architecture-vol2/</link>
      <description>
        <![CDATA[<p>Through the AWS documentation, books like AWS in Action or AWS training, you can gain theoretical knowledge. But beyond that, it is very]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/rds/">rds</category>
      <category domain="https://cloudonaut.io/tag/eks/">eks</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <category domain="https://cloudonaut.io/tag/show-me-your-architecture/">show-me-your-architecture</category>
      <guid isPermaLink="true">https://cloudonaut.io/platform-engineering-on-aws-show-me-your-architecture-vol2/</guid>
      <pubDate>Tue, 11 Jul 2023 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Through the AWS documentation, books like AWS in Action or AWS training, you can gain theoretical knowledge. But beyond that, it is very valuable to learn directly from practice. In this series, we inspect real-life AWS architectures. In the 2nd volume of the series, Matt provides insights into platform engineering on AWS.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/07/show-me-your-architecture-vol2-title@730w.webp 730w, /images/2023/07/show-me-your-architecture-vol2-title@730w2x.webp 1460w, /images/2023/07/show-me-your-architecture-vol2-title@610w.webp 610w, /images/2023/07/show-me-your-architecture-vol2-title@610w2x.webp 1220w, /images/2023/07/show-me-your-architecture-vol2-title@450w.webp 450w, /images/2023/07/show-me-your-architecture-vol2-title@450w2x.webp 900w, /images/2023/07/show-me-your-architecture-vol2-title@330w.webp 330w, /images/2023/07/show-me-your-architecture-vol2-title@330w2x.webp 660w, /images/2023/07/show-me-your-architecture-vol2-title@545w.webp 545w, /images/2023/07/show-me-your-architecture-vol2-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/07/show-me-your-architecture-vol2-title@730w.jpg 730w, /images/2023/07/show-me-your-architecture-vol2-title@730w2x.jpg 1460w, /images/2023/07/show-me-your-architecture-vol2-title@610w.jpg 610w, /images/2023/07/show-me-your-architecture-vol2-title@610w2x.jpg 1220w, /images/2023/07/show-me-your-architecture-vol2-title@450w.jpg 450w, /images/2023/07/show-me-your-architecture-vol2-title@450w2x.jpg 900w, /images/2023/07/show-me-your-architecture-vol2-title@330w.jpg 330w, /images/2023/07/show-me-your-architecture-vol2-title@330w2x.jpg 660w, /images/2023/07/show-me-your-architecture-vol2-title@545w.jpg 545w, /images/2023/07/show-me-your-architecture-vol2-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/07/show-me-your-architecture-vol2-title.jpg" alt="Show Me Your Architecture Vol. 2: Scanning S3 buckets for malware" title="Show Me Your Architecture Vol. 2: Scanning S3 buckets for malware"></picture></p><h2 id="Who-are-you"><a href="#Who-are-you" class="headerlink" title="Who are you?"></a>Who are you?</h2><p>I’m Matt Gowie, Founder at <a href="https://masterpoint.io/" target="_blank" rel="noopener">Masterpoint</a>. I started my career as a software engineer and later transitioned into the AWS and DevOps world. I established Masterpoint originally as a solo consultancy. Still, in recent years we’ve grown to a larger team and are entirely focused on AWS Platform Engineering efforts using Terraform, Kubernetes, and a focus on GitOps. Our project successes have included short-term engagements and larger projects for diverse clients ranging from seed-funded startups, Fortune 20 enterprises, and many that fall in between.</p><h2 id="Which-problem-do-you-solve"><a href="#Which-problem-do-you-solve" class="headerlink" title="Which problem do you solve?"></a>Which problem do you solve?</h2><p>We’re building cloud platforms that allow our clients to easily deploy their AWS applications. We empower developers to deploy their microservices by providing continuous delivery mechanisms and a production-ready platform. Our goal is to provide a ready-to-use application layer customized to the needs of our clients.</p><h2 id="What-does-the-architecture-look-like"><a href="#What-does-the-architecture-look-like" class="headerlink" title="What does the architecture look like?"></a>What does the architecture look like?</h2><p>As shown in the following figure, we use the following building blocks as the generic platform for our clients.</p><ul><li><strong>Amazon Elastic Kubernetes Service (EKS)</strong> orchestrates containers.</li><li><strong>AWS Fargate</strong> and <strong>EKS Managed Node Groups</strong> act as our compute layer for EKS.</li><li><strong>Amazon RDS</strong>, <strong>Amazon ElastiCache</strong>, <strong>Amazon OpenSearch</strong>, <strong>Amazon S3</strong>, … and more provide managed services consumed by our clients’ applications.</li><li><strong><a href="https://argo-cd.readthedocs.io/en/stable/" target="_blank" rel="noopener">Argo CD</a></strong> allows application engineers to deploy their microservices using declarative GitOps CD for Kubernetes.</li><li><strong><a href="https://spacelift.io/" target="_blank" rel="noopener">Spacelift</a></strong> is used to manage the infrastructure automation that we define in Terraform modules and configuration files.</li><li>The <strong><a href="https://github.com/isindir/sops-secrets-operator" target="_blank" rel="noopener">SOPS Operator</a></strong> is our standard means to manage Kubernetes Secret Resources, providing sensitive configuration parameters to microservices in a GitOps way.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@730w.webp 730w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@730w2x.webp 1460w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@610w.webp 610w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@610w2x.webp 1220w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@450w.webp 450w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@450w2x.webp 900w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@330w.webp 330w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@330w2x.webp 660w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@545w.webp 545w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@730w.png 730w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@730w2x.png 1460w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@610w.png 610w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@610w2x.png 1220w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@450w.png 450w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@450w2x.png 900w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@330w.png 330w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@330w2x.png 660w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@545w.png 545w, /images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/07/show-me-your-architecture-vol2-platform-engineering-on-aws.png" alt="Platform Engineering on AWS: EKS, Fargate, RDS, ElastiCache, Argo CD, Spacelift, and many more." title="Platform Engineering on AWS: EKS, Fargate, RDS, ElastiCache, Argo CD, Spacelift, and many more."></picture></p><p>Using Infrastructure as Code with Terraform allows us to bootstrap all the underlying infrastructure like VPC, EKS, and so on. Spacelift executes our Terraform code to spin up the platform up until the point where Argo CD is up and running. From that point, Argo CD takes over, mainly to deploy application microservices and any 3rd party tooling needed in the cluster (like a CSI driver, observability tooling, log processor, or similar).</p><h2 id="What-other-approaches-did-you-consider"><a href="#What-other-approaches-did-you-consider" class="headerlink" title="What other approaches did you consider?"></a>What other approaches did you consider?</h2><p>The obvious question is, why EKS instead of ECS? The main reason why we bet on EKS is that it supports GitOps. The idea behind GitOps is that changes to the infrastructure or application code are pulled to the cluster instead of pushed, which enforces a single source of truth (Git) and removes infrastructure drift from the equation. There is no GitOps operator for ECS. Therefore, ECS requires the traditional approach of a CI&#x2F;CD pipeline.</p><p>So why is GitOps so important? GitOps is the perfect choice when it comes to involving application engineers in their platform. For developers, GitOps feels natural and is simple to use because it simply revolves around a tool that we all already know well: Git and our Git Provider (GitHub, GitLab, etc.).</p><p>Besides picking the right service for orchestrating containers, we have considered using Flux instead of Argo CD. Both Flux and Argo CD are continuous delivery GitOps tools for Kubernetes. We picked Argo CD because it comes with a graphical user interface that is friendly to application engineers and is feature complete. However, we have an eye on the progress Flux is making because it has some interesting capabilities and is gaining ground.</p><h2 id="What-are-the-limitations-of-the-architecture"><a href="#What-are-the-limitations-of-the-architecture" class="headerlink" title="What are the limitations of the architecture?"></a>What are the limitations of the architecture?</h2><p>The architecture of the platform has one design flaw. Terraform spins up the infrastructure and installs Argo CD. Then, Argo CD takes over and provisions the application services and supporting tooling. The problem is that it is important to ensure that neither Terraform nor Argo CD crosses the boundary between the resources they manage. For example, Terraform should not interact with the K8s resources managed by Argo CD. And Argo CD should not modify AWS resources managed by Terraform.</p><p>We wanted to solve this limitation by potentially moving all of our Terraform towards Crossplane, but our research showed us that it is not ready for our level of infrastructure automation. Veronika from my team just published a blog post summarizing our experiences with Crossplane: <a href="https://masterpoint.io/updates/passing-on-crossplane/" target="_blank" rel="noopener">Crossplane: Why it Didn’t Work for Us</a>.</p><h2 id="How-did-architecture-evolve"><a href="#How-did-architecture-evolve" class="headerlink" title="How did architecture evolve?"></a>How did architecture evolve?</h2><p>Our architecture constantly evolves, as platform engineering, K8s, and GitOps is a very vibrant space.</p><p>Here is one example, at the beginning, we were big fans of AWS Systems Manager Parameter Store to make secrets available to microservices. However, managing those secrets required multiple steps to be driven by a GitOps workflow, which was unreliable and a lot of work.</p><p>Therefore, we started using SOPS to manage secrets once we figured out that it was a more powerful, Git-driven pattern for secrets management. The SOPS operator that we use allows us to manage a SOPS file, and it updates Kubernetes Secrets whenever changes are made in Git and keep things secure by encrypting the secret values that we store in Git via AWS KMS.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>The combination of EKS and GitOps allows Matt and his team to build platforms that allow application engineers to deploy their microservices securely, reliably, and with ease. GitOps is a modern and intuitive way to deploy microservices and enhances the collaboration between application and platform engineers. By using services like RDS, ElastiCache, OpenSearch, or S3, Matt offloads the complexity of managing databases and storage and focuses on the important part: the business application.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Detecting connectivity anomalies with CloudWatch Internet Monitor</title>
      <link>https://cloudonaut.io/detecting-connectivity-anomalies-with-cloudwatch-internet-monitor/</link>
      <description>
        <![CDATA[<p>Imagine customer support informs you that some customers can no longer access your web application. Immediately you check the monitoring,]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <category domain="https://cloudonaut.io/tag/cloudfront/">cloudfront</category>
      <guid isPermaLink="true">https://cloudonaut.io/detecting-connectivity-anomalies-with-cloudwatch-internet-monitor/</guid>
      <pubDate>Tue, 20 Jun 2023 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Imagine customer support informs you that some customers can no longer access your web application. Immediately you check the monitoring, but no abnormalities are visible on the dashboard. No alarm has been triggered. So, what’s the problem?</p><p>The number of components that need to work together flawlessly to ensure a customer is able to access your web application is tremendous. It’s not only the components you control, like the application, EC2 instances, ALB, and so on, but also many components out of your control. For example, there might be an issue with the peering between your customer’s Internet Service Provider (ISP) and the AWS network.</p><p>But how to monitor the availability and performance of the Internet? <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-InternetMonitor.html" target="_blank" rel="noopener">Amazon CloudWatch Internet Monitor</a> provides insights into network connectivity issues between ISPs and the AWS network. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/06/internet-monitor-title@730w.webp 730w, /images/2023/06/internet-monitor-title@730w2x.webp 1460w, /images/2023/06/internet-monitor-title@610w.webp 610w, /images/2023/06/internet-monitor-title@610w2x.webp 1220w, /images/2023/06/internet-monitor-title@450w.webp 450w, /images/2023/06/internet-monitor-title@450w2x.webp 900w, /images/2023/06/internet-monitor-title@330w.webp 330w, /images/2023/06/internet-monitor-title@330w2x.webp 660w, /images/2023/06/internet-monitor-title@545w.webp 545w, /images/2023/06/internet-monitor-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/06/internet-monitor-title@730w.jpg 730w, /images/2023/06/internet-monitor-title@730w2x.jpg 1460w, /images/2023/06/internet-monitor-title@610w.jpg 610w, /images/2023/06/internet-monitor-title@610w2x.jpg 1220w, /images/2023/06/internet-monitor-title@450w.jpg 450w, /images/2023/06/internet-monitor-title@450w2x.jpg 900w, /images/2023/06/internet-monitor-title@330w.jpg 330w, /images/2023/06/internet-monitor-title@330w2x.jpg 660w, /images/2023/06/internet-monitor-title@545w.jpg 545w, /images/2023/06/internet-monitor-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/06/internet-monitor-title.jpg" alt="Detecting connectivity anomalies with CloudWatch Internet Monitor" title="Detecting connectivity anomalies with CloudWatch Internet Monitor"></picture></p><h2 id="Why"><a href="#Why" class="headerlink" title="Why?"></a>Why?</h2><p>The Internet Monitor by AWS provides insights into networking issues that affect your customers’ experience. The following screenshot shows a performance issue detected by Internet Monitor affecting readers of this blog dialing in from Oslo.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/06/internet-monitor-event@730w.webp 730w, /images/2023/06/internet-monitor-event@730w2x.webp 1460w, /images/2023/06/internet-monitor-event@610w.webp 610w, /images/2023/06/internet-monitor-event@610w2x.webp 1220w, /images/2023/06/internet-monitor-event@450w.webp 450w, /images/2023/06/internet-monitor-event@450w2x.webp 900w, /images/2023/06/internet-monitor-event@330w.webp 330w, /images/2023/06/internet-monitor-event@330w2x.webp 660w, /images/2023/06/internet-monitor-event@545w.webp 545w, /images/2023/06/internet-monitor-event@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/06/internet-monitor-event@730w.png 730w, /images/2023/06/internet-monitor-event@730w2x.png 1460w, /images/2023/06/internet-monitor-event@610w.png 610w, /images/2023/06/internet-monitor-event@610w2x.png 1220w, /images/2023/06/internet-monitor-event@450w.png 450w, /images/2023/06/internet-monitor-event@450w2x.png 900w, /images/2023/06/internet-monitor-event@330w.png 330w, /images/2023/06/internet-monitor-event@330w2x.png 660w, /images/2023/06/internet-monitor-event@545w.png 545w, /images/2023/06/internet-monitor-event@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/06/internet-monitor-event.png" alt="CloudWatch Internet Monitor detects a performance issue for users from Oslo" title="CloudWatch Internet Monitor detects a performance issue for users from Oslo"></picture></p><p>Of course, we cannot fix that issue ourselves. Instead, we can only wait for the ISP or AWS to fix the networking issue. But insights into networking issues are essential when communicating with customers, such as answering support requests.</p><p>Besides that, the Internet Monitor helps to validate or improve your networking architecture. For example, moving your web application to another region, using CloudFront or Network Accelerator, or even going multi-region.</p><h2 id="How"><a href="#How" class="headerlink" title="How?"></a>How?</h2><p>The following figure illustrates the Internet Monitor supports monitoring the following resources.</p><ul><li>CloudFront</li><li>WorkSpaces</li><li>VPC</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/06/internetmonitorig-overview@730w.webp 730w, /images/2023/06/internetmonitorig-overview@730w2x.webp 1460w, /images/2023/06/internetmonitorig-overview@610w.webp 610w, /images/2023/06/internetmonitorig-overview@610w2x.webp 1220w, /images/2023/06/internetmonitorig-overview@450w.webp 450w, /images/2023/06/internetmonitorig-overview@450w2x.webp 900w, /images/2023/06/internetmonitorig-overview@330w.webp 330w, /images/2023/06/internetmonitorig-overview@330w2x.webp 660w, /images/2023/06/internetmonitorig-overview@545w.webp 545w, /images/2023/06/internetmonitorig-overview@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/06/internetmonitorig-overview@730w.png 730w, /images/2023/06/internetmonitorig-overview@730w2x.png 1460w, /images/2023/06/internetmonitorig-overview@610w.png 610w, /images/2023/06/internetmonitorig-overview@610w2x.png 1220w, /images/2023/06/internetmonitorig-overview@450w.png 450w, /images/2023/06/internetmonitorig-overview@450w2x.png 900w, /images/2023/06/internetmonitorig-overview@330w.png 330w, /images/2023/06/internetmonitorig-overview@330w2x.png 660w, /images/2023/06/internetmonitorig-overview@545w.png 545w, /images/2023/06/internetmonitorig-overview@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/06/internetmonitorig-overview.png" alt="How does CloudWatch Internet Monitor work?" title="How does CloudWatch Internet Monitor work?"></picture></p><p>Please note that Internet Monitor does support VPC monitoring only in some regions (see <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-InternetMonitor.Regions.html" target="_blank" rel="noopener">Supported AWS Regions for Amazon CloudWatch Internet Monitor</a>).</p><p>Internet Monitor monitors the subset of the Internet accessed by users of your web application. Unlike other tools that monitor web applications globally, Internet Monitor specifically targets your web application’s traffic. It utilizes powerful probes and algorithms to detect connectivity issues and alerts you through health events. By overlaying the traffic profile of your active viewers, it creates a performance and availability map. The tool publishes internet measurements every five minutes for the top 500 city-networks and can publish measures for all monitored city-networks to an Amazon S3 bucket.</p><p>The following instructions guide you through creating a monitor.</p><p>Open <a href="https://us-east-1.console.aws.amazon.com/cloudwatch/home#internet-monitor:monitors" target="_blank" rel="noopener">CloudWatch Internet Monitor</a> via AWS Management Console.</p><p>Press the <strong>Create monitor</strong> button.</p><p>Define a name for the monitor and select the resource to monitor. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/06/internet-monitor-create-01@730w.webp 730w, /images/2023/06/internet-monitor-create-01@730w2x.webp 1460w, /images/2023/06/internet-monitor-create-01@610w.webp 610w, /images/2023/06/internet-monitor-create-01@610w2x.webp 1220w, /images/2023/06/internet-monitor-create-01@450w.webp 450w, /images/2023/06/internet-monitor-create-01@450w2x.webp 900w, /images/2023/06/internet-monitor-create-01@330w.webp 330w, /images/2023/06/internet-monitor-create-01@330w2x.webp 660w, /images/2023/06/internet-monitor-create-01@545w.webp 545w, /images/2023/06/internet-monitor-create-01@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/06/internet-monitor-create-01@730w.png 730w, /images/2023/06/internet-monitor-create-01@730w2x.png 1460w, /images/2023/06/internet-monitor-create-01@610w.png 610w, /images/2023/06/internet-monitor-create-01@610w2x.png 1220w, /images/2023/06/internet-monitor-create-01@450w.png 450w, /images/2023/06/internet-monitor-create-01@450w2x.png 900w, /images/2023/06/internet-monitor-create-01@330w.png 330w, /images/2023/06/internet-monitor-create-01@330w2x.png 660w, /images/2023/06/internet-monitor-create-01@545w.png 545w, /images/2023/06/internet-monitor-create-01@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/06/internet-monitor-create-01.png" alt="Creating a Internet Monitor (Part 2)" title="Creating a Internet Monitor (Part 2)"></picture></p><p>Select monitoring 100% of the traffic. Specify a maximum number of monitored city-networks. Monitor up to 100 city-networks free of charge per AWS account.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/06/internet-monitor-create-02@730w.webp 730w, /images/2023/06/internet-monitor-create-02@730w2x.webp 1460w, /images/2023/06/internet-monitor-create-02@610w.webp 610w, /images/2023/06/internet-monitor-create-02@610w2x.webp 1220w, /images/2023/06/internet-monitor-create-02@450w.webp 450w, /images/2023/06/internet-monitor-create-02@450w2x.webp 900w, /images/2023/06/internet-monitor-create-02@330w.webp 330w, /images/2023/06/internet-monitor-create-02@330w2x.webp 660w, /images/2023/06/internet-monitor-create-02@545w.webp 545w, /images/2023/06/internet-monitor-create-02@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/06/internet-monitor-create-02@730w.png 730w, /images/2023/06/internet-monitor-create-02@730w2x.png 1460w, /images/2023/06/internet-monitor-create-02@610w.png 610w, /images/2023/06/internet-monitor-create-02@610w2x.png 1220w, /images/2023/06/internet-monitor-create-02@450w.png 450w, /images/2023/06/internet-monitor-create-02@450w2x.png 900w, /images/2023/06/internet-monitor-create-02@330w.png 330w, /images/2023/06/internet-monitor-create-02@330w2x.png 660w, /images/2023/06/internet-monitor-create-02@545w.png 545w, /images/2023/06/internet-monitor-create-02@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/06/internet-monitor-create-02.png" alt="Creating a Internet Monitor (Part 2)" title="Creating a Internet Monitor (Part 2)"></picture></p><p>Click the <strong>Next</strong> button.<br>Review your inputs and press the *<em>Create monitor</em> button.</p><p>Wait a few hours for data about the internet availability and performance affecting the monitored resource arrives. For example, historical data about the monitored resource, a CloudFront distribution in our example, shows up.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/06/internet-monitor-historical-explorer@730w.webp 730w, /images/2023/06/internet-monitor-historical-explorer@730w2x.webp 1460w, /images/2023/06/internet-monitor-historical-explorer@610w.webp 610w, /images/2023/06/internet-monitor-historical-explorer@610w2x.webp 1220w, /images/2023/06/internet-monitor-historical-explorer@450w.webp 450w, /images/2023/06/internet-monitor-historical-explorer@450w2x.webp 900w, /images/2023/06/internet-monitor-historical-explorer@330w.webp 330w, /images/2023/06/internet-monitor-historical-explorer@330w2x.webp 660w, /images/2023/06/internet-monitor-historical-explorer@545w.webp 545w, /images/2023/06/internet-monitor-historical-explorer@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/06/internet-monitor-historical-explorer@730w.png 730w, /images/2023/06/internet-monitor-historical-explorer@730w2x.png 1460w, /images/2023/06/internet-monitor-historical-explorer@610w.png 610w, /images/2023/06/internet-monitor-historical-explorer@610w2x.png 1220w, /images/2023/06/internet-monitor-historical-explorer@450w.png 450w, /images/2023/06/internet-monitor-historical-explorer@450w2x.png 900w, /images/2023/06/internet-monitor-historical-explorer@330w.png 330w, /images/2023/06/internet-monitor-historical-explorer@330w2x.png 660w, /images/2023/06/internet-monitor-historical-explorer@545w.png 545w, /images/2023/06/internet-monitor-historical-explorer@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/06/internet-monitor-historical-explorer.png" alt="Amazon CloudWatch Internet Monitor shows historical data" title="Amazon CloudWatch Internet Monitor shows historical data"></picture></p><h2 id="Pricing"><a href="#Pricing" class="headerlink" title="Pricing"></a>Pricing</h2><p>The minimum monthly cost to monitor a resource (CloudFront distribution, WorksSpaces directory, or a VPC) is about $7. But only if you limit the number of monitored city-networks to 100.</p><p>If you want to monitor more than 100 city-networks, you are paying $0.74 per 10,000 monitored city-networks per hour.</p><p>The <a href="https://aws.amazon.com/cloudwatch/pricing/" target="_blank" rel="noopener">pricing example provided by AWS</a> calculates monthly costs of $2600 to monitor 10 CloudFront distributions and 20 Virtual Private Clouds (VPCs) among 45,000 city-networks.</p><p>In our example of monitoring our blog, we pay $22 per month for 1 CloudFront distribution and about 300 city-networks.</p><blockquote><p>Be warned. Limit the maximum number of monitored city-networks to avoid unexpected costs.</p></blockquote><h2 id="Recieve-alerts-in-realtime"><a href="#Recieve-alerts-in-realtime" class="headerlink" title="Recieve alerts in realtime"></a>Recieve alerts in realtime</h2><p>Internet Monitor publishes events to EventBridge, allowing you to receive realtime alerts.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/06/internetmonitorig-realtime-alert@730w.webp 730w, /images/2023/06/internetmonitorig-realtime-alert@730w2x.webp 1460w, /images/2023/06/internetmonitorig-realtime-alert@610w.webp 610w, /images/2023/06/internetmonitorig-realtime-alert@610w2x.webp 1220w, /images/2023/06/internetmonitorig-realtime-alert@450w.webp 450w, /images/2023/06/internetmonitorig-realtime-alert@450w2x.webp 900w, /images/2023/06/internetmonitorig-realtime-alert@330w.webp 330w, /images/2023/06/internetmonitorig-realtime-alert@330w2x.webp 660w, /images/2023/06/internetmonitorig-realtime-alert@545w.webp 545w, /images/2023/06/internetmonitorig-realtime-alert@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/06/internetmonitorig-realtime-alert@730w.png 730w, /images/2023/06/internetmonitorig-realtime-alert@730w2x.png 1460w, /images/2023/06/internetmonitorig-realtime-alert@610w.png 610w, /images/2023/06/internetmonitorig-realtime-alert@610w2x.png 1220w, /images/2023/06/internetmonitorig-realtime-alert@450w.png 450w, /images/2023/06/internetmonitorig-realtime-alert@450w2x.png 900w, /images/2023/06/internetmonitorig-realtime-alert@330w.png 330w, /images/2023/06/internetmonitorig-realtime-alert@330w2x.png 660w, /images/2023/06/internetmonitorig-realtime-alert@545w.png 545w, /images/2023/06/internetmonitorig-realtime-alert@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/06/internetmonitorig-realtime-alert.png" alt="Internet Monitor sends alerts to EventBridge" title="Internet Monitor sends alerts to EventBridge"></picture></p><p>For example, I added a feature to marbot, our AWS monitoring solution, which configures an EventBridge rule, forwards alerts to marbot’s API, and displays the alert in Slack or Microsoft Teams. See <a href="https://marbot.io/help/monitoring-amazon-cloudwatch-internet-monitor.html" target="_blank" rel="noopener">Monitoring: Amazon CloudWatch Internet Monitor</a> for details.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p><strong>Amazon CloudWatch Internet Monitor</strong> provides insights into connectivity issues between Internet Service Providers (ISPs) and the AWS network. In my opinion, Internet Monitor is a nice-to-have for most web applications. Being aware of networking issues is helpful for customer care. Besides that, insights into networking issues might help make data-driven decisions about cloud architecture. Other than that, there is little that you can do when Internet Monitor informs you about a network degradation than waiting for the ISP and AWS to fix the issue. For us, monitoring our blog with Internet Monitor provides little value. Therefore, I will disable the monitor.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Now available: Book Amazon Web Services in Action 3rd Edition</title>
      <link>https://cloudonaut.io/book-aws-in-action-3rd-edition-now-available/</link>
      <description>
        <![CDATA[<p>We are happy to announce the official launch of our new book Amazon Web Services in Action 3rd Edition. The final version of the book is]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/rds/">rds</category>
      <guid isPermaLink="true">https://cloudonaut.io/book-aws-in-action-3rd-edition-now-available/</guid>
      <pubDate>Wed, 03 May 2023 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>We are happy to announce the official launch of our new book Amazon Web Services in Action 3rd Edition. The final version of the book is out now. We wrote the 1st edition back in 2015, and since then, we sold more than 30,000 copies, and the book has been translated into multiple languages, like Japanese and Portuguese. We are honored that our book has helped many get started with AWS.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/05/aws-in-action-3rd@730w.webp 730w, /images/2023/05/aws-in-action-3rd@730w2x.webp 1460w, /images/2023/05/aws-in-action-3rd@610w.webp 610w, /images/2023/05/aws-in-action-3rd@610w2x.webp 1220w, /images/2023/05/aws-in-action-3rd@450w.webp 450w, /images/2023/05/aws-in-action-3rd@450w2x.webp 900w, /images/2023/05/aws-in-action-3rd@330w.webp 330w, /images/2023/05/aws-in-action-3rd@330w2x.webp 660w, /images/2023/05/aws-in-action-3rd@545w.webp 545w, /images/2023/05/aws-in-action-3rd@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/05/aws-in-action-3rd@730w.jpg 730w, /images/2023/05/aws-in-action-3rd@730w2x.jpg 1460w, /images/2023/05/aws-in-action-3rd@610w.jpg 610w, /images/2023/05/aws-in-action-3rd@610w2x.jpg 1220w, /images/2023/05/aws-in-action-3rd@450w.jpg 450w, /images/2023/05/aws-in-action-3rd@450w2x.jpg 900w, /images/2023/05/aws-in-action-3rd@330w.jpg 330w, /images/2023/05/aws-in-action-3rd@330w2x.jpg 660w, /images/2023/05/aws-in-action-3rd@545w.jpg 545w, /images/2023/05/aws-in-action-3rd@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/05/aws-in-action-3rd.jpg" alt="Now available: Book Amazon Web Services in Action 3rd Edition" title="Now available: Book Amazon Web Services in Action 3rd Edition"></picture></p><h2 id="What’s-new"><a href="#What’s-new" class="headerlink" title="What’s new?"></a>What’s new?</h2><p>The 3rd edition contains a new chapter covering containers on AWS: <strong>Building modern architectures for the cloud: ECS, Fargate, and App Runner</strong>. The chapter starts with the simplest way to deploy containers on AWS: App Runner. After that, we dive into our favorite architecture consisting of ECS to orchestrate containers and Fargate providing a Serverless container infrastructure.</p><p>Besides adding a new chapter, we have entirely rewritten chapter 15: <strong>Automating deployment: CodeDeploy, CloudFormation, and Packer</strong>. Automating the deployments is key to success in the cloud. Therefore, chapter 15 introduces three different options:</p><ul><li>CodeDeploy to roll out new versions of an application among a fleet of EC2 instances.</li><li>Packer to bundle the latest version of an application into an AMI.</li><li>CloudFormation to orchestrate rolling updates of EC2 instances managed by an auto-scaling group.</li></ul><h2 id="What’s-in-the-book"><a href="#What’s-in-the-book" class="headerlink" title="What’s in the book?"></a>What’s in the book?</h2><p>Amazon Web Services in Action is an in-depth guide to AWS. The book consists of four parts.</p><ol><li>Getting started</li><li>Building virtual infrastructure</li><li>Storing data in the cloud</li><li>Architecting on AWS</li></ol><p>Here is a list of the services we cover in our book: EC2, VPC, IAM, Lambda, S3, Glacier, EBS, EFS, RDS, DynamoDB, ElastiCache, CloudWatch, Auto Scaling, Elastic Loadbalancing, SQS, ECS, Fargate, App Runner, CloudFormation, and CodeDeploy.</p><p>Besides that, the book includes our compressed learnings while building on AWS for ourselves and our consulting clients. We cover best practices to build secure and reliable systems in the cloud.</p><p>Infrastructure as Code is a game changer! We use CloudFormation for most examples in our book.</p><h2 id="Buy-now"><a href="#Buy-now" class="headerlink" title="Buy now!"></a>Buy now!</h2><p>As of today, Amazon Web Services in Action is on sale.</p><p>Get our book directly from our publisher, Manning, the best option to buy the ebook.</p><blockquote><p>Use coupon code <code>wittig40</code> to get 40% off all formats when buying from Manning.</p></blockquote><p><a href="https://bit.ly/amazon-web-services-in-action-3rd-edition" target="_blank" rel="noopener">Buy from Manning</a></p><p>Besides that, Amazon.com also sells our book, which is a good option if you want to buy the printed book at low shipping costs.</p><p><a href="https://a.co/d/512tfax" target="_blank" rel="noopener">Buy from Amazon.com</a></p><p>Alternatively, ask your local bookstore. They should be able to order our book within the next few weeks.</p><h2 id="Thank-you"><a href="#Thank-you" class="headerlink" title="Thank you!"></a>Thank you!</h2><p>Thank you for buying and reading our book. Special thanks to those who purchased the book during the MEAP (Manning Early Access Program) phase for trusting us and providing valuable feedback.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How we built bucketAV powered by Sophos</title>
      <link>https://cloudonaut.io/how-we-built-bucketav-powered-by-sophos/</link>
      <description>
        <![CDATA[<p>This is the behind-the-scenes story of our latest product launch <a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/auto-scaling/">auto-scaling</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-we-built-bucketav-powered-by-sophos/</guid>
      <pubDate>Thu, 27 Apr 2023 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>This is the behind-the-scenes story of our latest product launch <a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV powered by Sophos, a malware protection solution for Amazon S3</a>. We share insights into building and selling a product on the AWS Marketplace.</p><p>Our story began in 2015 when we published an open-source solution to scan S3 buckets for malware. Because the open-source project was a huge success, we built and sold a similar solution on the AWS Marketplace. In 2019, we released bucketAV powered by ClamAV. Today, over 1,000 customers rely on bucketAV to protect their S3 buckets from malware and we are happy to announce bucketAV powered by Sophos.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/04/how-we-built-bucketav-powered-by-sophos-title@730w.webp 730w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@730w2x.webp 1460w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@610w.webp 610w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@610w2x.webp 1220w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@450w.webp 450w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@450w2x.webp 900w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@330w.webp 330w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@330w2x.webp 660w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@545w.webp 545w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/04/how-we-built-bucketav-powered-by-sophos-title@730w.jpg 730w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@730w2x.jpg 1460w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@610w.jpg 610w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@610w2x.jpg 1220w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@450w.jpg 450w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@450w2x.jpg 900w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@330w.jpg 330w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@330w2x.jpg 660w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@545w.jpg 545w, /images/2023/04/how-we-built-bucketav-powered-by-sophos-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/04/how-we-built-bucketav-powered-by-sophos-title.jpg" alt="How we built bucketAV powered by Sophos" title="How we built bucketAV powered by Sophos"></picture></p><p>For context, here’s an overview of how bucketAV works. bucketAV scans S3 objects on-demand or based on a recurring schedule.</p><ol><li>Create a scan job based on an upload event or schedule.</li><li>Download the file from S3.</li><li>Scan file for malware.</li><li>Report scan result.</li><li>Trigger automated mitigation.</li></ol><h2 id="Making-data-available-worldwide"><a href="#Making-data-available-worldwide" class="headerlink" title="Making data available worldwide"></a>Making data available worldwide</h2><p>An anti-malware engine like Sophos relies on a database containing information about known threats. It is crucial to regularly update the database. Our customers run bucketAV in all commercial regions provided by AWS. Therefore, we need to distribute the data worldwide.</p><p>We came up with the following solution to make data available worldwide at low costs:</p><ol><li>We created S3 buckets in all commercial regions.</li><li>We launched an EC2 instance in <code>eu-west-1</code>.</li><li>We configured AWS Systems Manager to run a recurring job on the EC2 instance.</li><li>The recurring job downloads the latest threat database.</li><li>The recurring job uploads the latest threat database to all S3 buckets.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/04/s3-replicate-worldwide@730w.webp 730w, /images/2023/04/s3-replicate-worldwide@730w2x.webp 1460w, /images/2023/04/s3-replicate-worldwide@610w.webp 610w, /images/2023/04/s3-replicate-worldwide@610w2x.webp 1220w, /images/2023/04/s3-replicate-worldwide@450w.webp 450w, /images/2023/04/s3-replicate-worldwide@450w2x.webp 900w, /images/2023/04/s3-replicate-worldwide@330w.webp 330w, /images/2023/04/s3-replicate-worldwide@330w2x.webp 660w, /images/2023/04/s3-replicate-worldwide@545w.webp 545w, /images/2023/04/s3-replicate-worldwide@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/04/s3-replicate-worldwide@730w.png 730w, /images/2023/04/s3-replicate-worldwide@730w2x.png 1460w, /images/2023/04/s3-replicate-worldwide@610w.png 610w, /images/2023/04/s3-replicate-worldwide@610w2x.png 1220w, /images/2023/04/s3-replicate-worldwide@450w.png 450w, /images/2023/04/s3-replicate-worldwide@450w2x.png 900w, /images/2023/04/s3-replicate-worldwide@330w.png 330w, /images/2023/04/s3-replicate-worldwide@330w2x.png 660w, /images/2023/04/s3-replicate-worldwide@545w.png 545w, /images/2023/04/s3-replicate-worldwide@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/04/s3-replicate-worldwide.png" alt="Replicating data worldwide: S3, EC2, Systems Manager" title="Replicating data worldwide: S3, EC2, Systems Manager"></picture></p><p>Why not use CloudFront or a single S3 bucket? Because when EC2 instances download data from an S3 bucket in the same region, we are not paying for the traffic. So distributing our data among S3 buckets in each region is the cheapest option.</p><p>Why not use S3 Cross-Region Replication (CRR)? First, CRR with replication time control costs $0.015 per GB. Second, CRR does not guarantee the replication order, which is essential in our scenario.</p><h2 id="Metering-and-billing"><a href="#Metering-and-billing" class="headerlink" title="Metering and billing"></a>Metering and billing</h2><p>bucketAV is a solution bundled into an AMI and a CloudFormation template. The AWS Marketplace supports different <a href="https://docs.aws.amazon.com/marketplace/latest/userguide/pricing-ami-products.html" target="_blank" rel="noopener">pricing models</a>. Typically, you pay hourly for every EC2 instance launched from the AMI sold through the AWS Marketplace. However, for bucketAV powered by Sophos, we decided to use a different approach. We are charging for the processed data. </p><p>How does that work? Every hour, bucketAV reports the amount of processed data to the AWS Marketplace. To do so, bucketAV calls the AWS Marketplace Metering Service API from each EC2 instance. To make that work, each EC2 instance needs an IAM role granting access to <code>aws-marketplace:MeterUsage</code>. Besides that, each EC2 instance must be able to reach the API endpoint <code>https://metering.marketplace.$REGION.amazonaws.com</code>, which is not yet covered by a VPC endpoint, unfortunately.</p><p>But how to test usage-based pricing? While submitting a new product to the AWS Marketplace, AWS creates a restricted listing that is only accessible from your AWS accounts. We used that period to test and fix our metering implementation.</p><h2 id="Optimizing-performance"><a href="#Optimizing-performance" class="headerlink" title="Optimizing performance"></a>Optimizing performance</h2><p>When running performance tests with bucketAV powered by Sophos, we noticed that while downloading files from S3 consumed most of the time, the EC2 instances did not reach the maximum network bandwidth. Especially when downloading files with a maximum file size of 5 TB, the EC2 instance was idling a lot. When debugging the issue, we found out that AWS recommends downloading files in chunks and parallel to provide maximum performance (see <a href="https://docs.aws.amazon.com/whitepapers/latest/s3-optimizing-performance-best-practices/use-byte-range-fetches.html" target="_blank" rel="noopener">Performance Guidelines for Amazon S3: Use Byte-Range Fetches</a>).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/04/s3-accelerate-download@730w.webp 730w, /images/2023/04/s3-accelerate-download@730w2x.webp 1460w, /images/2023/04/s3-accelerate-download@610w.webp 610w, /images/2023/04/s3-accelerate-download@610w2x.webp 1220w, /images/2023/04/s3-accelerate-download@450w.webp 450w, /images/2023/04/s3-accelerate-download@450w2x.webp 900w, /images/2023/04/s3-accelerate-download@330w.webp 330w, /images/2023/04/s3-accelerate-download@330w2x.webp 660w, /images/2023/04/s3-accelerate-download@545w.webp 545w, /images/2023/04/s3-accelerate-download@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/04/s3-accelerate-download@730w.png 730w, /images/2023/04/s3-accelerate-download@730w2x.png 1460w, /images/2023/04/s3-accelerate-download@610w.png 610w, /images/2023/04/s3-accelerate-download@610w2x.png 1220w, /images/2023/04/s3-accelerate-download@450w.png 450w, /images/2023/04/s3-accelerate-download@450w2x.png 900w, /images/2023/04/s3-accelerate-download@330w.png 330w, /images/2023/04/s3-accelerate-download@330w2x.png 660w, /images/2023/04/s3-accelerate-download@545w.png 545w, /images/2023/04/s3-accelerate-download@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/04/s3-accelerate-download.png" alt="Use Byte-Range Fetches to accelerate S3 GetObject" title="Use Byte-Range Fetches to accelerate S3 GetObject"></picture></p><p>Unfortunately, the AWS SDK for JavaScript (v2 and v3) does not support byte-range fetches when downloading data from S3. We also could not find any up-to-date libraries, so we implemented and open-sourced our own implementation: <a href="https://github.com/widdix/s3-getobject-accelerator" target="_blank" rel="noopener">widdix&#x2F;s3-getobject-accelerator</a>. The following example downloads an object from S3 by downloading four parts of 8 MB in parallel.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123;createWriteStream&#125; = <span class="built_in">require</span>(<span class="string">&#x27;node:fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> &#123;pipeline&#125; = <span class="built_in">require</span>(<span class="string">&#x27;node:stream&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> &#123;download&#125; = <span class="built_in">require</span>(<span class="string">&#x27;s3-getobject-accelerator&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="title function_">pipeline</span>(</span><br><span class="line">  <span class="title function_">download</span>(&#123;</span><br><span class="line">    <span class="attr">bucket</span>: <span class="string">&#x27;bucket&#x27;</span>,</span><br><span class="line">    <span class="attr">key</span>: <span class="string">&#x27;key&#x27;</span>,</span><br><span class="line">    <span class="attr">version</span>: <span class="string">&#x27;optional version&#x27;</span></span><br><span class="line">  &#125;, &#123;</span><br><span class="line">    <span class="attr">partSizeInMegabytes</span>: <span class="number">8</span>,</span><br><span class="line">    <span class="attr">concurrency</span>: <span class="number">4</span></span><br><span class="line">  &#125;).<span class="title function_">readStream</span>(),</span><br><span class="line">  <span class="title function_">createWriteStream</span>(<span class="string">&#x27;/tmp/test&#x27;</span>), <span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;something went wrong&#x27;</span>, err);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;done&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>Besides that, we spent much time running performance benchmarks to compare different EC2 instance types. We discovered that a <code>c5.large</code> improves performance by 20% compared to an <code>m5.large</code> for our workload. Also, a <code>c6i.large</code> performed 30% better than a <code>m5.large</code> in our scenario. The lesson learned is that benchmarking different instance types pays off.</p><h2 id="Reducing-costs"><a href="#Reducing-costs" class="headerlink" title="Reducing costs"></a>Reducing costs</h2><p>Some of our customers scan a few MBs others scan TBs of data. As we use SQS to store all scan jobs, scaling horizontally by adding and removing EC2 instances is obvious. To do so, we are using an auto-scaling group. To reduce costs, we provide the option to run bucketAV on spot instances. However, an auto-scaling group does not support replacing spot instances with on-demand instances in case spot capacity is unavailable for longer.</p><p>But, we found a way to fallback to on-demand EC2 instances if spot capacity is unavailable:</p><ol><li>Create an auto-scaling group <code>spot</code> to launch spot instances.</li><li>Create an auto-scaling group <code>ondemand</code> to launch on-demand instances.</li><li>Scale the desired auto-scaling group <code>spot</code> capacity as usual.</li><li>Scale the desired capacity of the auto-scaling group <code>ondemand</code> based on the difference between the desired and actual size of the auto-scaling group <code>spot</code>.</li></ol><p>Check out Michael’s blog post <a href="/fallback-to-on-demand-ec2-instances-if-spot-capacity-is-unavailable">Fallback to on-demand EC2 instances if spot capacity is unavailable</a> for more details and code examples.</p><h2 id="Terminating-gracefully"><a href="#Terminating-gracefully" class="headerlink" title="Terminating gracefully"></a>Terminating gracefully</h2><p>As we are using auto-scaling groups and spot instances, there are three main reasons why an EC2 instance running bucketAV gets terminated:</p><ol><li>The auto-scaling group terminates an instance because of a scale-in event.</li><li>The auto-scaling group terminates an instance during a rolling update initiated by a CloudFormation.</li><li>AWS interrupts a spot instance.</li></ol><p>In all these scenarios, we want to ensure bucketAV terminates gracefully. Most importantly, the instance should keep running to complete the currently running scan tasks or at least flush reporting and metering data.</p><p>We’ve implemented graceful termination by making use of auto-scaling lifecycle hooks.</p><ol><li>On each EC2 instance, bucketAV polls the metadata service for the current auto-scaling and spot state.</li><li>In case bucketAV detects that the instance has been marked for termination, bucketAV shuts down gracefully.</li><li>bucketAV waits until all running scan jobs are complete. During that time, bucketAV sends a heartbeat to the auto-scaling group.</li><li>After all jobs are finished, bucketAV completes the lifecycle hook.</li></ol><p>The following code snippet shows how to fetch the auto-scaling target lifecycle state from the IMDSv2. We use parts of our library <a href="https://github.com/widdix/s3-getobject-accelerator" target="_blank" rel="noopener">s3-getobject-accelerator</a> as the AWS JavaScript SDK v2 <a href="https://github.com/aws/aws-sdk-js/issues/3584" target="_blank" rel="noopener">does not support IMDSv2 out of the box</a>.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123;imds&#125; = <span class="built_in">require</span>(<span class="string">&#x27;s3-getobject-accelerator&#x27;</span>);</span><br></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">imds</span>(<span class="string">&#x27;/latest/meta-data/autoscaling/target-lifecycle-state&#x27;</span>, <span class="variable constant_">IMDS_TIMEOUT</span>, <span class="function">(<span class="params">err, data</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (data === <span class="string">&#x27;Terminated&#x27;</span>) &#123;</span><br><span class="line">    <span class="comment">// The ASG is terminating the instance, the target lifecycle state is Terminated.&#x27;</span></span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (data === <span class="string">&#x27;InService&#x27;</span>) &#123;</span><br><span class="line">    <span class="comment">// The instance is up and running.</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>And here is how you fetch the notification about a spot instance interruption from IMDSv2.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="title function_">imds</span>(<span class="string">&#x27;/latest/meta-data/spot/instance-action&#x27;</span>, <span class="variable constant_">IMDS_TIMEOUT</span>, <span class="function">(<span class="params">err, data</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err.<span class="property">statusCode</span> === <span class="number">404</span>) &#123;</span><br><span class="line">      <span class="comment">// 404 is the expected answer, as long as the instance has not been interrupted</span></span><br><span class="line">    &#125; &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// TODO handle error</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// The spot instance got interrupted and will be terminated within 2 minutes</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>The following snippet shows how to configure an auto-scaling lifecycle hook with CloudFormation.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">AutoScalingGroup:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::AutoScaling::AutoScalingGroup&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">LaunchTemplate:</span> <span class="string">&#x27;...&#x27;</span> </span><br><span class="line">    <span class="attr">MaxSize:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">MinSize:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">VPCZoneIdentifier:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">SubnetAPrivate</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">SubnetBPrivate</span></span><br><span class="line">    <span class="attr">LifecycleHookSpecificationList:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">DefaultResult:</span> <span class="string">&#x27;CONTINUE&#x27;</span></span><br><span class="line">    <span class="attr">HeartbeatTimeout:</span> <span class="number">360</span></span><br><span class="line">    <span class="attr">LifecycleHookName:</span> <span class="string">&#x27;terminate_gracefully&#x27;</span></span><br><span class="line">    <span class="attr">LifecycleTransition:</span> <span class="string">&#x27;autoscaling:EC2_INSTANCE_TERMINATING&#x27;</span></span><br></pre></td></tr></table></figure><p>With the <code>autoscaling:EC2_INSTANCE_TERMINATING</code> lifecycle hook in place, the auto-scaling group will wait until someone, for example, the instance itself, completes the lifecycle action before terminating the instance. Sending a heartbeat is required to tell the auto-scaling group that the instance is still alive. The following code snippet shows how to send a heartbeat and complete the lifecycle option using the AWS SDK for JavaScript v2.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> autoscaling = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">AutoScaling</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2011-01-01&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> autoScalingGroupName = <span class="string">&#x27;...&#x27;</span>; <span class="comment">// the name of the auto-scaling group</span></span><br><span class="line"><span class="keyword">let</span> instanceId = <span class="string">&#x27;...&#x27;</span>; <span class="comment">// the ID of the EC2 instance</span></span><br><span class="line"><span class="keyword">let</span> lifecycleHookName = <span class="string">&#x27;...&#x27;</span> <span class="comment">// the name of the auto-scaling lifecycle hook</span></span><br><span class="line"></span><br><span class="line">heartbeatIntervalId = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  autoscaling.<span class="title function_">recordLifecycleActionHeartbeat</span>(&#123;</span><br><span class="line">  <span class="title class_">AutoScalingGroupName</span>: autoScalingGroupName,</span><br><span class="line">  <span class="title class_">InstanceId</span>: instanceId,</span><br><span class="line">  <span class="title class_">LifecycleHookName</span>: lifecycleHookName</span><br><span class="line">  &#125;, <span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// Handle error</span></span><br><span class="line">  &#125;);</span><br><span class="line">&#125;, <span class="number">5</span>*<span class="number">60</span>*<span class="number">1000</span>);</span><br><span class="line"></span><br><span class="line">autoscaling.<span class="title function_">completeLifecycleAction</span>(&#123;</span><br><span class="line">  <span class="title class_">AutoScalingGroupName</span>: autoScalingGroupName,</span><br><span class="line">  <span class="title class_">InstanceId</span>: instanceId,</span><br><span class="line">  <span class="title class_">LifecycleActionResult</span>: <span class="string">&#x27;CONTINUE&#x27;</span>,</span><br><span class="line">  <span class="title class_">LifecycleHookName</span>: <span class="string">&#x27;lifecycleHookName&#x27;</span></span><br><span class="line">&#125;, cb);</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>That’s what we learned while building <a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV powered by Sophos, a malware protection solution for Amazon S3</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Fallback to on-demand EC2 instances if spot capacity is unavailable</title>
      <link>https://cloudonaut.io/fallback-to-on-demand-ec2-instances-if-spot-capacity-is-unavailable/</link>
      <description>
        <![CDATA[<p>In recent months, I was again reminded that EC2 spot capacity is not always available. For years, I was looking for a safety net for my s]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/auto-scaling/">auto-scaling</category>
      <guid isPermaLink="true">https://cloudonaut.io/fallback-to-on-demand-ec2-instances-if-spot-capacity-is-unavailable/</guid>
      <pubDate>Tue, 21 Mar 2023 10:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>In recent months, I was again reminded that EC2 spot capacity is not always available. For years, I was looking for a safety net for my spot-based Auto Scaling Groups (ASGs). If spot capacity is unavailable, launch on-demand EC2 instances and replace them with spot as soon as spot capacity is back. After many proofs of concept, I want to share my approach to the problem.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/03/safety-net@730w.webp 730w, /images/2023/03/safety-net@730w2x.webp 1460w, /images/2023/03/safety-net@610w.webp 610w, /images/2023/03/safety-net@610w2x.webp 1220w, /images/2023/03/safety-net@450w.webp 450w, /images/2023/03/safety-net@450w2x.webp 900w, /images/2023/03/safety-net@330w.webp 330w, /images/2023/03/safety-net@330w2x.webp 660w, /images/2023/03/safety-net@545w.webp 545w, /images/2023/03/safety-net@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/03/safety-net@730w.jpg 730w, /images/2023/03/safety-net@730w2x.jpg 1460w, /images/2023/03/safety-net@610w.jpg 610w, /images/2023/03/safety-net@610w2x.jpg 1220w, /images/2023/03/safety-net@450w.jpg 450w, /images/2023/03/safety-net@450w2x.jpg 900w, /images/2023/03/safety-net@330w.jpg 330w, /images/2023/03/safety-net@330w2x.jpg 660w, /images/2023/03/safety-net@545w.jpg 545w, /images/2023/03/safety-net@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/03/safety-net.jpg" alt="Safety net" title="Safety net"></picture></p><p>I assume your existing ASG is configured to spread the load across as many availability zones and instance types as possible. Besides that, I encourage you to enable <a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-capacity-rebalancing.html" target="_blank" rel="noopener">Capacity Rebalancing</a> to handle spot interruptions. Besides that, add the following resources to implement the on-demand safety net:</p><ul><li>Fallback ASG to launch on-demand EC2 instances</li><li>Two step scaling policies to scale up&#x2F;down the fallback ASG</li><li>Two CloudWatch alarms to trigger the scaling policies</li></ul><h2 id="Configure-existing-ASG"><a href="#Configure-existing-ASG" class="headerlink" title="Configure existing ASG"></a>Configure existing ASG</h2><p>Enable your existing ASG to emit the <a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-cloudwatch-monitoring.html" target="_blank" rel="noopener">CloudWatch metrics</a> <code>GroupInServiceInstances</code> and <code>GroupDesiredCapacity</code>.</p><p>In CloudFormation:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">SpotAutoScalingGroup:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::AutoScaling::AutoScalingGroup&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line">    <span class="attr">CapacityRebalance:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">MaxSize:</span> <span class="number">10</span></span><br><span class="line">    <span class="attr">MinSize:</span> <span class="number">2</span></span><br><span class="line">    <span class="attr">MixedInstancesPolicy:</span></span><br><span class="line">      <span class="comment"># [...]</span></span><br><span class="line">      <span class="attr">InstancesDistribution:</span></span><br><span class="line">          <span class="attr">OnDemandAllocationStrategy:</span> <span class="string">prioritized</span></span><br><span class="line">          <span class="attr">OnDemandBaseCapacity:</span> <span class="number">0</span></span><br><span class="line">          <span class="attr">OnDemandPercentageAboveBaseCapacity:</span> <span class="number">0</span></span><br><span class="line">          <span class="attr">SpotAllocationStrategy:</span> <span class="string">&#x27;capacity-optimized-prioritized&#x27;</span></span><br><span class="line">    <span class="attr">MetricsCollection:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Granularity:</span> <span class="string">1Minute</span></span><br><span class="line">      <span class="attr">Metrics:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">GroupInServiceInstances</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">GroupDesiredCapacity</span></span><br></pre></td></tr></table></figure><h2 id="Configure-additional-fallback-ASG"><a href="#Configure-additional-fallback-ASG" class="headerlink" title="Configure additional fallback ASG"></a>Configure additional fallback ASG</h2><p>Add a new ASG to spin up on-demand capacity. Use the same launch template&#x2F;configuration as your spot ASG.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">FallbackAutoScalingGroup:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::AutoScaling::AutoScalingGroup&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line">    <span class="attr">MetricsCollection:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Granularity:</span> <span class="string">1Minute</span></span><br><span class="line">      <span class="attr">Metrics:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">GroupInServiceInstances</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">GroupDesiredCapacity</span></span><br><span class="line">    <span class="attr">MaxSize:</span> <span class="number">10</span> <span class="comment"># set this to the same value as your spot max size</span></span><br><span class="line">    <span class="attr">MinSize:</span> <span class="number">0</span></span><br></pre></td></tr></table></figure><h2 id="Create-CloudWatch-alarms-to-trigger-auto-scaling"><a href="#Create-CloudWatch-alarms-to-trigger-auto-scaling" class="headerlink" title="Create CloudWatch alarms to trigger auto-scaling"></a>Create CloudWatch alarms to trigger auto-scaling</h2><p>The trick is to use the following formula to calculate the number of instances that need to be added&#x2F;removed from the fallback ASG:</p><figure class="highlight applescript"><table><tr><td class="code"><pre><span class="line">desired spot-<span class="built_in">running</span> spot-desired fallback</span><br></pre></td></tr></table></figure><p>The following table helps you to understand the formula with some examples:</p><table class="table table-striped table-responsive"><thead><tr><th>example</th><th>desired spot</th><th>running spot</th><th>desired fallback</th><th>result</th></tr></thead><tbody><tr><td>all good, spot capacity is available</td><td>4</td><td>4</td><td>0</td><td>0</td></tr><tr><td>spot capacity is missing</td><td>4</td><td>3</td><td>0</td><td>1</td></tr><tr><td>spot capacity is missing, but fallback capacity is already started</td><td>4</td><td>3</td><td>1</td><td>0</td></tr><tr><td>spot capacity is available; fallback capacity can be removed</td><td>4</td><td>4</td><td>1</td><td>-1</td></tr></tbody></table><p>The following logic is needed to work with the result of the formula:</p><p>If <code>result &gt; 0</code>: increase the desired capacity of the fallback ASG by <code>result</code>.<br>Else if <code>result &lt; 0</code>: decrease the desired capacity of the fallback ASG by <code>result</code>.<br>Else: do nothing.</p><p>The logic can be implemented with CloudWatch alarms and step scaling policies.</p><p>CloudWatch alarms trigger the step scaling policies to scale up&#x2F;down the fallback ASG. To reduce noise caused by auto-scaling activities in the spot ASG, I configured the alarms only to fire if the formula is negative&#x2F;positive three times in a row. The following two CloudWatch alarms are mostly identical, except for the <code>ComparisonOperator</code>.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">FallbackScaleUpAlarm:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">FallbackScaleUp</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">3</span> <span class="comment"># if for three times in a row...</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">0</span>         <span class="comment"># ...the formula result is &gt; 0, trigger alarm</span></span><br><span class="line">    <span class="attr">TreatMissingData:</span> <span class="string">notBreaching</span></span><br><span class="line">    <span class="attr">Metrics:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Id:</span> <span class="string">running</span> <span class="comment"># get the value for running spot</span></span><br><span class="line">      <span class="attr">Label:</span> <span class="string">running</span></span><br><span class="line">      <span class="attr">MetricStat:</span></span><br><span class="line">        <span class="attr">Metric:</span></span><br><span class="line">          <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/AutoScaling&#x27;</span></span><br><span class="line">          <span class="attr">MetricName:</span> <span class="string">GroupInServiceInstances</span></span><br><span class="line">          <span class="attr">Dimensions:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">AutoScalingGroupName</span></span><br><span class="line">            <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">SpotAutoScalingGroup</span></span><br><span class="line">        <span class="attr">Period:</span> <span class="number">60</span></span><br><span class="line">        <span class="attr">Stat:</span> <span class="string">Maximum</span></span><br><span class="line">      <span class="attr">ReturnData:</span> <span class="literal">false</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Id:</span> <span class="string">desired</span>  <span class="comment"># get the value for desired spot</span></span><br><span class="line">      <span class="attr">Label:</span> <span class="string">desired</span></span><br><span class="line">      <span class="attr">MetricStat:</span></span><br><span class="line">        <span class="attr">Metric:</span></span><br><span class="line">          <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/AutoScaling&#x27;</span></span><br><span class="line">          <span class="attr">MetricName:</span> <span class="string">GroupDesiredCapacity</span></span><br><span class="line">          <span class="attr">Dimensions:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">AutoScalingGroupName</span></span><br><span class="line">            <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">SpotAutoScalingGroup</span></span><br><span class="line">        <span class="attr">Period:</span> <span class="number">60</span></span><br><span class="line">        <span class="attr">Stat:</span> <span class="string">Maximum</span></span><br><span class="line">      <span class="attr">ReturnData:</span> <span class="literal">false</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Id:</span> <span class="string">desiredfallback</span> <span class="comment"># get the value for desired fallback</span></span><br><span class="line">      <span class="attr">Label:</span> <span class="string">desiredfallback</span></span><br><span class="line">      <span class="attr">MetricStat:</span></span><br><span class="line">        <span class="attr">Metric:</span></span><br><span class="line">          <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/AutoScaling&#x27;</span></span><br><span class="line">          <span class="attr">MetricName:</span> <span class="string">GroupDesiredCapacity</span></span><br><span class="line">          <span class="attr">Dimensions:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">AutoScalingGroupName</span></span><br><span class="line">            <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">FallbackAutoScalingGroup</span></span><br><span class="line">        <span class="attr">Period:</span> <span class="number">60</span></span><br><span class="line">        <span class="attr">Stat:</span> <span class="string">Maximum</span></span><br><span class="line">      <span class="attr">ReturnData:</span> <span class="literal">false</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Expression:</span> <span class="string">&#x27;desired-running-desiredfallback&#x27;</span> <span class="comment"># this is the formula presented earlier</span></span><br><span class="line">      <span class="attr">Id:</span> <span class="string">e1</span></span><br><span class="line">      <span class="attr">Label:</span> <span class="string">&#x27;fallback&#x27;</span></span><br><span class="line">      <span class="attr">ReturnData:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">FallbackScaleDownAlarm:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">FallbackScaleDown</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">LessThanThreshold</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">3</span> <span class="comment"># if for three times in a row...</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">0</span>         <span class="comment"># ...the formula result is &lt; 0, trigger alarm</span></span><br><span class="line">    <span class="attr">TreatMissingData:</span> <span class="string">notBreaching</span></span><br><span class="line">    <span class="attr">Metrics:</span></span><br><span class="line">      <span class="comment"># [...] same as in FallbackScaleUpAlarm</span></span><br></pre></td></tr></table></figure><p>In an ideal world, we could use the result of the formula to change the desired capacity directly. Remember, the formula calculates the instances that need to be added (positive values)&#x2F;removed (negative values) from the fallback ASG. Unfortunately, we must take a slight detour via a step scaling policy.</p><ol><li>The CloudWatch alarm triggers the step scaling policy with the formula result.</li><li>The step scaling policy translates the received value into a change in capacity (adjustment)…</li><li>…and updates the desired count of the ASG.</li></ol><p>You can configure how the step scaling policy transforms the value from CloudWatch into a change in capacity by defining step adjustments. A step is defined by a lower and upper bound and a change in capacity.</p><p>I use the following steps to translate from the formula result to a change in desired capacity:</p><table><thead><tr><th>policy</th><th>range</th><th>change in desired capacity</th></tr></thead><tbody><tr><td>up</td><td>0 &lt;&#x3D; result &lt; 2</td><td>+1</td></tr><tr><td>up</td><td>2 &lt;&#x3D; result &lt; 3</td><td>+2</td></tr><tr><td>up</td><td>3 &lt;&#x3D; result &lt; 4</td><td>+3</td></tr><tr><td>up</td><td>4 &lt;&#x3D; result &lt; 5</td><td>+4</td></tr><tr><td>up</td><td>5 &lt;&#x3D; result &lt; 10</td><td>+5</td></tr><tr><td>up</td><td>10 &lt;&#x3D; result &lt; 25</td><td>+10</td></tr><tr><td>up</td><td>25 &lt;&#x3D; result &lt; +infinity</td><td>+25</td></tr><tr><td>down</td><td>0 &gt;&#x3D; fallback &gt; -2</td><td>-1</td></tr><tr><td>down</td><td>-2 &gt;&#x3D; fallback &gt; -3</td><td>-2</td></tr><tr><td>down</td><td>-3 &gt;&#x3D; fallback &gt; -4</td><td>-3</td></tr><tr><td>down</td><td>-4 &gt;&#x3D; fallback &gt; -5</td><td>-4</td></tr><tr><td>down</td><td>-5 &gt;&#x3D; fallback &gt; -infinity</td><td>-5</td></tr></tbody></table><blockquote><p>You can define up to 20 adjustments per step scaling policy.</p></blockquote><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">FallbackScaleUp:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::AutoScaling::ScalingPolicy&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AdjustmentType:</span> <span class="string">ChangeInCapacity</span></span><br><span class="line">    <span class="attr">AutoScalingGroupName:</span> <span class="type">!Ref</span> <span class="string">FallbackAutoScalingGroup</span></span><br><span class="line">    <span class="attr">EstimatedInstanceWarmup:</span> <span class="number">300</span></span><br><span class="line">    <span class="attr">MetricAggregationType:</span> <span class="string">Average</span></span><br><span class="line">    <span class="attr">PolicyType:</span> <span class="string">StepScaling</span></span><br><span class="line">    <span class="attr">StepAdjustments:</span> <span class="comment"># the lower bound is inclusive and the upper bound is exclusive</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">MetricIntervalLowerBound:</span> <span class="number">0</span></span><br><span class="line">      <span class="attr">MetricIntervalUpperBound:</span> <span class="number">2</span></span><br><span class="line">      <span class="attr">ScalingAdjustment:</span> <span class="number">1</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">MetricIntervalLowerBound:</span> <span class="number">2</span></span><br><span class="line">      <span class="attr">MetricIntervalUpperBound:</span> <span class="number">3</span></span><br><span class="line">      <span class="attr">ScalingAdjustment:</span> <span class="number">2</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">MetricIntervalLowerBound:</span> <span class="number">3</span></span><br><span class="line">      <span class="attr">MetricIntervalUpperBound:</span> <span class="number">4</span></span><br><span class="line">      <span class="attr">ScalingAdjustment:</span> <span class="number">3</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">MetricIntervalLowerBound:</span> <span class="number">4</span></span><br><span class="line">      <span class="attr">MetricIntervalUpperBound:</span> <span class="number">5</span></span><br><span class="line">      <span class="attr">ScalingAdjustment:</span> <span class="number">4</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">MetricIntervalLowerBound:</span> <span class="number">5</span></span><br><span class="line">      <span class="attr">MetricIntervalUpperBound:</span> <span class="number">10</span></span><br><span class="line">      <span class="attr">ScalingAdjustment:</span> <span class="number">5</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">MetricIntervalLowerBound:</span> <span class="number">10</span></span><br><span class="line">      <span class="attr">MetricIntervalUpperBound:</span> <span class="number">25</span></span><br><span class="line">      <span class="attr">ScalingAdjustment:</span> <span class="number">10</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">MetricIntervalLowerBound:</span> <span class="number">25</span></span><br><span class="line">      <span class="attr">ScalingAdjustment:</span> <span class="number">25</span></span><br><span class="line"><span class="attr">FallbackScaleDown:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::AutoScaling::ScalingPolicy&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AdjustmentType:</span> <span class="string">ChangeInCapacity</span></span><br><span class="line">    <span class="attr">AutoScalingGroupName:</span> <span class="type">!Ref</span> <span class="string">FallbackAutoScalingGroup</span></span><br><span class="line">    <span class="attr">EstimatedInstanceWarmup:</span> <span class="number">300</span></span><br><span class="line">    <span class="attr">MetricAggregationType:</span> <span class="string">Average</span></span><br><span class="line">    <span class="attr">PolicyType:</span> <span class="string">StepScaling</span></span><br><span class="line">    <span class="attr">StepAdjustments:</span> <span class="comment"># the lower bound is exclusive and the upper bound is inclusive</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">MetricIntervalUpperBound:</span> <span class="number">0</span></span><br><span class="line">      <span class="attr">MetricIntervalLowerBound:</span> <span class="number">-2</span></span><br><span class="line">      <span class="attr">ScalingAdjustment:</span> <span class="number">-1</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">MetricIntervalUpperBound:</span> <span class="number">-2</span></span><br><span class="line">      <span class="attr">MetricIntervalLowerBound:</span> <span class="number">-3</span></span><br><span class="line">      <span class="attr">ScalingAdjustment:</span> <span class="number">-2</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">MetricIntervalUpperBound:</span> <span class="number">-3</span></span><br><span class="line">      <span class="attr">MetricIntervalLowerBound:</span> <span class="number">-4</span></span><br><span class="line">      <span class="attr">ScalingAdjustment:</span> <span class="number">-3</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">MetricIntervalUpperBound:</span> <span class="number">-4</span></span><br><span class="line">      <span class="attr">MetricIntervalLowerBound:</span> <span class="number">-5</span></span><br><span class="line">      <span class="attr">ScalingAdjustment:</span> <span class="number">-4</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">MetricIntervalUpperBound:</span> <span class="number">-5</span></span><br><span class="line">      <span class="attr">ScalingAdjustment:</span> <span class="number">-5</span></span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>The following graph shows the fallback in action:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/03/fallback-in-action@730w.webp 730w, /images/2023/03/fallback-in-action@730w2x.webp 1460w, /images/2023/03/fallback-in-action@610w.webp 610w, /images/2023/03/fallback-in-action@610w2x.webp 1220w, /images/2023/03/fallback-in-action@450w.webp 450w, /images/2023/03/fallback-in-action@450w2x.webp 900w, /images/2023/03/fallback-in-action@330w.webp 330w, /images/2023/03/fallback-in-action@330w2x.webp 660w, /images/2023/03/fallback-in-action@545w.webp 545w, /images/2023/03/fallback-in-action@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/03/fallback-in-action@730w.png 730w, /images/2023/03/fallback-in-action@730w2x.png 1460w, /images/2023/03/fallback-in-action@610w.png 610w, /images/2023/03/fallback-in-action@610w2x.png 1220w, /images/2023/03/fallback-in-action@450w.png 450w, /images/2023/03/fallback-in-action@450w2x.png 900w, /images/2023/03/fallback-in-action@330w.png 330w, /images/2023/03/fallback-in-action@330w2x.png 660w, /images/2023/03/fallback-in-action@545w.png 545w, /images/2023/03/fallback-in-action@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/03/fallback-in-action.png" alt="Fallback in action" title="Fallback in action"></picture></p><p>The red line shows the desired spot, the orange line shows the running spot, and the green line shows the running fallback.</p><ul><li><strong>9:25</strong> two spot instances are desired and running (<code>desired spot = 4; running spot = 2</code>).</li><li><strong>9:27</strong> one additional spot instance is requested (<code>desired spot = 3</code>).</li><li><strong>9:32</strong> spot capacity not available; one fallback instance is running (<code>desired spot = 3; running spot = 2; running fallback = 1</code>)</li><li><strong>9:35</strong> one additional spot instance is requested (<code>desired spot = 4</code>)</li><li><strong>9:40</strong> spot capacity not available; two fallback instances are running (<code>desired spot = 4; running spot = 2; running fallback = 2</code>)</li></ul><p>As you can see, it takes around 5 minutes for on-demand capacity to replace the missing spot capacity. This is caused by the 3 x 1-minute delay added by the CloudWatch alarm configuration and the delay introduced by starting an EC2 instance before it influences the <code>GroupInServiceInstances</code> metric. You could remove up to 2 minutes of delay by adjusting the CloudWatch alarms to only wait for one or two threshold violations before triggering the scaling action.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Monitoring with EventBridge</title>
      <link>https://cloudonaut.io/aws-monitoring-with-eventbridge/</link>
      <description>
        <![CDATA[<p>When it comes to AWS monitoring, you probably think of Amazon CloudWatch first. That’s right, but there is another source of information]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <category domain="https://cloudonaut.io/tag/eventbridge/">eventbridge</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-monitoring-with-eventbridge/</guid>
      <pubDate>Thu, 09 Mar 2023 12:10:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>When it comes to AWS monitoring, you probably think of Amazon CloudWatch first. That’s right, but there is another source of information about the health of your cloud infrastructure: Amazon EventBdrige. In this blog post, you’ll learn how to tap into EventBridge to get important information about running your cloud infrastructure.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/03/aws-monitoring-with-eventbridge-title@730w.webp 730w, /images/2023/03/aws-monitoring-with-eventbridge-title@730w2x.webp 1460w, /images/2023/03/aws-monitoring-with-eventbridge-title@610w.webp 610w, /images/2023/03/aws-monitoring-with-eventbridge-title@610w2x.webp 1220w, /images/2023/03/aws-monitoring-with-eventbridge-title@450w.webp 450w, /images/2023/03/aws-monitoring-with-eventbridge-title@450w2x.webp 900w, /images/2023/03/aws-monitoring-with-eventbridge-title@330w.webp 330w, /images/2023/03/aws-monitoring-with-eventbridge-title@330w2x.webp 660w, /images/2023/03/aws-monitoring-with-eventbridge-title@545w.webp 545w, /images/2023/03/aws-monitoring-with-eventbridge-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/03/aws-monitoring-with-eventbridge-title@730w.jpg 730w, /images/2023/03/aws-monitoring-with-eventbridge-title@730w2x.jpg 1460w, /images/2023/03/aws-monitoring-with-eventbridge-title@610w.jpg 610w, /images/2023/03/aws-monitoring-with-eventbridge-title@610w2x.jpg 1220w, /images/2023/03/aws-monitoring-with-eventbridge-title@450w.jpg 450w, /images/2023/03/aws-monitoring-with-eventbridge-title@450w2x.jpg 900w, /images/2023/03/aws-monitoring-with-eventbridge-title@330w.jpg 330w, /images/2023/03/aws-monitoring-with-eventbridge-title@330w2x.jpg 660w, /images/2023/03/aws-monitoring-with-eventbridge-title@545w.jpg 545w, /images/2023/03/aws-monitoring-with-eventbridge-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/03/aws-monitoring-with-eventbridge-title.jpg" alt="Builder's Diary " title="Builder's Diary "></picture></p><h2 id="How-to-configure-AWS-Monitoring-based-on-EventBridge"><a href="#How-to-configure-AWS-Monitoring-based-on-EventBridge" class="headerlink" title="How to configure AWS Monitoring based on EventBridge?"></a>How to configure AWS Monitoring based on EventBridge?</h2><p>The following diagram shows what is needed to extend your AWS monitoring with the help of EventBridge.</p><ol><li>Services like AWS Backup, Amazon EC2, and AWS Health publish events when things go wrong or human intervention is necessary to EventBridge.</li><li>EventBridge rules filter those events based on a pattern and forward matching events to an SNS topic.</li><li>The SNS topic sends events to on-call engineers via Email, SMS, or HTTPS.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/03/aws-monitoring-with-eventbridge@730w.webp 730w, /images/2023/03/aws-monitoring-with-eventbridge@730w2x.webp 1460w, /images/2023/03/aws-monitoring-with-eventbridge@610w.webp 610w, /images/2023/03/aws-monitoring-with-eventbridge@610w2x.webp 1220w, /images/2023/03/aws-monitoring-with-eventbridge@450w.webp 450w, /images/2023/03/aws-monitoring-with-eventbridge@450w2x.webp 900w, /images/2023/03/aws-monitoring-with-eventbridge@330w.webp 330w, /images/2023/03/aws-monitoring-with-eventbridge@330w2x.webp 660w, /images/2023/03/aws-monitoring-with-eventbridge@545w.webp 545w, /images/2023/03/aws-monitoring-with-eventbridge@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/03/aws-monitoring-with-eventbridge@730w.png 730w, /images/2023/03/aws-monitoring-with-eventbridge@730w2x.png 1460w, /images/2023/03/aws-monitoring-with-eventbridge@610w.png 610w, /images/2023/03/aws-monitoring-with-eventbridge@610w2x.png 1220w, /images/2023/03/aws-monitoring-with-eventbridge@450w.png 450w, /images/2023/03/aws-monitoring-with-eventbridge@450w2x.png 900w, /images/2023/03/aws-monitoring-with-eventbridge@330w.png 330w, /images/2023/03/aws-monitoring-with-eventbridge@330w2x.png 660w, /images/2023/03/aws-monitoring-with-eventbridge@545w.png 545w, /images/2023/03/aws-monitoring-with-eventbridge@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/03/aws-monitoring-with-eventbridge.png" alt="AWS Monitoring with EventBridge: Architecture" title="AWS Monitoring with EventBridge: Architecture"></picture></p><p>I have compiled some examples of EventBridge rules and event patterns for AWS monitoring in the following. The code snippets are written in Terraform configuration syntax, but the event patterns can also be used with CloudFormation or even in the AWS Management Console.</p><h2 id="Monitoring-AWS-account-root-user-login-with-EventBridge"><a href="#Monitoring-AWS-account-root-user-login-with-EventBridge" class="headerlink" title="Monitoring AWS account root user login with EventBridge"></a>Monitoring AWS account root user login with EventBridge</h2><p>The following EventBridge rule (formerly known as CloudWatch event rule) defined in Terraform configuration syntax ensures you are notified whenever someone uses the AWS account root user to log in. </p><figure class="highlight kotlin"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_cloudwatch_event_rule&quot;</span> <span class="string">&quot;root_user_login&quot;</span> &#123;</span><br><span class="line">  name          = <span class="string">&quot;aws-monitoring-root-user-login&quot;</span></span><br><span class="line">  event_pattern = &lt;&lt;JSON</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;detail-type&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;AWS Console Sign In via CloudTrail&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;detail&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;userIdentity&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;arn&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;arn:<span class="subst">$&#123;data.aws_partition.current.partition&#125;</span>:iam::<span class="subst">$&#123;data.aws_caller_identity.current.account_id&#125;</span>:root&quot;</span></span><br><span class="line">      ]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">JSON</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">data</span> <span class="string">&quot;aws_partition&quot;</span> <span class="string">&quot;current&quot;</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">data</span> <span class="string">&quot;aws_caller_identity&quot;</span> <span class="string">&quot;current&quot;</span> &#123;&#125;</span><br></pre></td></tr></table></figure><h2 id="Monitoring-AWS-Health-announcements-with-EventBridge"><a href="#Monitoring-AWS-Health-announcements-with-EventBridge" class="headerlink" title="Monitoring AWS Health announcements with EventBridge"></a>Monitoring AWS Health announcements with EventBridge</h2><p>Over the past years, AWS improved the AWS Health Dashboard and is using this channel to communicate outages as well as breaking changes to services. Getting notified about new issues helps ensure your cloud infrastructure’s continuity.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_cloudwatch_event_rule&quot; &quot;health_issue&quot; &#123;</span><br><span class="line">  <span class="type">name</span>          = &quot;aws-monitoring-health-issue&quot;</span><br><span class="line">  event_pattern = &lt;&lt;<span class="type">JSON</span></span><br><span class="line">&#123;</span><br><span class="line">  &quot;source&quot;: [ </span><br><span class="line">    &quot;aws.health&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;detail-type&quot;: [</span><br><span class="line">    &quot;AWS Health Event&quot;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">JSON</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Monitoring-EC2-Auto-Scaling-with-EventBridge"><a href="#Monitoring-EC2-Auto-Scaling-with-EventBridge" class="headerlink" title="Monitoring EC2 Auto Scaling with EventBridge"></a>Monitoring EC2 Auto Scaling with EventBridge</h2><p>EC2 Auto Scaling launches instances, for example, to add capacity to a fleet. It is crucial to get notified if Auto Scaling fails to launch or terminate an instance, as human intervention is most likely required to fix the problem.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_cloudwatch_event_rule&quot; &quot;auto_scaling_failed&quot; &#123;</span><br><span class="line">  <span class="type">name</span>          = &quot;aws-monitoring-auto-scaling-failed&quot;</span><br><span class="line">  event_pattern = &lt;&lt;<span class="type">JSON</span></span><br><span class="line">&#123;</span><br><span class="line">  &quot;source&quot;: [ </span><br><span class="line">    &quot;aws.autoscaling&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;detail-type&quot;: [</span><br><span class="line">    &quot;EC2 Instance Launch Unsuccessful&quot;,</span><br><span class="line">    &quot;EC2 Instance Terminate Unsuccessful&quot;,</span><br><span class="line">    &quot;EC2 Auto Scaling Instance Refresh Failed&quot;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">JSON</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Monitoring-EBS-Snapshots-with-EventBridge"><a href="#Monitoring-EBS-Snapshots-with-EventBridge" class="headerlink" title="Monitoring EBS Snapshots with EventBridge"></a>Monitoring EBS Snapshots with EventBridge</h2><p>Do you rely on creating EBS snapshots for backing up data? If so, you should keep an eye on failed EBS snapshots by using the following EventBridge rule.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_cloudwatch_event_rule&quot; &quot;ebs_failed&quot; &#123;</span><br><span class="line">  <span class="type">name</span>          = &quot;aws-monitoring-ebs-failed&quot;</span><br><span class="line">  event_pattern = &lt;&lt;<span class="type">JSON</span></span><br><span class="line">&#123;</span><br><span class="line">  &quot;source&quot;: [ </span><br><span class="line">    &quot;aws.ec2&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;detail-type&quot;: [</span><br><span class="line">    &quot;EBS Snapshot Notification&quot;,</span><br><span class="line">    &quot;EBS Multi-Volume Snapshots Completion Status&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;detail&quot;: &#123;</span><br><span class="line">    &quot;result&quot;: [</span><br><span class="line">      &quot;failed&quot;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">JSON</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Monitoring-SSM-Automation-with-EventBridge"><a href="#Monitoring-SSM-Automation-with-EventBridge" class="headerlink" title="Monitoring SSM Automation with EventBridge"></a>Monitoring SSM Automation with EventBridge</h2><p>The Systems Manager provides a toolkit to automate the management of EC2 instances. But will you notice when automation fails during the night? The following EventBridge rule will keep you posted.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_cloudwatch_event_rule&quot; &quot;ssm_automation_failed&quot; &#123;</span><br><span class="line">  <span class="type">name</span>          = &quot;aws-monitoring-ssm-automation-failed&quot;</span><br><span class="line">  event_pattern = &lt;&lt;<span class="type">JSON</span></span><br><span class="line">&#123;</span><br><span class="line">  &quot;source&quot;: [ </span><br><span class="line">    &quot;aws.ssm&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;detail-type&quot;: [</span><br><span class="line">    &quot;EC2 Automation Execution Status-change Notification&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;detail&quot;: &#123;</span><br><span class="line">    &quot;status&quot;: [</span><br><span class="line">      &quot;Failed&quot;,</span><br><span class="line">      &quot;TimedOut&quot;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">JSON</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Monitoring-ECS-Tasks-with-EventBridge"><a href="#Monitoring-ECS-Tasks-with-EventBridge" class="headerlink" title="Monitoring ECS Tasks with EventBridge"></a>Monitoring ECS Tasks with EventBridge</h2><p>The Elastic Container Service (ECS) orchestrates containers. But sometimes containers fail and exit with an exit code &gt; 0. The following EventBridge rule will ensure you are getting notified about the issue.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_cloudwatch_event_rule&quot;</span> <span class="string">&quot;ecs_task_failed_non_zero&quot;</span> &#123;</span><br><span class="line">  name          = <span class="string">&quot;aws-monitoring-ecs-task-failed-non-zero&quot;</span></span><br><span class="line">  event_pattern = &lt;&lt;<span class="symbol">JSON</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;source&quot;</span>: [ </span><br><span class="line">    <span class="string">&quot;aws.ecs&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;detail-type&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;ECS Task State Change&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;detail&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;group&quot;</span>: [&#123;<span class="string">&quot;anything-but&quot;</span>: &#123;<span class="string">&quot;prefix&quot;</span>: <span class="string">&quot;service:&quot;</span>&#125;&#125;],</span><br><span class="line">    <span class="string">&quot;lastStatus&quot;</span>: [<span class="string">&quot;STOPPED&quot;</span>],</span><br><span class="line">    <span class="string">&quot;stopCode&quot;</span>: [<span class="string">&quot;EssentialContainerExited&quot;</span>],</span><br><span class="line">    <span class="string">&quot;containers&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;exitCode&quot;</span>: [&#123;<span class="string">&quot;anything-but&quot;</span>: <span class="number">0</span>&#125;]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="symbol">JSON</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Monitoring-ECR-Image-Scan-with-EventBridge"><a href="#Monitoring-ECR-Image-Scan-with-EventBridge" class="headerlink" title="Monitoring ECR Image Scan with EventBridge"></a>Monitoring ECR Image Scan with EventBridge</h2><p>The Elastic Container Registry (ECR) comes with the capability to scan container images for known vulnerabilities. But how do you ensure you are notified about severe findings? Here you go.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_cloudwatch_event_rule&quot;</span> <span class="string">&quot;ecr_image_scan_finding&quot;</span> &#123;</span><br><span class="line">  name          = <span class="string">&quot;aws-monitoring-ecr-image-scan-finding&quot;</span></span><br><span class="line">  event_pattern = &lt;&lt;<span class="symbol">JSON</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;source&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;aws.ecr&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;detail-type&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;ECR Image Scan&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;detail&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;scan-status&quot;</span>: [<span class="string">&quot;COMPLETE&quot;</span>],</span><br><span class="line">    <span class="string">&quot;finding-severity-counts&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;$or&quot;</span>: [</span><br><span class="line">        &#123;<span class="string">&quot;CRITICAL&quot;</span>: [&#123;<span class="string">&quot;numeric&quot;</span>: [<span class="string">&quot;&gt;&quot;</span>, <span class="number">0</span>]&#125;]&#125;,</span><br><span class="line">        &#123;<span class="string">&quot;HIGH&quot;</span>: [&#123;<span class="string">&quot;numeric&quot;</span>: [<span class="string">&quot;&gt;&quot;</span>, <span class="number">0</span>]&#125;]&#125;,</span><br><span class="line">        &#123;<span class="string">&quot;MEDIUM&quot;</span>: [&#123;<span class="string">&quot;numeric&quot;</span>: [<span class="string">&quot;&gt;&quot;</span>, <span class="number">0</span>]&#125;]&#125;,</span><br><span class="line">        &#123;<span class="string">&quot;UNDEFINED&quot;</span>: [&#123;<span class="string">&quot;numeric&quot;</span>: [<span class="string">&quot;&gt;&quot;</span>, <span class="number">0</span>]&#125;]&#125;</span><br><span class="line">      ]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="symbol">JSON</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Monitoring-Amazon-Certificate-Manager-ACM-with-EventBridge"><a href="#Monitoring-Amazon-Certificate-Manager-ACM-with-EventBridge" class="headerlink" title="Monitoring Amazon Certificate Manager (ACM) with EventBridge"></a>Monitoring Amazon Certificate Manager (ACM) with EventBridge</h2><p>We have all experienced downtimes caused by expired SSL&#x2F;TLS certificates. This doesn’t have to be the case. Monitor the Amazon Certificate Manager (ACM) and get notified when certificates expire.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_cloudwatch_event_rule&quot; &quot;acm_certificate_approaching_expiration&quot; &#123;</span><br><span class="line">  <span class="type">name</span>          = &quot;aws-monitoring-acm-certificate-expiration&quot;</span><br><span class="line">  event_pattern = &lt;&lt;<span class="type">JSON</span></span><br><span class="line">&#123;</span><br><span class="line">  &quot;source&quot;: [ </span><br><span class="line">    &quot;aws.acm&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;detail-type&quot;: [</span><br><span class="line">    &quot;ACM Certificate Approaching Expiration&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;detail&quot;: &#123;</span><br><span class="line">    &quot;DaysToExpiry&quot;: [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">7</span>, <span class="number">14</span>, <span class="number">21</span>, <span class="number">28</span>, <span class="number">35</span>, <span class="number">42</span>, <span class="number">49</span>, <span class="number">56</span>, <span class="number">63</span>, <span class="number">70</span>]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">JSON</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Monitoring-AWS-Backup-with-EventBridge"><a href="#Monitoring-AWS-Backup-with-EventBridge" class="headerlink" title="Monitoring AWS Backup with EventBridge"></a>Monitoring AWS Backup with EventBridge</h2><p>Sometimes backup services like AWS Backup provide false security. After all, what happens if AWS Backup runs into errors when creating important backups? You can use the following EventBridge rule to catch backup errors.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_cloudwatch_event_rule&quot; &quot;backup_failed&quot; &#123;</span><br><span class="line">  <span class="type">name</span>          = &quot;aws-monitoring-backup-failed&quot;</span><br><span class="line">  event_pattern = &lt;&lt;<span class="type">JSON</span></span><br><span class="line">&#123;</span><br><span class="line">  &quot;source&quot;: [ </span><br><span class="line">    &quot;aws.backup&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;detail-type&quot;: [</span><br><span class="line">    &quot;Backup Job State Change&quot;,</span><br><span class="line">    &quot;Copy Job State Change&quot;,</span><br><span class="line">    &quot;Restore Job State Change&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;detail&quot;: &#123;</span><br><span class="line">    &quot;state&quot;: [</span><br><span class="line">      &quot;FAILED&quot;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">JSON</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Monitoring-Elastic-Beanstalk-with-EventBridge"><a href="#Monitoring-Elastic-Beanstalk-with-EventBridge" class="headerlink" title="Monitoring Elastic Beanstalk with EventBridge"></a>Monitoring Elastic Beanstalk with EventBridge</h2><p>Elastic Beanstalk is a popular service for deploying web applications on AWS. It would help if you were the first to know about a problem with your application. The following EventBridge rule notifies you about issues with your Elastic Beanstalk applications.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_cloudwatch_event_rule&quot; &quot;elastic_beanstalk_failed&quot; &#123;</span><br><span class="line">  <span class="type">name</span>          = &quot;aws-monitoring-elastic-beanstalk-failed&quot;</span><br><span class="line">  event_pattern = &lt;&lt;<span class="type">JSON</span></span><br><span class="line">&#123;</span><br><span class="line">  &quot;source&quot;: [</span><br><span class="line">    &quot;aws.elasticbeanstalk&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;detail-type&quot;: [</span><br><span class="line">    &quot;Elastic Beanstalk resource status change&quot;,</span><br><span class="line">    &quot;Other resource status change&quot;,</span><br><span class="line">    &quot;Health status change&quot;,</span><br><span class="line">    &quot;ManagedUpdateStatusChangeEnabled&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;detail&quot;: &#123;</span><br><span class="line">    &quot;Severity&quot;: [</span><br><span class="line">      &quot;WARN&quot;,</span><br><span class="line">      &quot;ERROR&quot;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">JSON</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>When it comes to AWS monitoring, EventBridge is an essential source of information. Ensure you are using EventBridge rules forwarding events to an SNS topic to get notified about issues with your cloud infrastructure.</p><p>All examples from this blog post originate from <a href="http://github.com/marbot-io/terraform-aws-marbot-monitoring-basic" target="_blank" rel="noopener">marbot-io&#x2F;terraform-aws-marbot-monitoring-basic</a>.</p><p>Also, please check out our product <a href="https://marbot.io/" target="_blank" rel="noopener">marbot</a> to roll out monitoring based on CloudWatch and EventBridge with ease.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Builder's Diary Vol. 6: Serverless and DevOps - a match made in heaven</title>
      <link>https://cloudonaut.io/builders-diary-vol6-serverless-and-devops-a-match-made-in-heaven/</link>
      <description>
        <![CDATA[<p>Get insights into the day-to-day challenges of builders. In this issue, Vilius Kukanauskas from our partner DEMICON explains why Serverle]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <guid isPermaLink="true">https://cloudonaut.io/builders-diary-vol6-serverless-and-devops-a-match-made-in-heaven/</guid>
      <pubDate>Tue, 28 Feb 2023 19:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Get insights into the day-to-day challenges of builders. In this issue, Vilius Kukanauskas from our partner DEMICON explains why Serverless and DevOps are a match made in heaven.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/diary@730w.webp 730w, /images/2022/06/diary@730w2x.webp 1460w, /images/2022/06/diary@610w.webp 610w, /images/2022/06/diary@610w2x.webp 1220w, /images/2022/06/diary@450w.webp 450w, /images/2022/06/diary@450w2x.webp 900w, /images/2022/06/diary@330w.webp 330w, /images/2022/06/diary@330w2x.webp 660w, /images/2022/06/diary@545w.webp 545w, /images/2022/06/diary@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/diary@730w.jpg 730w, /images/2022/06/diary@730w2x.jpg 1460w, /images/2022/06/diary@610w.jpg 610w, /images/2022/06/diary@610w2x.jpg 1220w, /images/2022/06/diary@450w.jpg 450w, /images/2022/06/diary@450w2x.jpg 900w, /images/2022/06/diary@330w.jpg 330w, /images/2022/06/diary@330w2x.jpg 660w, /images/2022/06/diary@545w.jpg 545w, /images/2022/06/diary@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/diary.jpg" alt="Builder's Diary " title="Builder's Diary "></picture></p><p>Here you go if you prefer a video or podcast instead of reading.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=0lxLB9jZD6I">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/0lxLB9jZD6I" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/72-serverless-and-devops-a-match-made-in-heaven-builders-diary-vol-006/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p><em><strong>cloudonaut:</strong> How has your career progressed? What were the most important steps?</em></p><p><strong>Vilius Kukanauskas:</strong> I started my career as a software engineer implementing Vaadin and Spring Boot applications, for example. In 2019, I transitioned from a software to a DevOps engineering role and got in touch with AWS, Kubernetes, and Terraform. In January 2022, I took my career in a new direction and joined DEMICON as a consultant focusing on AWS and DevOps.</p><p><em><strong>cloudonaut:</strong> What was your main motivation to apply for your role at DEMICON?</em></p><p><strong>Vilius Kukanauskas:</strong> I learned to appreciate remote work during the pandemic. I was skeptical at first, but now I can’t imagine going to the office every day. I enjoy the free time that comes from not having to commute. And I’m convinced that I’m even more effective remotely. DEMICON is a remote-first company, so remote work is part of the culture. That was important to me.</p><p><em><strong>cloudonaut:</strong> You have been with DEMICON for over a year. How have you experienced this time?</em></p><p><strong>Vilius Kukanauskas:</strong> I like my role as an external consultant in projects. Until now, I had to change companies whenever I wanted to familiarize myself with a new technology. At DEMICON, I work on different projects with different customers and with different focuses. I learn a lot in the process, and I really like that.</p><p>For example, one of my first projects was to rewrite a monolithic application. Together with a small team, we used Lambda, Step Functions, and DynamoDB. For the implementation, we used TypeScript and CDK. I’m currently working on a project focusing on network infrastructure and Terraform.</p><p><em><strong>cloudonaut:</strong> Back to your career, you mentioned that you transitioned from a software to a DevOps engineering role. Why?</em></p><p><strong>Vilius Kukanauskas:</strong> Ever since I first came into contact with a computer, I have loved programming. When your code runs correctly and becomes visible in reality, or at least on the screen, it gives me an endorphin rush every time.</p><p>As a software engineer, I was interested in how my code gets into production. The CI&#x2F;CD pipeline and infrastructure were new territories for me that I wanted to explore.</p><p><em><strong>cloudonaut:</strong> How do you define the term DevOps?</em></p><p><strong>Vilius Kukanauskas:</strong> I have a somewhat radical approach. For me, DevOps consists of two verbs: develop and operate. For me, it is important to get into action. It’s about developing software and also operating it.</p><p><em><strong>cloudonaut:</strong> How to combine DevOps practices with Serverless architectures?</em></p><p><strong>Vilius Kukanauskas:</strong> Serverless and DevOps is a match made in heaven. In my experience, application code and infrastructure as code converge when building Serverless applications. There is no longer a split between Dev and Ops.</p><p><em><strong>cloudonaut:</strong> Can you give me an example of that?</em></p><p><strong>Vilius Kukanauskas:</strong> I would love to. I mentioned before that I built a serverless application based on Lambda and Step Functions. For those, who have yet to hear about Step Functions, it is a workflow management service. By defining a state machine, you can orchestrate workflows. For example, you are able to invoke Lambda functions, parse results, and invoke other AWS services. Step Functions even supports conditions and loops in a workflow definition.</p><p>So here is what happens. Part of the application logic is written in code and executed by Lambda functions. But another component becomes part of the infrastructure, namely the workflow definition for step functions.</p><p><em><strong>cloudonaut:</strong> Does this mean that application code and Infrastructure as Code fuse and end up in one repository?</em></p><p><strong>Vilius Kukanauskas:</strong> Yes, exactly. We managed the TypeScript code for the Lambda functions and the CDK code in a single repository. So all things needed to deploy the Serverless application was stored in the repository.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/02/serverless-fusion-dev-ops@730w.webp 730w, /images/2023/02/serverless-fusion-dev-ops@730w2x.webp 1460w, /images/2023/02/serverless-fusion-dev-ops@610w.webp 610w, /images/2023/02/serverless-fusion-dev-ops@610w2x.webp 1220w, /images/2023/02/serverless-fusion-dev-ops@450w.webp 450w, /images/2023/02/serverless-fusion-dev-ops@450w2x.webp 900w, /images/2023/02/serverless-fusion-dev-ops@330w.webp 330w, /images/2023/02/serverless-fusion-dev-ops@330w2x.webp 660w, /images/2023/02/serverless-fusion-dev-ops@545w.webp 545w, /images/2023/02/serverless-fusion-dev-ops@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/02/serverless-fusion-dev-ops@730w.png 730w, /images/2023/02/serverless-fusion-dev-ops@730w2x.png 1460w, /images/2023/02/serverless-fusion-dev-ops@610w.png 610w, /images/2023/02/serverless-fusion-dev-ops@610w2x.png 1220w, /images/2023/02/serverless-fusion-dev-ops@450w.png 450w, /images/2023/02/serverless-fusion-dev-ops@450w2x.png 900w, /images/2023/02/serverless-fusion-dev-ops@330w.png 330w, /images/2023/02/serverless-fusion-dev-ops@330w2x.png 660w, /images/2023/02/serverless-fusion-dev-ops@545w.png 545w, /images/2023/02/serverless-fusion-dev-ops@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/02/serverless-fusion-dev-ops.png" alt="" title=""></picture></p><p><em><strong>cloudonaut:</strong> You’re evidently passionate about Serverless architectures. What advice would you give to developers and system administrators who want to explore this new technology?</em></p><p><strong>Vilius Kukanauskas:</strong> Get out of your comfort zone.</p><p>Developers need to learn that there is a whole new approach besides frameworks such as Spring Boot. I admit it takes work to get familiar with Serverless, Lambda, Step Functions, DynamoDB, IAM, and so on. But it is worth it. As a developer, you have the advantage that you can build highly available and scalable applications with the building blocks that AWS provides for Serverless architectures on a level that was hardly reachable before.</p><p>System administrators bring valuable experience from operations, for example, in the area of monitoring and security. But, it is essential that system administrators learn a programming language such as TypeScript, JavaScript, Python, or Go. Again, it’s worth it. You can accomplish so much with these new skills.</p><p><em><strong>cloudonaut:</strong> Thanks a lot for sharing your insights into Serverless and DevOps, Vilius!</em></p><p><strong>Vilius Kukanauskas:</strong> Thank you as well. It was a pleasure.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to filter S3 events by object size</title>
      <link>https://cloudonaut.io/how-to-filter-s3-events-by-object-size/</link>
      <description>
        <![CDATA[<p>While answering a support request for <a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV</a>, I stumbled upon the fo]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/eventbridge/">eventbridge</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-filter-s3-events-by-object-size/</guid>
      <pubDate>Wed, 22 Feb 2023 10:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>While answering a support request for <a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV</a>, I stumbled upon the following question:</p><blockquote><p>Is there a way to only scan S3 objects with a size of less than 1 GB for malware?</p></blockquote><p>This translates to the more general question:</p><blockquote><p>How to filter S3 events by object size?</p></blockquote><p>Filtering S3 events by object size is helpful in the following scenarios:</p><ul><li>How to get notified via email when someone uploads a large file to S3?</li><li>How to ensure only files smaller than 100 MB are processed by Lambda to avoid timeouts?</li><li>How to trigger an ECS task after an archive with more than 1 GB has been uploaded to S3?</li></ul><p>Luckily, there are simple ways to filter S3 events by object size.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/02/how-to-filter-s3-events-by-object-size-title@730w.webp 730w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@730w2x.webp 1460w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@610w.webp 610w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@610w2x.webp 1220w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@450w.webp 450w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@450w2x.webp 900w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@330w.webp 330w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@330w2x.webp 660w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@545w.webp 545w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/02/how-to-filter-s3-events-by-object-size-title@730w.jpg 730w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@730w2x.jpg 1460w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@610w.jpg 610w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@610w2x.jpg 1220w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@450w.jpg 450w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@450w2x.jpg 900w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@330w.jpg 330w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@330w2x.jpg 660w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@545w.jpg 545w, /images/2023/02/how-to-filter-s3-events-by-object-size-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/02/how-to-filter-s3-events-by-object-size-title.jpg" alt="How to filter S3 events by object size" title="How to filter S3 events by object size"></picture></p><h2 id="S3-Event-Notifications-vs-EventBridge"><a href="#S3-Event-Notifications-vs-EventBridge" class="headerlink" title="S3 Event Notifications vs. EventBridge"></a>S3 Event Notifications vs. EventBridge</h2><p>Nowadays, there are two options to react to new or modified S3 objects:</p><ul><li><strong>S3 Event Notifications</strong> has been around for years and allows us to send events to SNS, SQS, and Lambda.</li><li><strong>EventBridge</strong>, the serverless event bus, is the state-of-the-art approach for building event-driven systems on AWS.</li></ul><p>Both options allow you to filter events based on the S3 object size.</p><h2 id="Filtering-S3-Event-Notifications-by-object-size"><a href="#Filtering-S3-Event-Notifications-by-object-size" class="headerlink" title="Filtering S3 Event Notifications by object size"></a>Filtering S3 Event Notifications by object size</h2><p>Assuming you configured S3 Event Notifications to deliver events to an SNS topic. The following filter policy only delivers events about an object with an object size of less than <code>1000000000</code> bytes (1 GB) to the subscriber.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Records&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;s3&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;object&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">&#123;</span><span class="attr">&quot;numeric&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;&lt;&quot;</span><span class="punctuation">,</span> <span class="number">1000000000</span><span class="punctuation">]</span><span class="punctuation">&#125;</span><span class="punctuation">]</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>First, create an SNS topic.</p><p>Second, configure S3 Event Notifications, as illustrated in the following screenshots.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/02/s3-events-sns-01@730w.webp 730w, /images/2023/02/s3-events-sns-01@730w2x.webp 1460w, /images/2023/02/s3-events-sns-01@610w.webp 610w, /images/2023/02/s3-events-sns-01@610w2x.webp 1220w, /images/2023/02/s3-events-sns-01@450w.webp 450w, /images/2023/02/s3-events-sns-01@450w2x.webp 900w, /images/2023/02/s3-events-sns-01@330w.webp 330w, /images/2023/02/s3-events-sns-01@330w2x.webp 660w, /images/2023/02/s3-events-sns-01@545w.webp 545w, /images/2023/02/s3-events-sns-01@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/02/s3-events-sns-01@730w.png 730w, /images/2023/02/s3-events-sns-01@730w2x.png 1460w, /images/2023/02/s3-events-sns-01@610w.png 610w, /images/2023/02/s3-events-sns-01@610w2x.png 1220w, /images/2023/02/s3-events-sns-01@450w.png 450w, /images/2023/02/s3-events-sns-01@450w2x.png 900w, /images/2023/02/s3-events-sns-01@330w.png 330w, /images/2023/02/s3-events-sns-01@330w2x.png 660w, /images/2023/02/s3-events-sns-01@545w.png 545w, /images/2023/02/s3-events-sns-01@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/02/s3-events-sns-01.png" alt="Configure S3 Event Notifications: 01" title="Configure S3 Event Notifications: 01"></picture></p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/02/s3-events-sns-02@730w.webp 730w, /images/2023/02/s3-events-sns-02@730w2x.webp 1460w, /images/2023/02/s3-events-sns-02@610w.webp 610w, /images/2023/02/s3-events-sns-02@610w2x.webp 1220w, /images/2023/02/s3-events-sns-02@450w.webp 450w, /images/2023/02/s3-events-sns-02@450w2x.webp 900w, /images/2023/02/s3-events-sns-02@330w.webp 330w, /images/2023/02/s3-events-sns-02@330w2x.webp 660w, /images/2023/02/s3-events-sns-02@545w.webp 545w, /images/2023/02/s3-events-sns-02@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/02/s3-events-sns-02@730w.png 730w, /images/2023/02/s3-events-sns-02@730w2x.png 1460w, /images/2023/02/s3-events-sns-02@610w.png 610w, /images/2023/02/s3-events-sns-02@610w2x.png 1220w, /images/2023/02/s3-events-sns-02@450w.png 450w, /images/2023/02/s3-events-sns-02@450w2x.png 900w, /images/2023/02/s3-events-sns-02@330w.png 330w, /images/2023/02/s3-events-sns-02@330w2x.png 660w, /images/2023/02/s3-events-sns-02@545w.png 545w, /images/2023/02/s3-events-sns-02@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/02/s3-events-sns-02.png" alt="Configure S3 Event Notifications: 01" title="Configure S3 Event Notifications: 01"></picture></p><p>Third, create a subscription for the SNS topic.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/02/s3-events-sns-03@730w.webp 730w, /images/2023/02/s3-events-sns-03@730w2x.webp 1460w, /images/2023/02/s3-events-sns-03@610w.webp 610w, /images/2023/02/s3-events-sns-03@610w2x.webp 1220w, /images/2023/02/s3-events-sns-03@450w.webp 450w, /images/2023/02/s3-events-sns-03@450w2x.webp 900w, /images/2023/02/s3-events-sns-03@330w.webp 330w, /images/2023/02/s3-events-sns-03@330w2x.webp 660w, /images/2023/02/s3-events-sns-03@545w.webp 545w, /images/2023/02/s3-events-sns-03@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/02/s3-events-sns-03@730w.png 730w, /images/2023/02/s3-events-sns-03@730w2x.png 1460w, /images/2023/02/s3-events-sns-03@610w.png 610w, /images/2023/02/s3-events-sns-03@610w2x.png 1220w, /images/2023/02/s3-events-sns-03@450w.png 450w, /images/2023/02/s3-events-sns-03@450w2x.png 900w, /images/2023/02/s3-events-sns-03@330w.png 330w, /images/2023/02/s3-events-sns-03@330w2x.png 660w, /images/2023/02/s3-events-sns-03@545w.png 545w, /images/2023/02/s3-events-sns-03@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/02/s3-events-sns-03.png" alt="Create SNS subscription" title="Create SNS subscription"></picture></p><p>Apply the subscription filter as shown in the following screenshot. Make sure to select the policy scope <code>MessageBody</code>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/02/s3-events-sns-04@730w.webp 730w, /images/2023/02/s3-events-sns-04@730w2x.webp 1460w, /images/2023/02/s3-events-sns-04@610w.webp 610w, /images/2023/02/s3-events-sns-04@610w2x.webp 1220w, /images/2023/02/s3-events-sns-04@450w.webp 450w, /images/2023/02/s3-events-sns-04@450w2x.webp 900w, /images/2023/02/s3-events-sns-04@330w.webp 330w, /images/2023/02/s3-events-sns-04@330w2x.webp 660w, /images/2023/02/s3-events-sns-04@545w.webp 545w, /images/2023/02/s3-events-sns-04@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/02/s3-events-sns-04@730w.png 730w, /images/2023/02/s3-events-sns-04@730w2x.png 1460w, /images/2023/02/s3-events-sns-04@610w.png 610w, /images/2023/02/s3-events-sns-04@610w2x.png 1220w, /images/2023/02/s3-events-sns-04@450w.png 450w, /images/2023/02/s3-events-sns-04@450w2x.png 900w, /images/2023/02/s3-events-sns-04@330w.png 330w, /images/2023/02/s3-events-sns-04@330w2x.png 660w, /images/2023/02/s3-events-sns-04@545w.png 545w, /images/2023/02/s3-events-sns-04@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/02/s3-events-sns-04.png" alt="Apply subscription filter" title="Apply subscription filter"></picture></p><h2 id="Filtering-EventBridge-events-by-S3-object-size"><a href="#Filtering-EventBridge-events-by-S3-object-size" class="headerlink" title="Filtering EventBridge events by S3 object size"></a>Filtering EventBridge events by S3 object size</h2><p>After <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-event-notifications-eventbridge.html" target="_blank" rel="noopener">enabling EventBridge events</a>, the following event pattern matches events about new or modified objects with a size of less than <code>1000000000</code> bytes (1 GB).</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;source&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;aws.s3&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;detail-type&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;Object Created&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;detail&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;object&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;numeric&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;&lt;&quot;</span><span class="punctuation">,</span> <span class="number">1000000000</span><span class="punctuation">]</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>First, enable EventBridge events for the S3 bucket, as illustrated in the following screenshot.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/02/s3-events-eventbridge-01@730w.webp 730w, /images/2023/02/s3-events-eventbridge-01@730w2x.webp 1460w, /images/2023/02/s3-events-eventbridge-01@610w.webp 610w, /images/2023/02/s3-events-eventbridge-01@610w2x.webp 1220w, /images/2023/02/s3-events-eventbridge-01@450w.webp 450w, /images/2023/02/s3-events-eventbridge-01@450w2x.webp 900w, /images/2023/02/s3-events-eventbridge-01@330w.webp 330w, /images/2023/02/s3-events-eventbridge-01@330w2x.webp 660w, /images/2023/02/s3-events-eventbridge-01@545w.webp 545w, /images/2023/02/s3-events-eventbridge-01@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/02/s3-events-eventbridge-01@730w.png 730w, /images/2023/02/s3-events-eventbridge-01@730w2x.png 1460w, /images/2023/02/s3-events-eventbridge-01@610w.png 610w, /images/2023/02/s3-events-eventbridge-01@610w2x.png 1220w, /images/2023/02/s3-events-eventbridge-01@450w.png 450w, /images/2023/02/s3-events-eventbridge-01@450w2x.png 900w, /images/2023/02/s3-events-eventbridge-01@330w.png 330w, /images/2023/02/s3-events-eventbridge-01@330w2x.png 660w, /images/2023/02/s3-events-eventbridge-01@545w.png 545w, /images/2023/02/s3-events-eventbridge-01@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/02/s3-events-eventbridge-01.png" alt="Enable S3 EventBridge events" title="Enable S3 EventBridge events"></picture></p><p>Second, create an EventBridge rule, as shown in the following screenshot.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/02/s3-events-eventbridge-02@730w.webp 730w, /images/2023/02/s3-events-eventbridge-02@730w2x.webp 1460w, /images/2023/02/s3-events-eventbridge-02@610w.webp 610w, /images/2023/02/s3-events-eventbridge-02@610w2x.webp 1220w, /images/2023/02/s3-events-eventbridge-02@450w.webp 450w, /images/2023/02/s3-events-eventbridge-02@450w2x.webp 900w, /images/2023/02/s3-events-eventbridge-02@330w.webp 330w, /images/2023/02/s3-events-eventbridge-02@330w2x.webp 660w, /images/2023/02/s3-events-eventbridge-02@545w.webp 545w, /images/2023/02/s3-events-eventbridge-02@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/02/s3-events-eventbridge-02@730w.png 730w, /images/2023/02/s3-events-eventbridge-02@730w2x.png 1460w, /images/2023/02/s3-events-eventbridge-02@610w.png 610w, /images/2023/02/s3-events-eventbridge-02@610w2x.png 1220w, /images/2023/02/s3-events-eventbridge-02@450w.png 450w, /images/2023/02/s3-events-eventbridge-02@450w2x.png 900w, /images/2023/02/s3-events-eventbridge-02@330w.png 330w, /images/2023/02/s3-events-eventbridge-02@330w2x.png 660w, /images/2023/02/s3-events-eventbridge-02@545w.png 545w, /images/2023/02/s3-events-eventbridge-02@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/02/s3-events-eventbridge-02.png" alt="Create an EventBridge rule" title="Create an EventBridge rule"></picture></p><p>Third, copy and paste the event pattern as demonstrated in the following screenshot.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2023/02/s3-events-eventbridge-03@730w.webp 730w, /images/2023/02/s3-events-eventbridge-03@730w2x.webp 1460w, /images/2023/02/s3-events-eventbridge-03@610w.webp 610w, /images/2023/02/s3-events-eventbridge-03@610w2x.webp 1220w, /images/2023/02/s3-events-eventbridge-03@450w.webp 450w, /images/2023/02/s3-events-eventbridge-03@450w2x.webp 900w, /images/2023/02/s3-events-eventbridge-03@330w.webp 330w, /images/2023/02/s3-events-eventbridge-03@330w2x.webp 660w, /images/2023/02/s3-events-eventbridge-03@545w.webp 545w, /images/2023/02/s3-events-eventbridge-03@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2023/02/s3-events-eventbridge-03@730w.png 730w, /images/2023/02/s3-events-eventbridge-03@730w2x.png 1460w, /images/2023/02/s3-events-eventbridge-03@610w.png 610w, /images/2023/02/s3-events-eventbridge-03@610w2x.png 1220w, /images/2023/02/s3-events-eventbridge-03@450w.png 450w, /images/2023/02/s3-events-eventbridge-03@450w2x.png 900w, /images/2023/02/s3-events-eventbridge-03@330w.png 330w, /images/2023/02/s3-events-eventbridge-03@330w2x.png 660w, /images/2023/02/s3-events-eventbridge-03@545w.png 545w, /images/2023/02/s3-events-eventbridge-03@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2023/02/s3-events-eventbridge-03.png" alt="Configure event pattern" title="Configure event pattern"></picture></p><p>Fourth, create the EventBridge rule.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Both S3 Event Notifications and EventBridge events allow you to filter events about new or modified S3 events by object size.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Builder's Diary Vol. 5: ECS Anywhere</title>
      <link>https://cloudonaut.io/builders-diary-vol5-ecs-anywhere/</link>
      <description>
        <![CDATA[<p>Get insights into the day-to-day challenges of builders. In this issue, Samia Rabah from our partner DEMICON talks about ECS Anywhere to]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <guid isPermaLink="true">https://cloudonaut.io/builders-diary-vol5-ecs-anywhere/</guid>
      <pubDate>Thu, 22 Dec 2022 14:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Get insights into the day-to-day challenges of builders. In this issue, Samia Rabah from our partner DEMICON talks about ECS Anywhere to orchestrate containers on-premises and in the cloud.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/diary@730w.webp 730w, /images/2022/06/diary@730w2x.webp 1460w, /images/2022/06/diary@610w.webp 610w, /images/2022/06/diary@610w2x.webp 1220w, /images/2022/06/diary@450w.webp 450w, /images/2022/06/diary@450w2x.webp 900w, /images/2022/06/diary@330w.webp 330w, /images/2022/06/diary@330w2x.webp 660w, /images/2022/06/diary@545w.webp 545w, /images/2022/06/diary@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/diary@730w.jpg 730w, /images/2022/06/diary@730w2x.jpg 1460w, /images/2022/06/diary@610w.jpg 610w, /images/2022/06/diary@610w2x.jpg 1220w, /images/2022/06/diary@450w.jpg 450w, /images/2022/06/diary@450w2x.jpg 900w, /images/2022/06/diary@330w.jpg 330w, /images/2022/06/diary@330w2x.jpg 660w, /images/2022/06/diary@545w.jpg 545w, /images/2022/06/diary@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/diary.jpg" alt="Builder's Diary " title="Builder's Diary "></picture></p><p>If you prefer a video or podcast instead of reading, here you go.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=1Cz-se8OgQY">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/1Cz-se8OgQY" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/66-ecs-anywhere-builders-diary-vol-5/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p><em><strong>cloudonaut:</strong> You joined DEMICON as a DevOps and Cloud Consultant in July 2022. How has your career gone up to this point?</em></p><p><strong>Samia Rabah:</strong> After graduating with a master’s degree in machine learning, I started my career as a Python developer. Later, I joined KM.ON as a full stack developer and contributed to one of their data science projects. As a full-stack developer, I came in contact with the cloud and the endless possibilities fascinated me. So I was looking for a new job where I could focus on my cloud skills. That’s how I found out about an open position at DEMICON.</p><p><em><strong>cloudonaut:</strong> DEMICON is a remote-first company, so what did the onboarding process look like?</em></p><p><strong>Samia Rabah:</strong> The onboarding process was welcoming and well-structured. For example, I had remote coffee meetings during the first week with many of my new colleagues. Also, all my hardware and credentials arrived even before I started my new position to ensure a seamless start. Besides that, I had the opportunity to meet my team and others who were beginning fresh at DEMICON in person.</p><p><em><strong>cloudonaut:</strong> In our experience, being a developer is different from being a consultant. How did you get into the role of a consultant at DEMICON?</em></p><p><strong>Samia Rabah:</strong> I worked on improving an internal project during the first weeks. I also prepared for and passed the AWS Certified Developer Associate exams. After that, I conducted a workshop at the customer’s site for the first time. And later, I worked on my first client project, which is where I implemented a solution based on ECS Anywhere.</p><p><em><strong>cloudonaut:</strong> What was the challenge you faced in your first project?</em></p><p><strong>Samia Rabah:</strong> My customer was already running a workload consisting of an API and batch processing layer on ECS. The batch processing required GPUs and was therefore running on expensive EC2 instances. The customer wanted to use underutilized on-premises machines to reduce costs instead of running the entire GPU workload on AWS.</p><p><em><strong>cloudonaut:</strong> What is ECS Anywhere?</em></p><p><strong>Samia Rabah:</strong> The Elastic Container Service (ECS) orchestrates containers on EC2 instances or Fargate. With ECS Anywhere, you can run containers on bare-metal or virtual machines. All you need to do is install and configure the SSM and ECS agent.</p><p><em><strong>cloudonaut:</strong> Could you please share the architecture for the API and batch processing workload running on ECS, Fargate, and ECS Anywhere?</em></p><p><strong>Samia Rabah:</strong> The following figure illustrates the components of the architecture.</p><ul><li><strong>ECS</strong> orchestrates the services, tasks, and containers.</li><li><strong>ALB</strong> forwards requests to the API service.</li><li><strong>Fargate</strong> provides the compute capacity for the API service and allows running batch processes in the cloud during peak hours.</li><li><strong>ECS Anywhere</strong> runs containers on on-premises machines.</li><li><strong>EFS</strong> is used to load the input and persist the output of batch jobs.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/12/ecs-anywhere@730w.webp 730w, /images/2022/12/ecs-anywhere@730w2x.webp 1460w, /images/2022/12/ecs-anywhere@610w.webp 610w, /images/2022/12/ecs-anywhere@610w2x.webp 1220w, /images/2022/12/ecs-anywhere@450w.webp 450w, /images/2022/12/ecs-anywhere@450w2x.webp 900w, /images/2022/12/ecs-anywhere@330w.webp 330w, /images/2022/12/ecs-anywhere@330w2x.webp 660w, /images/2022/12/ecs-anywhere@545w.webp 545w, /images/2022/12/ecs-anywhere@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/12/ecs-anywhere@730w.png 730w, /images/2022/12/ecs-anywhere@730w2x.png 1460w, /images/2022/12/ecs-anywhere@610w.png 610w, /images/2022/12/ecs-anywhere@610w2x.png 1220w, /images/2022/12/ecs-anywhere@450w.png 450w, /images/2022/12/ecs-anywhere@450w2x.png 900w, /images/2022/12/ecs-anywhere@330w.png 330w, /images/2022/12/ecs-anywhere@330w2x.png 660w, /images/2022/12/ecs-anywhere@545w.png 545w, /images/2022/12/ecs-anywhere@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/12/ecs-anywhere.png" alt="ECS Anywhere Architecture" title="ECS Anywhere Architecture"></picture></p><p><em><strong>cloudonaut:</strong> How does monitoring and logging work for containers running on-premises?</em></p><p><strong>Samia Rabah:</strong> ECS Anywhere supports the <code>awslogs</code> log driver. Therefore, each container is capable of sending its logs to a CloudWatch log group. Doing so is highly recommended, as centralized logging is crucial for debugging issues. Note that ECS Anywhere also supports task and task execution roles. Therefore, attaching IAM roles to your tasks&#x2F;containers is possible to grant access to other AWS services.</p><p><em><strong>cloudonaut:</strong> That sounds great. Logging and IAM roles are very helpful. But, does ECS Anywhere come with any limitations?</em></p><p><strong>Samia Rabah:</strong> Yes. The network mode <code>awsvpc</code> is not supported, as the workload is running on-premises. If necessary, you need to peer your on-premises network with your VPC. For example, we have been using a site-to-site VPN connection. Besides that, AWS Anywhere does not support Elastic Load Balancing. Also, the native integration for EFS volumes is not supported. Instead, you need to mount the EFS volume on the on-premises machines and use local container volumes.</p><p><em><strong>cloudonaut:</strong> Do you recommend ECS Anywhere for other scenarios as well?</em></p><p><strong>Samia Rabah:</strong> In general, I’m a huge fan of ECS Anywhere. It worked very well and enabled hybrid cloud scenarios. ECS Anywhere is a good fit for batch processing or similar workloads that do not require inbound traffic. I can also imagine using ECS Anywhere in scenarios where the whole workload runs outside of AWS, for example, to process data at the edge.</p><p><em><strong>cloudonaut:</strong> Thanks a lot for sharing your insights into ECS Anywhere with us, Samia!</em></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Builder's Diary Vol. 4: Serverless Software Engineering</title>
      <link>https://cloudonaut.io/builders-diary-vol4-serverless-software-engineering/</link>
      <description>
        <![CDATA[<p>Get insights into the day-to-day challenges of builders. In this issue, Florian Dröge and Lars Hüper from our partner tecRacer share insi]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/builders-diary-vol4-serverless-software-engineering/</guid>
      <pubDate>Tue, 06 Dec 2022 14:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Get insights into the day-to-day challenges of builders. In this issue, Florian Dröge and Lars Hüper from our partner tecRacer share insights into crafting Serverless applications that last.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/diary@730w.webp 730w, /images/2022/06/diary@730w2x.webp 1460w, /images/2022/06/diary@610w.webp 610w, /images/2022/06/diary@610w2x.webp 1220w, /images/2022/06/diary@450w.webp 450w, /images/2022/06/diary@450w2x.webp 900w, /images/2022/06/diary@330w.webp 330w, /images/2022/06/diary@330w2x.webp 660w, /images/2022/06/diary@545w.webp 545w, /images/2022/06/diary@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/diary@730w.jpg 730w, /images/2022/06/diary@730w2x.jpg 1460w, /images/2022/06/diary@610w.jpg 610w, /images/2022/06/diary@610w2x.jpg 1220w, /images/2022/06/diary@450w.jpg 450w, /images/2022/06/diary@450w2x.jpg 900w, /images/2022/06/diary@330w.jpg 330w, /images/2022/06/diary@330w2x.jpg 660w, /images/2022/06/diary@545w.jpg 545w, /images/2022/06/diary@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/diary.jpg" alt="Builder's Diary " title="Builder's Diary "></picture></p><p>If you prefer a video or podcast instead of reading, here you go.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=oryQyuUwyvs">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/oryQyuUwyvs" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/63-serverless-software-engineering-builders-diary-vol-4/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>Find a written excerpt of the interview with Florian Dröge and Lars Hüper in the following.</p><p><em><strong>cloudonaut:</strong> What does a typical Serverless project at tecRacer look like?</em></p><p><strong>Lars Hüper:</strong> Our team focuses on Serverless projects. Our projects are diverse. For example, we modernize applications by replacing an MS SQL database with a cloud-native database like Aurora Serverless. We also work on large projects where we build complex Serverless applications. An example we will surely mention more often is the new development of software for pension schemes. In this project, we build up an internal development team on behalf of the customer for years. We assume the role of architects, developers, and coaches in this project. The pension scheme application we are building has a time horizon of over 20 years.</p><p><em><strong>cloudonaut:</strong> That’s amazing; the fact that you have accompanied projects for many years is outstanding. Most Serverless projects we have worked on were proofs-of-concept. What’s your favorite technology stack for building Serverless applications?</em></p><p><strong>Florian Dröge:</strong> We prefer writing TypeScript. This applies to Infrastructure as Code with the AWS CDK, as well as to the Lambda-based backend and also the frontend. Doing so allows developers to quickly make changes to all parts of the stack, which increases agility a lot. Despite this, most developers focus on specific areas to deepen their expertise.</p><p>On top of that, we use the common Serverless building blocks:</p><ul><li>AWS Lambda</li><li>Amazon API Gateway and AWS AppSync</li><li>Amazon SNS and EventBridge</li><li>Amazon DynamoDB and Aurora Serverless</li><li>Amazon S3</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/11/serverless-architecture@730w.webp 730w, /images/2022/11/serverless-architecture@730w2x.webp 1460w, /images/2022/11/serverless-architecture@610w.webp 610w, /images/2022/11/serverless-architecture@610w2x.webp 1220w, /images/2022/11/serverless-architecture@450w.webp 450w, /images/2022/11/serverless-architecture@450w2x.webp 900w, /images/2022/11/serverless-architecture@330w.webp 330w, /images/2022/11/serverless-architecture@330w2x.webp 660w, /images/2022/11/serverless-architecture@545w.webp 545w, /images/2022/11/serverless-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/11/serverless-architecture@730w.png 730w, /images/2022/11/serverless-architecture@730w2x.png 1460w, /images/2022/11/serverless-architecture@610w.png 610w, /images/2022/11/serverless-architecture@610w2x.png 1220w, /images/2022/11/serverless-architecture@450w.png 450w, /images/2022/11/serverless-architecture@450w2x.png 900w, /images/2022/11/serverless-architecture@330w.png 330w, /images/2022/11/serverless-architecture@330w2x.png 660w, /images/2022/11/serverless-architecture@545w.png 545w, /images/2022/11/serverless-architecture@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/11/serverless-architecture.png" alt="Serverless building blocks" title="Serverless building blocks"></picture></p><p><em><strong>cloudonaut:</strong> What are the challenges of developing complex systems using serverless technologies? And how do you overcome them?</em></p><p><strong>Lars Hüper:</strong> A significant advantage of the Serverless approach is that it’s straightforward for a developer to get up and running and ship code right away. However, I would like to issue a warning. It is essential to understand the problem you are trying to solve before writing code and assembling building blocks.</p><p>Therefore, we ask questions like “How does the process work right now?” and “Why do you want to digitize the process?”. On top of that, we focus on understanding the business domain. For example, we’ve learned much about pension schemes in the past few years.</p><p>In other words, we are following the domain-driven design (DDD) approach:</p><ol><li>We start with event storming, a workshop to learn about the domain quickly.</li><li>We design the system with input from domain experts.</li><li>We start implementing and shipping software.</li></ol><p>We realized that it is crucial to go back to the first step from time to time. Therefore, we conduct event-storming workshops from time to time.</p><p><em><strong>cloudonaut:</strong> I am impressed with how you craftmanship your software. How do you ensure you can replace services or the whole cloud provider over time?</em></p><p><strong>Florian Dröge:</strong> As I said, we are planning a life cycle of more than 20 years for the pension scheme application. As a result, we expect to replace some of the services we use today over time.</p><p>We strive for <em>Clean Architecture</em> as Robert C. Martin (Uncle Bob) described. An architecture following this approach is represented by four circles.</p><ol><li><em>Entities</em> the business rules and objects.</li><li><em>Use Cases</em> the application-specific business rules.</li><li><em>Interface Adapters</em> the converts that transform the data into different formats.</li><li><em>Frameworks and Drivers</em> the database, the frontend framework, …</li></ol><p>According to the dependency rule of <em>Clean Architecture</em>, dependencies may only point from the outside to the inside.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/11/clean-architecture@730w.webp 730w, /images/2022/11/clean-architecture@730w2x.webp 1460w, /images/2022/11/clean-architecture@610w.webp 610w, /images/2022/11/clean-architecture@610w2x.webp 1220w, /images/2022/11/clean-architecture@450w.webp 450w, /images/2022/11/clean-architecture@450w2x.webp 900w, /images/2022/11/clean-architecture@330w.webp 330w, /images/2022/11/clean-architecture@330w2x.webp 660w, /images/2022/11/clean-architecture@545w.webp 545w, /images/2022/11/clean-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/11/clean-architecture@730w.png 730w, /images/2022/11/clean-architecture@730w2x.png 1460w, /images/2022/11/clean-architecture@610w.png 610w, /images/2022/11/clean-architecture@610w2x.png 1220w, /images/2022/11/clean-architecture@450w.png 450w, /images/2022/11/clean-architecture@450w2x.png 900w, /images/2022/11/clean-architecture@330w.png 330w, /images/2022/11/clean-architecture@330w2x.png 660w, /images/2022/11/clean-architecture@545w.png 545w, /images/2022/11/clean-architecture@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/11/clean-architecture.png" alt="Clean Architecture" title="Clean Architecture"></picture></p><p>So, our core business logic does not even know that it runs on Serverless or that we use DynamoDB to persist data. This pattern allows us to replace services when needed. For example, we replaced DynamoDB with Aurora Serverless in a specific scenario. We are not reinventing the wheel but are applying software engineering principles to Serverless development. And that’s what we are missing in the documentation and examples provided by AWS.</p><p>_<strong>cloudonaut:</strong> That’s great! You mentioned that you replaced DynamoDB with Aurora Serverless as the access pattern changed during the project. Please share more insights into how Serverless architectures evolve.</p><p><strong>Florian Dröge:</strong> The first thing that comes to my mind is that at the beginning of a project, developers create a lot of DynamoDB tables to persist data. As the understanding of the access pattern improves over time, we aim for the single-table or other NoSQL patterns. On top of that, as our understanding of the data queries grows, we rethink whether we are using the best tool for the job. So we consider using specialized databases like Amazon Neptune, a graph database, for example.</p><p><strong>Lars Hüper:</strong> Let me add another example. We’ve observed that SNS was used to implement component-to-component communication in a “remote procedure call”-like pattern. Therefore, we revisited that area with a focus on following the event-driven design. Don’t get me wrong, there are occasions where RPC-like calls between Lambda functions are fine, but only in a very limited scope. Outside the scope, the event-driven design allows for loose coupling of components.</p><p><strong>Florian Dröge:</strong> One more thing, we noticed that we needed to hand over some parameters like the name of a global S3 bucket to many Lambda functions. Passing those parameters through our whole infrastructure code was cumbersome. Therefore, we added AWS AppConfing to the mix. We use AppConfig to manage global parameters that all or most of our Lambda functions require. For local parameters, we still use environment variables.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/11/appconfig@730w.webp 730w, /images/2022/11/appconfig@730w2x.webp 1460w, /images/2022/11/appconfig@610w.webp 610w, /images/2022/11/appconfig@610w2x.webp 1220w, /images/2022/11/appconfig@450w.webp 450w, /images/2022/11/appconfig@450w2x.webp 900w, /images/2022/11/appconfig@330w.webp 330w, /images/2022/11/appconfig@330w2x.webp 660w, /images/2022/11/appconfig@545w.webp 545w, /images/2022/11/appconfig@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/11/appconfig@730w.png 730w, /images/2022/11/appconfig@730w2x.png 1460w, /images/2022/11/appconfig@610w.png 610w, /images/2022/11/appconfig@610w2x.png 1220w, /images/2022/11/appconfig@450w.png 450w, /images/2022/11/appconfig@450w2x.png 900w, /images/2022/11/appconfig@330w.png 330w, /images/2022/11/appconfig@330w2x.png 660w, /images/2022/11/appconfig@545w.png 545w, /images/2022/11/appconfig@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/11/appconfig.png" alt="Lambda configuration parameters: AppConfig and environment variables" title="Lambda configuration parameters: AppConfig and environment variables"></picture></p><p><strong>Lars Hüper:</strong> We also encountered difficulties deploying our CDK code. We realized that splitting your infrastructure into many small stacks caused troubles due to missing dependency declarations. Therefore, we are now using larger stacks and bundling them together using nested stacks, which simplifies dependency management for us.</p><p><em><strong>cloudonaut:</strong></em> Thanks a lot, Florian and Lars, for sharing your insights into developing Serverless applications following domain-driven design and clean architecture. I learned a lot from both of you.</p><blockquote><div class="row"><div class="col-2"><img src="/img/cta/jobs/tecracer.jpg" class="img-fluid"></div><div class="col-10"><strong>Open Position: Cloud Consultant AWS Serverless Development</strong><br>Would you like to join Florian and Lars's team to engineer advanced Serverless applications? tecRacer is hiring a Cloud Consultant focusing on AWS Serverless. <a href="https://jobs.tecracer.com?utm_source=cloudonaut&utm_medium=blog&utm_campaign=cloudonaut_talent_2022_05">Apply now!</a></div></blockquote>]]>
      </content:encoded>
    </item>
    <item>
      <title>Hot off the Cloud #007: AppSync JavaScript Resolvers + IAM MFA + CloudFront CD</title>
      <link>https://cloudonaut.io/hot-off-the-cloud-007/</link>
      <description>
        <![CDATA[<p>Here are our toughts about the latest AWS announcements as well as our lessons learned.</p>
<p><picture class="img-fluid"><source type="i]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/cloudfront/">cloudfront</category>
      <category domain="https://cloudonaut.io/tag/appsync/">appsync</category>
      <guid isPermaLink="true">https://cloudonaut.io/hot-off-the-cloud-007/</guid>
      <pubDate>Wed, 23 Nov 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Here are our toughts about the latest AWS announcements as well as our lessons learned.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/11/hot-off-the-cloud@730w.webp 730w, /images/2022/11/hot-off-the-cloud@730w2x.webp 1460w, /images/2022/11/hot-off-the-cloud@610w.webp 610w, /images/2022/11/hot-off-the-cloud@610w2x.webp 1220w, /images/2022/11/hot-off-the-cloud@450w.webp 450w, /images/2022/11/hot-off-the-cloud@450w2x.webp 900w, /images/2022/11/hot-off-the-cloud@330w.webp 330w, /images/2022/11/hot-off-the-cloud@330w2x.webp 660w, /images/2022/11/hot-off-the-cloud@545w.webp 545w, /images/2022/11/hot-off-the-cloud@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/11/hot-off-the-cloud@730w.jpg 730w, /images/2022/11/hot-off-the-cloud@730w2x.jpg 1460w, /images/2022/11/hot-off-the-cloud@610w.jpg 610w, /images/2022/11/hot-off-the-cloud@610w2x.jpg 1220w, /images/2022/11/hot-off-the-cloud@450w.jpg 450w, /images/2022/11/hot-off-the-cloud@450w2x.jpg 900w, /images/2022/11/hot-off-the-cloud@330w.jpg 330w, /images/2022/11/hot-off-the-cloud@330w2x.jpg 660w, /images/2022/11/hot-off-the-cloud@545w.jpg 545w, /images/2022/11/hot-off-the-cloud@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/11/hot-off-the-cloud.jpg" alt="Hot off the Cloud" title="Hot off the Cloud"></picture></p><p>Unbelievable, re:Invent starts in 5 days. Therefore it is no surprise that AWS announces new features at a high pace. On the one hand, we are having difficulty keeping track of all the news. On the other hand, we are excited to see our preferred cloud platform evolve. Unfortunately, we are not traveling to Las Vegas this year. So we will follow re:Invent from abroad.</p><h2 id="Live-Stream"><a href="#Live-Stream" class="headerlink" title="Live Stream"></a>Live Stream</h2><p>Before we start, this is the written version of our weekly show Hot off the Cloud. Check out the recording in case you prefer watching videos instead of reading!</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=xkDUgcyDSDM">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/xkDUgcyDSDM" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="AWS-News"><a href="#AWS-News" class="headerlink" title="AWS News"></a>AWS News</h2><p>In the following you find our thoughts about the AWS announcements form the past 7 days.</p><blockquote><p>Want this as a weekly newsletter in your inbox? <a href="/newsletter/">Sign up for our newsletter!</a></p></blockquote><h3 id="Amazon-S3-Glacier-improves-restore-throughput-by-up-to-10x-when-retrieving-large-volumes-of-archived-data"><a href="#Amazon-S3-Glacier-improves-restore-throughput-by-up-to-10x-when-retrieving-large-volumes-of-archived-data" class="headerlink" title="Amazon S3 Glacier improves restore throughput by up to 10x when retrieving large volumes of archived data"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/11/amazon-s3-glacier-restore-throughput-10x-large-volumes-archived-data/" target="_blank" rel="noopener">Amazon S3 Glacier improves restore throughput by up to 10x when retrieving large volumes of archived data</a></h3><p>Restoring data archived with S3 Glacier is getting faster, as AWS increased the number of restore requests to 1000 TPS per account and region. That’s 10x what has been possible before. So restoring significant amounts of objects is possible at a higher speed from now on.</p><h3 id="AWS-AppSync-GraphQL-APIs-Supports-JavaScript-Resolvers"><a href="#AWS-AppSync-GraphQL-APIs-Supports-JavaScript-Resolvers" class="headerlink" title="AWS AppSync GraphQL APIs Supports JavaScript Resolvers"></a><a href="https://aws.amazon.com/blogs/aws/aws-appsync-graphql-apis-supports-javascript-resolvers/" target="_blank" rel="noopener">AWS AppSync GraphQL APIs Supports JavaScript Resolvers</a></h3><p>Hurray, no need to write resolvers in the Apache Velocity Template Language (VTL) anymore. AppSyny announces support for JavaScript resolvers!<br>Note that the JavaScript runtime provides similar functionality to ECMAScript 6.0 but supports only a subset of its features.</p><p>Check out the modules <code>@aws-appsync/utils</code> and <code>@aws-appsync/eslint-plugin</code> as they speed up writing and testing JavaScript resolvers.</p><p>Also, JavaScript resolvers for AppSync are already supported by CloudFormation. Only one thing is missing before we start using AppSync in production: throttling&#x2F;rate limiting per tenant&#x2F;user.</p><h2 id="You-can-now-assign-multiple-MFA-devices-in-IAM"><a href="#You-can-now-assign-multiple-MFA-devices-in-IAM" class="headerlink" title="You can now assign multiple MFA devices in IAM"></a><a href="https://aws.amazon.com/blogs/security/you-can-now-assign-multiple-mfa-devices-in-iam/" target="_blank" rel="noopener">You can now assign multiple MFA devices in IAM</a></h2><p>We have been waiting for this feature for years! Finally, we can add multiple MFA devices to our AWS account root users. Until now, we had to use a virtual device, as we both need to log in but are located in different locations. Today, we added 4 YubiKeys as the MFA devices for our root users. Each of us owns two YubiKeys, one of which serves as a backup.</p><h3 id="New-region-in-Spain-and-India"><a href="#New-region-in-Spain-and-India" class="headerlink" title="New region in Spain and India"></a><a href="https://aws.amazon.com/blogs/aws/now-open-aws-region-in-spain/" target="_blank" rel="noopener">New region in Spain and India</a></h3><p>We are impressed by the pace at which AWS is bringing new Regions online. With eu-south-2 (Aragón) and ap-south-2 (Hyderabad), we can choose between 26 regions (plus 2 GovRegions + 2 regions in China).</p><p>Be aware that new regions only support some AWS services from the beginning. For example, we have been running into the issue that AWS Backup and Cognito are not yet available in new regions.</p><h3 id="Node-js-18-x-runtime-now-available-in-AWS-Lambda"><a href="#Node-js-18-x-runtime-now-available-in-AWS-Lambda" class="headerlink" title="Node.js 18.x runtime now available in AWS Lambda"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/11/aws-lambda-support-node-js-18/" target="_blank" rel="noopener">Node.js 18.x runtime now available in AWS Lambda</a></h3><p>First, we celebrate this announcement as we love to work with the latest technology.</p><p>Second, be aware that the Node.js 18.x runtime does not come with v2 of the AWS SDK for JavaScript but provides v3 only. As there are breaking changes between v2 and v3, you need to update your code accordingly before switching to the Node.js 18.x runtime.</p><p>Third, Node.js 18, in general comes with interesting new features:</p><ul><li><strong>Native Fetch API</strong> the new standard for HTTP requests</li><li><strong>Web Streams API</strong> simplifies processing data as a stream</li><li><strong>HTTP Timeouts</strong> configure timeouts for recieving HTTP headers</li><li>…</li></ul><h3 id="Amazon-CloudFront-launches-continuous-deployment-support"><a href="#Amazon-CloudFront-launches-continuous-deployment-support" class="headerlink" title="Amazon CloudFront launches continuous deployment support"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/11/amazon-cloudfront-continuous-deployment-support/" target="_blank" rel="noopener">Amazon CloudFront launches continuous deployment support</a></h3><p>We are using CloudFront to host our blog cloudonaut.io. The setup is complex, as we use Lambda@Edge to redirect requests or handle authentication, for example. Therefore, when we need to deploy changes to our CloudFront configuration, it happens that we break our blog.</p><p>Luckily, AWS announced continuous deployment support for CloudFront. Here is how the new feature works.</p><ol><li>Create a staging distribution, which belongs to the original distribution, with the configuration you are planning to ship.</li><li>Configure how CloudFront should decide whether to send a request to the original or staging distribution by header or weight.</li><li>Watch the CloudWatch metrics and logs for 5XX errors, increased latency, or other issues.</li><li>Update the original distribution with the configuration you tested on staging.</li></ol><p>Unfortunately, AWS does not yet provide a way to automate the blue-green deployment. Especially when you are using Infrastructure as Code, orchestrating the blue-green deployment is tricky.</p><p>Also, be aware that continuous deployment is not supported for distributions with HTTP&#x2F;3 enabled. Also, there is no guarantee that CloudFront will forward requests to the staging distribution as configured -especially under high load- CloudFront might decide to send all requests to the original distribution.</p><h3 id="Manage-your-resources-from-AWS-Organizations-using-AWS-CloudFormation"><a href="#Manage-your-resources-from-AWS-Organizations-using-AWS-CloudFormation" class="headerlink" title="Manage your resources from AWS Organizations using AWS CloudFormation"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/11/manage-resources-aws-organizations-cloudformation/" target="_blank" rel="noopener">Manage your resources from AWS Organizations using AWS CloudFormation</a></h3><p>After so many hours spent automating the process of provisioning AWS accounts and organizations, AWS finally releases CloudFormation support for accounts, organizational units, and policies.</p><ul><li><code>AWS::Organizations::Account</code></li><li><code>AWS::Organizations::OrganizationalUnit</code></li><li><code>AWS::Organizations::Policy</code></li></ul><p>So far, we provisioned AWS accounts manually. Now, we are migrating the accounts and organization to CloudFormation. The good news is that CloudFormation even supports importing those resources. Thumbs up!</p><h3 id="AWS-IAM-Identity-Center-now-supports-session-management-capabilities-for-AWS-Command-Line-Interface-AWS-CLI-and-SDKs"><a href="#AWS-IAM-Identity-Center-now-supports-session-management-capabilities-for-AWS-Command-Line-Interface-AWS-CLI-and-SDKs" class="headerlink" title="AWS IAM Identity Center now supports session management capabilities for AWS Command Line Interface (AWS CLI) and SDKs"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/11/aws-iam-identity-center-session-management-aws-cli-sdks/" target="_blank" rel="noopener">AWS IAM Identity Center now supports session management capabilities for AWS Command Line Interface (AWS CLI) and SDKs</a></h3><p>When AWS announced the possibility of controlling the session length for the IAM Identity Center (formerly AWS SSO), we complained that the session duration did not apply to the temporary credentials used for the CLI and SDKs.<br>And just a short time later, AWS solves this problem exactly. That’s wonderful!</p><h2 id="Lesson-Learned-Rotating-KMS-Keys-is-getting-costly-over-time"><a href="#Lesson-Learned-Rotating-KMS-Keys-is-getting-costly-over-time" class="headerlink" title="Lesson Learned: Rotating KMS Keys is getting costly over time!"></a>Lesson Learned: Rotating KMS Keys is getting costly over time!</h2><p>Do you enable key rotation for customer-managed KMS keys? We do so because many compliance checks like the AWS Security Hub ask you to do so.</p><p>Did you know that the cost for a customer-managed KMS key increases by $1 per month each time the key gets rotated? We were surprised that we were already paying $3 per month for one of our keys. And there’s no way to undo that. Old keys cannot be deleted, as they may have been used to encrypt data that still needs to be accessed.</p><p>We will think twice before enabling key rotation for customer-manged KMS keys in the future. Rotating keys offers minimal advantages from a security point of view, as existing data is not re-encrypted when rotating keys.</p><p>What are your thoughts on rotating customer-managed KMS keys?</p><blockquote><p>Want this as a weekly newsletter in your inbox? <a href="/newsletter/">Sign up for our newsletter!</a></p></blockquote>]]>
      </content:encoded>
    </item>
    <item>
      <title>Mastodon on AWS: Host your own instance</title>
      <link>https://cloudonaut.io/mastodon-on-aws/</link>
      <description>
        <![CDATA[<p>While Twitter seems to be in chaos, the free and decentralized alternative, Mastodon, is rising. At first glance, Twitter and Mastodon ar]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/rds/">rds</category>
      <category domain="https://cloudonaut.io/tag/elasticache/">elasticache</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <guid isPermaLink="true">https://cloudonaut.io/mastodon-on-aws/</guid>
      <pubDate>Thu, 17 Nov 2022 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>While Twitter seems to be in chaos, the free and decentralized alternative, Mastodon, is rising. At first glance, Twitter and Mastodon are similar. A toot is to Mastodon what a tweet is to Twitter. However, the decentralized nature of Mastodon makes a big difference. If you want to register with Mastodon, you first have to choose an instance (see <a href="https://instances.social/" target="_blank" rel="noopener">instances.social</a>). And it’s even possible to host your own Mastodon instance. That’s what we are up to: <a href="https://social.cloudonaut.io/" target="_blank" rel="noopener">social.cloudonaut.io</a>.</p><blockquote><p>Speaking of Mastodon, don’t forget to connect with us in the fediverse: <a href="https://social.cloudonaut.io/@andreas" target="_blank" rel="noopener">@andreas@social.cloudonaut.io</a> and <a href="https://social.cloudonaut.io/@michael" target="_blank" rel="noopener">@michael@social.cloudonaut.io</a>.</p></blockquote><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/11/mastodon@730w.webp 730w, /images/2022/11/mastodon@730w2x.webp 1460w, /images/2022/11/mastodon@610w.webp 610w, /images/2022/11/mastodon@610w2x.webp 1220w, /images/2022/11/mastodon@450w.webp 450w, /images/2022/11/mastodon@450w2x.webp 900w, /images/2022/11/mastodon@330w.webp 330w, /images/2022/11/mastodon@330w2x.webp 660w, /images/2022/11/mastodon@545w.webp 545w, /images/2022/11/mastodon@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/11/mastodon@730w.jpg 730w, /images/2022/11/mastodon@730w2x.jpg 1460w, /images/2022/11/mastodon@610w.jpg 610w, /images/2022/11/mastodon@610w2x.jpg 1220w, /images/2022/11/mastodon@450w.jpg 450w, /images/2022/11/mastodon@450w2x.jpg 900w, /images/2022/11/mastodon@330w.jpg 330w, /images/2022/11/mastodon@330w2x.jpg 660w, /images/2022/11/mastodon@545w.jpg 545w, /images/2022/11/mastodon@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/11/mastodon.jpg" alt="Mastodon on AWS" title="Mastodon on AWS"></picture></p><p>In the following, I will share details on how to deploy a Mastodon instance on AWS in case you want to host your own server as well.</p><h2 id="AWS-Architecture-for-Mastodon-on-AWS"><a href="#AWS-Architecture-for-Mastodon-on-AWS" class="headerlink" title="AWS Architecture for Mastodon on AWS"></a>AWS Architecture for Mastodon on AWS</h2><p>The following figure shows our architecture to deploy Mastodon on AWS.</p><ul><li>Route 53</li><li>Application Load Balancer (ALB)</li><li>ECS + Fargate</li><li>RDS for Postgres</li><li>ElastiCache for Redis</li><li>S3</li><li>SES</li><li>CloudWatch</li><li>VPC</li><li>IAM</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/11/mastodon-aws-architecture@730w.webp 730w, /images/2022/11/mastodon-aws-architecture@730w2x.webp 1460w, /images/2022/11/mastodon-aws-architecture@610w.webp 610w, /images/2022/11/mastodon-aws-architecture@610w2x.webp 1220w, /images/2022/11/mastodon-aws-architecture@450w.webp 450w, /images/2022/11/mastodon-aws-architecture@450w2x.webp 900w, /images/2022/11/mastodon-aws-architecture@330w.webp 330w, /images/2022/11/mastodon-aws-architecture@330w2x.webp 660w, /images/2022/11/mastodon-aws-architecture@545w.webp 545w, /images/2022/11/mastodon-aws-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/11/mastodon-aws-architecture@730w.png 730w, /images/2022/11/mastodon-aws-architecture@730w2x.png 1460w, /images/2022/11/mastodon-aws-architecture@610w.png 610w, /images/2022/11/mastodon-aws-architecture@610w2x.png 1220w, /images/2022/11/mastodon-aws-architecture@450w.png 450w, /images/2022/11/mastodon-aws-architecture@450w2x.png 900w, /images/2022/11/mastodon-aws-architecture@330w.png 330w, /images/2022/11/mastodon-aws-architecture@330w2x.png 660w, /images/2022/11/mastodon-aws-architecture@545w.png 545w, /images/2022/11/mastodon-aws-architecture@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/11/mastodon-aws-architecture.png" alt="Mastodon on AWS: Architeture" title="Mastodon on AWS: Architeture"></picture></p><p>What’s missing? So far, I did not deploy Elasticsearch yet. Elasticsearch provides optional search capabilities for Mastodon, for example, to search through your toots. I also haven’t deployed CloudFront as a CDN yet in front of the static files hosted on S3 and maybe even for some files delivered by the web application itself.</p><h2 id="Costs-for-running-Mastodon-on-AWS"><a href="#Costs-for-running-Mastodon-on-AWS" class="headerlink" title="Costs for running Mastodon on AWS"></a>Costs for running Mastodon on AWS</h2><p>Estimating costs for AWS is not trivial. My estimation assumes a small Mastodon instance for 1-50 users. The architecture’s monthly charges are about $60 per month. The following table lists the details.</p><table class="table table-striped table-responsive"><thead><tr><th>Service</th><th>Configuration</th><th style="text-align:right">Monthly Costs (USD)</th></tr></thead><tbody><tr><td>ECS + Fargate</td><td>3 Spot Tasks x (0.25 CPU + 0.5 GB)</td><td style="text-align:right">$8.66</td></tr><tr><td>RDS for Postgres</td><td>t4g.micro (Multi-AZ)</td><td style="text-align:right">$23.61</td></tr><tr><td>ElastiCache for Redis</td><td>t4g.micro (Single-AZ)</td><td style="text-align:right">$11.52</td></tr><tr><td>ALB</td><td>Load Balancer Hours</td><td style="text-align:right">$16.20</td></tr><tr><td>S3</td><td>25 GB + requests</td><td style="text-align:right">$0.58</td></tr><tr><td>Route 53</td><td>Hosted Zone</td><td style="text-align:right">$0.50</td></tr><tr><td><strong>Total</strong></td><td></td><td style="text-align:right">$61.08</td></tr></tbody></table><p>Please note that my cost estimation is not complete. For example, the estimation does not include network traffic, CloudWatch, SES, or domain.</p><p>Isn’t there a cheaper way to host Mastodon on AWS?</p><h2 id="Why-not-run-Mastodon-on-a-single-EC2-instance"><a href="#Why-not-run-Mastodon-on-a-single-EC2-instance" class="headerlink" title="Why not run Mastodon on a single EC2 instance?"></a>Why not run Mastodon on a single EC2 instance?</h2><p>Of course, you could also run Mastodon, the Postgres database, the Redis in-memory database, and even Elasticsearch on a virtual machine. Assuming all that fits on an EC2 instance of type <code>t4g.medium</code>, you are paying about $25 per month. That’s a simple and cost-effective way to run Mastodon on AWS. However, you get the following benefits by outsourcing data storage to services like RDS, ElastiCache, and S3:</p><ol><li>You are able to scale the application layer horizontally by adding virtual machines or containers.</li><li>You reduce maintenance effort. For example, managed services like RDS come with automated patching and backups.</li><li>You increase the resilience and availability as the managed services spread the storage layer among multiple availability zones out of the box.</li><li>You enable 0-downtime deployments, as you can spin up virtual machines or containers with the new version before terminating the old ones.</li></ol><h2 id="Deploying-Mastodon-containers"><a href="#Deploying-Mastodon-containers" class="headerlink" title="Deploying Mastodon containers"></a>Deploying Mastodon containers</h2><p>Luckily, Mastodon maintains a container image <a href="https://hub.docker.com/r/tootsuite/mastodon" target="_blank" rel="noopener">tootsuite&#x2F;mastodon</a> that we can use to deploy the three services a Mastodon instance consists of:</p><ul><li><code>web</code> delivers the web application consisting of a frontend and backend (Ruby on Rails).</li><li><code>streaming</code> allows clients to subscribe to server-sent events for real-time updates via a long-lived HTTP connection or WebSocket.</li><li><code>sidekiq</code> orchestrates and executes background jobs.</li></ul><p>As all three services are bundled into a single container image, it is necessary to use different commands when starting the containers.</p><ul><li><code>bundle exec rails s -p 3000</code> to start the <code>web</code> application.</li><li><code>node ./streaming</code> to start the streaming API.</li><li><code>bundle exec sidekiq</code> to start <code>sidekiq</code>.</li></ul><p>Configuring Mastodon is simple. All parameters are configurable via environment variables. See <a href="https://docs.joinmastodon.org/admin/config/" target="_blank" rel="noopener">Configuring your environment</a> for a list of all parameters.</p><h2 id="What-I-learned-from-running-Mastodon-on-AWS"><a href="#What-I-learned-from-running-Mastodon-on-AWS" class="headerlink" title="What I learned from running Mastodon on AWS"></a>What I learned from running Mastodon on AWS</h2><ol><li>Aurora Serverless v1&#x2F;v2 is not a good choice for Mastodon, as there is load on the database even if no users are actively using the instance. So Aurora Serverless v1 cannot pause. In general, Aurora Serverless is quite expensive, a minimal RDS for Postgres instance reduces costs.</li><li>Mastodon stores the <code>Home</code> timeline in Redis. I replaced an ElastiCache instance which resulted in an empty <code>Home</code> timeline that Mastodon backfills asynchronously.</li><li>The following configuration files from the Mastodon repository help understand how to deploy Mastodon containers: <a href="https://github.com/mastodon/mastodon/blob/main/Dockerfile" target="_blank" rel="noopener">Dockerfile</a>, <a href="https://github.com/mastodon/mastodon/blob/main/docker-compose.yml" target="_blank" rel="noopener">docker-compose.yml</a>, and <a href="https://github.com/mastodon/mastodon/blob/main/dist/nginx.conf" target="_blank" rel="noopener">nginx.conf</a>.</li><li>As we are currently running a minimal Mastodon instance, I could not verify it, but it <a href="https://github.com/mastodon/mastodon/issues/15543" target="_blank" rel="noopener">should be possible to scale Mastodon horizontally</a> (except the Postgres database).</li><li>Updating from version <code>v3.5.3</code> to <code>v4.0.2</code> worked by replacing the container image tags in my CloudFormation template.</li></ol><h2 id="Launch-a-Mastodon-instance-on-AWS-in-30-minutes"><a href="#Launch-a-Mastodon-instance-on-AWS-in-30-minutes" class="headerlink" title="Launch a Mastodon instance on AWS in 30 minutes"></a>Launch a Mastodon instance on AWS in 30 minutes</h2><p>We started working on an Infrastructure as Code project about a week ago: <a href="https://github.com/widdix/mastodon-on-aws" target="_blank" rel="noopener">widdix&#x2F;mastodon-on-aws</a>. The project allows you to deploy Mastodon on AWS with a few clicks in about 30 minutes.</p><p>First, you need an AWS account.</p><p>Second, a top-level or subdomain where you can configure an NS record to delegate to the Route 53 nameservers is required. For example, you could register a domain with Route 53 or use an existing domain and add an NS record to the hosted zone.</p><p>Third, <a href="https://console.aws.amazon.com/cloudformation/home#/stacks/create/review?templateURL=https://s3.eu-central-1.amazonaws.com/mastodon-on-aws-cloudformation/v0.5.0/quickstart.yml&stackName=mastodon-on-aws" target="_blank" rel="noopener">click here to deploy Mastodon on AWS</a> to your AWS account.</p><p>To generate the required secrets and keys, use the following commands.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Start Docker container locally</span></span><br><span class="line">$ docker run -it tootsuite/mastodon:latest sh</span><br><span class="line"></span><br><span class="line"><span class="comment"># Generate SECRET_KEY_BASE</span></span><br><span class="line">$ bundle <span class="built_in">exec</span> rake secret</span><br><span class="line">758a3b431265776b9ab55910890162bb84aec0617724ca611475c3a774965f2d0aca183091d3c1a84ff3640cf7cc438c559034a2735253ee895b7a2308ac450c</span><br><span class="line"></span><br><span class="line"><span class="comment"># Generate OTP_SECRET</span></span><br><span class="line">$ bundle <span class="built_in">exec</span> rake secret</span><br><span class="line">c528b5cbb0236e4b0c2fe38a6d7ed1edc5fa12608c67a45690e225f005bad8bfbabfa99f7b83cb9c0981ba8fcc5fd76c68918d9bc854bd158c2c23fd6df89abc</span><br><span class="line"></span><br><span class="line"><span class="comment"># Generate VAPID_PRIVATE_KEY and VAPID_PUBLIC_KEY</span></span><br><span class="line">$ bundle <span class="built_in">exec</span> rake mastodon:webpush:generate_vapid_key</span><br><span class="line">VAPID_PRIVATE_KEY=am3vlPBGQGv7Rl3xOKXSv7lRYyWfZITItb88FXX9IOs=</span><br><span class="line">VAPID_PUBLIC_KEY=BMGkIr1PaK4v7Kut7q7eoHtWxu9gEBQ5BeV28xOIR9c9VIvDWvOViTn1SV5G2LIEFGWo0f1dQka-UynR58WMn2Y=</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>I’m curious to see if Mastodon catches on as an alternative to Twitter for us. Running your own Mastodon instance under your domain is a plus. And with AWS and our Infrastructure as Code project <a href="https://github.com/widdix/mastodon-on-aws" target="_blank" rel="noopener">widdix&#x2F;mastodon-on-aws</a>, it’s not hard at all.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Builder's Diary Vol. 3: Infrastructure Pipeline with GitLab and Terraform Cloud</title>
      <link>https://cloudonaut.io/builders-diary-vol3-infrastructure-pipeline-gitlab-terraform-cloud/</link>
      <description>
        <![CDATA[<p>Get insights into the day-to-day challenges of builders. In this issue, Rico Nuguid from our partner DEMICON talks about automating deplo]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/terraform/">terraform</category>
      <guid isPermaLink="true">https://cloudonaut.io/builders-diary-vol3-infrastructure-pipeline-gitlab-terraform-cloud/</guid>
      <pubDate>Thu, 03 Nov 2022 20:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Get insights into the day-to-day challenges of builders. In this issue, Rico Nuguid from our partner DEMICON talks about automating deployments with Infrastructure Pipelines based on GitLab and Terraform Cloud.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/diary@730w.webp 730w, /images/2022/06/diary@730w2x.webp 1460w, /images/2022/06/diary@610w.webp 610w, /images/2022/06/diary@610w2x.webp 1220w, /images/2022/06/diary@450w.webp 450w, /images/2022/06/diary@450w2x.webp 900w, /images/2022/06/diary@330w.webp 330w, /images/2022/06/diary@330w2x.webp 660w, /images/2022/06/diary@545w.webp 545w, /images/2022/06/diary@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/diary@730w.jpg 730w, /images/2022/06/diary@730w2x.jpg 1460w, /images/2022/06/diary@610w.jpg 610w, /images/2022/06/diary@610w2x.jpg 1220w, /images/2022/06/diary@450w.jpg 450w, /images/2022/06/diary@450w2x.jpg 900w, /images/2022/06/diary@330w.jpg 330w, /images/2022/06/diary@330w2x.jpg 660w, /images/2022/06/diary@545w.jpg 545w, /images/2022/06/diary@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/diary.jpg" alt="Builder's Diary " title="Builder's Diary "></picture></p><p>If you prefer a video or podcast instead of reading, here you go.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=4B4jofqrL64">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/4B4jofqrL64" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/57-infrastructure-pipeline-with-gitlab-and-terraform-cloud-builders-diary-vol-3/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="How-did-you-get-into-all-things-AWS"><a href="#How-did-you-get-into-all-things-AWS" class="headerlink" title="How did you get into all things AWS?"></a>How did you get into all things AWS?</h2><p>I started my career as an IT consultant at IBM, where I was part of building the cloud division. Next, I co-founded Tandemploy, where we created a cloud-based HR-Software on Microsoft Azure. In 2020, I joined DEMICON as Business Unit Lead and Principal Cloud Solution Architect. At DEMICON, I deal almost exclusively with AWS. And over the last two years, I have become deeply familiar with Amazon’s cloud. All three cloud providers are similar, as the basic principles are the same everywhere. In detail, however, IBM Cloud, Microsoft Azure, and AWS differ enormously.</p><h2 id="What-is-it-like-to-work-as-a-consultant-at-DEMICON"><a href="#What-is-it-like-to-work-as-a-consultant-at-DEMICON" class="headerlink" title="What is it like to work as a consultant at DEMICON?"></a>What is it like to work as a consultant at DEMICON?</h2><p>While working as an IT consultant at IBM, I was on the road a lot as a consultant. I mainly worked at the customer’s site. Even if business trips are fascinating at the beginning, you lose the desire to stay in hotels over time. That’s different at DEMICON. At DEMICON, we foster a remote-first culture. Most of the time, I work from home. I like that very much, especially because I can create focus time to work undisturbed and concentrated.</p><p>But there are also project phases in which face-to-face collaboration with the customer or my colleagues is essential. In these cases, we meet on-site. For example, in one of the DEMICON offices or at the customer’s premises. Such short trips are a welcome variation in my everyday life.</p><p>Furthermore, I enjoy company-wide gatherings where all DEMICONIANs come together. We had a great summer party in Berlin recently.</p><h2 id="Why-Infrastructure-Pipeline"><a href="#Why-Infrastructure-Pipeline" class="headerlink" title="Why Infrastructure Pipeline?"></a>Why Infrastructure Pipeline?</h2><p>Rolling out infrastructure changes by running <code>terraform apply</code> from your local machine works fine, but only when you are the only one working on a project. When working together in a team, ensuring everyone uses the same runtime environment to execute the Terraform configuration is tough. For example, the whole team needs to use the same Terraform version. An Infrastructure Pipeline ensures that all changes are rolled out in the same way and solves this and many other problems.</p><p>It is also important to mention that when using an infrastructure pipeline, it is no longer necessary to grant engineers administrator access to AWS accounts. Instead, only the pipeline requires administrator access, and engineers get by with read-only permissions.</p><h2 id="An-Infrastructure-Pipeline"><a href="#An-Infrastructure-Pipeline" class="headerlink" title="An Infrastructure Pipeline"></a>An Infrastructure Pipeline</h2><p>As is often the case, there are several ways to solve a problem. One of my preferred approaches for an infrastructure pipeline consists of the following components.</p><ul><li><strong>Terraform</strong> a declarative approach to define cloud infrastructure as code.</li><li><strong>GitLab</strong> to collaborate and version the Terraform configuration.</li><li><strong>GitLab Webhook</strong> triggers Terraform Cloud whenever someone pushes a change of the infrastructure code.</li><li><strong>Terraform Cloud</strong> executes the Terraform configuration and provisions cloud resources.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/11/infrastructure-pipeline@730w.webp 730w, /images/2022/11/infrastructure-pipeline@730w2x.webp 1460w, /images/2022/11/infrastructure-pipeline@610w.webp 610w, /images/2022/11/infrastructure-pipeline@610w2x.webp 1220w, /images/2022/11/infrastructure-pipeline@450w.webp 450w, /images/2022/11/infrastructure-pipeline@450w2x.webp 900w, /images/2022/11/infrastructure-pipeline@330w.webp 330w, /images/2022/11/infrastructure-pipeline@330w2x.webp 660w, /images/2022/11/infrastructure-pipeline@545w.webp 545w, /images/2022/11/infrastructure-pipeline@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/11/infrastructure-pipeline@730w.png 730w, /images/2022/11/infrastructure-pipeline@730w2x.png 1460w, /images/2022/11/infrastructure-pipeline@610w.png 610w, /images/2022/11/infrastructure-pipeline@610w2x.png 1220w, /images/2022/11/infrastructure-pipeline@450w.png 450w, /images/2022/11/infrastructure-pipeline@450w2x.png 900w, /images/2022/11/infrastructure-pipeline@330w.png 330w, /images/2022/11/infrastructure-pipeline@330w2x.png 660w, /images/2022/11/infrastructure-pipeline@545w.png 545w, /images/2022/11/infrastructure-pipeline@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/11/infrastructure-pipeline.png" alt="Infrastructure Pipeline with GitLab and Terraform Cloud" title="Infrastructure Pipeline with GitLab and Terraform Cloud"></picture></p><h2 id="Why-Terraform-Cloud"><a href="#Why-Terraform-Cloud" class="headerlink" title="Why Terraform Cloud?"></a>Why Terraform Cloud?</h2><p>Terraform is a popular open-source tool to automate infrastructure on any cloud. On top of that, HashiCorp provides Terraform Cloud, a platform to automate the provisioning of cloud resources.</p><p>In my opinion, Terraform Cloud is an excellent service for the following reasons:</p><ul><li>Terraform Cloud manages the <strong>Terraform state</strong> simply and securely.</li><li>Terraform Cloud <strong>controls access to different environments</strong>. For example, who is allowed to deploy to production?</li><li>Terraform Cloud comes with a <strong>nice, clean UI</strong>, which for example, is helpful to inspect the results of <code>terraform plan</code> before executing the changes.</li><li>Terraform Cloud <strong>ensures security best practices</strong> before deploying to one of your AWS accounts.</li></ul><h2 id="How-do-you-deploy-multiple-environments-test-prod"><a href="#How-do-you-deploy-multiple-environments-test-prod" class="headerlink" title="How do you deploy multiple environments (test&#x2F;prod)?"></a>How do you deploy multiple environments (test&#x2F;prod)?</h2><p>There is no one-size-fits-all solution. But I’m happy to share my favorite approach to deploying to multiple environments while re-using the same code.</p><p>First, separate environment parameters from the resources. To do so, I prefer using Terraform modules. A module is a collection of resources for a specific domain. For example, a module could manage the networking layer, also known as VPC. Typically, I use a GitLab repository for every module. There are also open-source Terraform modules out there. Then, all that is needed to get the infrastructure for a new environment up and running is to initiate and parameterize the modules. To do so, I’m using a <code>live</code> repository, a concept that became popular by <a href="https://terragrunt.gruntwork.io/" target="_blank" rel="noopener">terragrunt</a>.</p><p>Second, I favor creating a branch for each environment, enabling us to efficiently manage the minor differences between the environments. For example, I’m starting a branch named <code>test</code> and a branch called <code>prod</code>.</p><p>Third, as I’m not a big fan of Terraform Cloud’s workspace variables, I recommend using <code>locals</code> to configure the parameters for each environment.</p><p>The following code snippets illustrate this lesser-known Terraform feature.</p><p>Create a Terraform configuration file named <code>environment.tf</code> to configure environment parameters. The following snippet shows how to specify the local value named <code>bucket_name</code>.</p><figure class="highlight mipsasm"><table><tr><td class="code"><pre><span class="line">locals &#123;</span><br><span class="line"> <span class="keyword">bucket_name </span>= <span class="keyword">cloudonaut-demo</span></span><br><span class="line"><span class="keyword"></span>&#125;</span><br></pre></td></tr></table></figure><p>This allows you to reference the environment parameter in your Terraform code using <code>locals.bucket_name</code> as shown in the following code snippet.</p><figure class="highlight mipsasm"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_s3_bucket&quot;</span> <span class="string">&quot;demo&quot;</span> &#123;</span><br><span class="line"> <span class="keyword">bucket </span>= locals.<span class="keyword">bucket_name</span></span><br><span class="line"><span class="keyword"></span>&#125;</span><br></pre></td></tr></table></figure><p>Those three approaches provide a clean and straightforward way to use the same Terraform code to deploy multiple similar environments.</p><h2 id="Which-open-source-Terraform-modules-do-you-recommend"><a href="#Which-open-source-Terraform-modules-do-you-recommend" class="headerlink" title="Which open-source Terraform modules do you recommend?"></a>Which open-source Terraform modules do you recommend?</h2><p>I highly recommend the Terraform modules <a href="https://registry.terraform.io/namespaces/terraform-aws-modules" target="_blank" rel="noopener">terraform-aws-modules</a> maintained by Anton Babenko. Besides that, I’ve also used Terraform modules by <a href="https://registry.terraform.io/namespaces/cloudposse" target="_blank" rel="noopener">cloudposse</a>. Using open-source Terraform modules is great, as you do not have to reinvent the wheel repeatedly.</p><h2 id="How-to-grant-Terraform-Cloud-access-to-an-AWS-account"><a href="#How-to-grant-Terraform-Cloud-access-to-an-AWS-account" class="headerlink" title="How to grant Terraform Cloud access to an AWS account?"></a>How to grant Terraform Cloud access to an AWS account?</h2><p>Of course, no one wants to use static credentials for AWS authentication. Unfortunately, Terraform Cloud still needs to provide a way to use IAM roles out of the box. However, HashiCorp is working on an OpenID Connect integration which is available upon request already.</p><p>Besides that, I’ve been using the following approach in the past. GitLab supports OpenID Connect. Therefore, I used GitLab to fetch temporary AWS credentials. Next, I used the API of Terraform Cloud to pass those temporary credentials. Afterward, when Terraform Cloud is running <code>terraform plan</code> or <code>terraform apply</code>, it uses the temporary AWS credentials.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Hot off the Cloud: October 2022</title>
      <link>https://cloudonaut.io/hot-off-the-cloud-2022-10/</link>
      <description>
        <![CDATA[<p>What happened at AWS in October 2022? This is our summary and analysis of the announcements that interested us.</p>
<p>In November, re:In]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/sqs/">sqs</category>
      <category domain="https://cloudonaut.io/tag/ebs/">ebs</category>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <guid isPermaLink="true">https://cloudonaut.io/hot-off-the-cloud-2022-10/</guid>
      <pubDate>Mon, 31 Oct 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>What happened at AWS in October 2022? This is our summary and analysis of the announcements that interested us.</p><p>In November, re:Invent, AWS’ major conference, will take place in Las Vegas. During re:Invent, AWS will announce many new features and services. Therefore, it was a bit quieter in October. We mainly saw more minor announcements, but they definitely made all of our lives better.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/10/hot-off-the-cloud-month-review@730w.webp 730w, /images/2022/10/hot-off-the-cloud-month-review@730w2x.webp 1460w, /images/2022/10/hot-off-the-cloud-month-review@610w.webp 610w, /images/2022/10/hot-off-the-cloud-month-review@610w2x.webp 1220w, /images/2022/10/hot-off-the-cloud-month-review@450w.webp 450w, /images/2022/10/hot-off-the-cloud-month-review@450w2x.webp 900w, /images/2022/10/hot-off-the-cloud-month-review@330w.webp 330w, /images/2022/10/hot-off-the-cloud-month-review@330w2x.webp 660w, /images/2022/10/hot-off-the-cloud-month-review@545w.webp 545w, /images/2022/10/hot-off-the-cloud-month-review@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/10/hot-off-the-cloud-month-review@730w.jpg 730w, /images/2022/10/hot-off-the-cloud-month-review@730w2x.jpg 1460w, /images/2022/10/hot-off-the-cloud-month-review@610w.jpg 610w, /images/2022/10/hot-off-the-cloud-month-review@610w2x.jpg 1220w, /images/2022/10/hot-off-the-cloud-month-review@450w.jpg 450w, /images/2022/10/hot-off-the-cloud-month-review@450w2x.jpg 900w, /images/2022/10/hot-off-the-cloud-month-review@330w.jpg 330w, /images/2022/10/hot-off-the-cloud-month-review@330w2x.jpg 660w, /images/2022/10/hot-off-the-cloud-month-review@545w.jpg 545w, /images/2022/10/hot-off-the-cloud-month-review@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/10/hot-off-the-cloud-month-review.jpg" alt="Hot off the Cloud: October 2022" title="Hot off the Cloud: October 2022"></picture></p><blockquote><p>Want our summary and analysis of AWS announcements every week instead of once a month? Check out our <a href="/newsletter/">newsletter</a>, <a href="https://podcast.cloudonaut.io/" target="_blank" rel="noopener">podcast</a>, or <a href="https://www.youtube.com/c/cloudonaut" target="_blank" rel="noopener">YouTube channel</a>.</p></blockquote><h2 id="​Amazon-Data-Lifecycle-Manager-now-automates-archival-of-EBS-Snapshots​"><a href="#​Amazon-Data-Lifecycle-Manager-now-automates-archival-of-EBS-Snapshots​" class="headerlink" title="​Amazon Data Lifecycle Manager now automates archival of EBS Snapshots​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/09/amazon-data-lifecycle-manager-automates-archival-ebs-snapshots/" target="_blank" rel="noopener">​Amazon Data Lifecycle Manager now automates archival of EBS Snapshots​</a></h2><p>First of all, what’s EBS Snapshots Archive? An EBS snapshot costs you $0.05&#x2F;GB-month. With the EBS Snapshots Archive’s cold-storage option, you are only paying $0.0125&#x2F;GB-month. Sounds great? Not really, because standard snapshots are incremental backups. So when you snapshot a volume with 100 GB, change 1 GB of data, and create a second snapshot, you are paying for 101 GB consumed storage only. But, the EBS Snapshots Archive does only support full snapshots. So, when archiving the two snapshots from the example, you have to pay 200 GB.</p><p>It doesn’t take a genius to realize that the EBS Snapshots Archive is cheaper if the blocks on the EBS volumes change significantly. For volumes with little change, switching to the cold-storage option may even be more expensive.</p><p>The Amazon Data Lifecycle Manager is a feature of the EC2 service which can backup EC2 instances and volumes. That’s precisely what AWS Backup does as well. Besides that, AWS Backup supports a bunch of other services as well. The Data Lifecycle Manager seems to be the old-fashioned way to back up EBS volumes. Therefore, surprisingly, this feature made it to the light of day.</p><p>In summary, this announcement is not essential for most of us. However, we learned a lot while looking into the details.</p><h2 id="​AWS-announces-updated-Support-Plans-Console-with-new-IAM-controls​"><a href="#​AWS-announces-updated-Support-Plans-Console-with-new-IAM-controls​" class="headerlink" title="​AWS announces updated Support Plans Console with new IAM controls​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/09/aws-updated-support-plans-console-new-iam-controls/" target="_blank" rel="noopener">​AWS announces updated Support Plans Console with new IAM controls​</a></h2><p>Hurray, one less reason to use the AWS root user. It is important to note that AWS introduced a new IAM service prefix called supportplans. Besides that, we are waiting for the possibility of subscribing to a support plan for the whole organization.</p><h2 id="​AWS-Systems-Manager-adds-CloudWatch-Alarms-to-control-tasks​"><a href="#​AWS-Systems-Manager-adds-CloudWatch-Alarms-to-control-tasks​" class="headerlink" title="​AWS Systems Manager adds CloudWatch Alarms to control tasks​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/09/aws-systems-manager-adds-cloudwatch-alarms-control-tasks/" target="_blank" rel="noopener">​AWS Systems Manager adds CloudWatch Alarms to control tasks​</a></h2><p>Use a CloudWatch alarm to monitor the rollout of changes to EC2 instances and stop an automation, a run command, the state manager, and maintenance windows if a CloudWatch Alarm flips from OK to ALARM. We cannot think of how our workloads -primarily run on single EC2 instances- could benefit from this. But this is interesting when rolling out changes to vast fleets of EC2 instances.</p><p>Fun fact, you could even do some chaos engineering with this feature. This makes the AWS Fault Injection Simulator jealous.</p><h2 id="​Amazon-File-Cache-is-now-generally-available​"><a href="#​Amazon-File-Cache-is-now-generally-available​" class="headerlink" title="​Amazon File Cache is now generally available​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/09/amazon-file-cache-generally-available/" target="_blank" rel="noopener">​Amazon File Cache is now generally available​</a></h2><p>This one got our attention right away. File Cache promises a POSIX interface for accessing files on S3 or NFS. We needed to access S3 buckets from EC2 instances when migrating legacy applications. However, most solutions out there come with significant drawbacks. File Cache is an interesting approach, as it provides a central layer for S3, allowing you to lock files and invalidate the cache.</p><p>Amazon File Cache is part of the FSx service. But it’s not a file system like FSx for Windows File Server. File Cache is based on Lustr and requires installing a client on the machines that need to access the cache.</p><p>At first glance, the pricing model looks exciting: $1.330 per GB-month. However, when we tried to create our first cache, we noticed that the minimum storage required for the cache is 1.2 TB. So Amazon File Cache starts at about $1600 per month. Not what we expected.</p><h2 id="​Amazon-SQS-announces-Server-Side-Encryption-with-Amazon-SQS-managed-encryption-keys-SSE-SQS-by-default​"><a href="#​Amazon-SQS-announces-Server-Side-Encryption-with-Amazon-SQS-managed-encryption-keys-SSE-SQS-by-default​" class="headerlink" title="​Amazon SQS announces Server-Side Encryption with Amazon SQS-managed encryption keys (SSE-SQS) by default​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/10/amazon-sqs-announces-server-side-encryption-ssq-managed-sse-sqs-default/" target="_blank" rel="noopener">​Amazon SQS announces Server-Side Encryption with Amazon SQS-managed encryption keys (SSE-SQS) by default​</a></h2><p>Werner Volges once said “Dance like no one is watching, encrypt like everyone is.” and we are glad to observe, that AWS progresses on its path to encrypt everything by default.</p><h2 id="​Amazon-Virtual-Private-Cloud-VPC-now-supports-two-new-CloudWatch-metrics-to-measure-and-track-network-address-usage"><a href="#​Amazon-Virtual-Private-Cloud-VPC-now-supports-two-new-CloudWatch-metrics-to-measure-and-track-network-address-usage" class="headerlink" title="​Amazon Virtual Private Cloud (VPC) now supports two new CloudWatch metrics to measure and track network address usage"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/10/amazon-virtual-private-cloud-vpc-now-supports-new-cloudwatch-metrics-measure-track-network-address-usage/" target="_blank" rel="noopener">​Amazon Virtual Private Cloud (VPC) now supports two new CloudWatch metrics to measure and track network address usage</a></h2><p>Large organisations, especially those that peer their VPCs with their lokal networks, are limited by the available IP address space. Now, AWS provides two additional CloudWatch metrics to monitor the network address usage (NAU):</p><ul><li><code>NetworkAddressUsage</code></li><li><code>NetworkAddressUsagePeered</code></li></ul><p>Please note, that those metrics are not enabled by default.The following AWS CLI command does the trick.</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">aws ec2 modify-vpc-attribute --vpc-id vpc-xyz --enable-network-address-usage-metrics</span><br></pre></td></tr></table></figure><p>AWS measures NAU units.</p><ul><li>1 NAU per IPv4&#x2F;IPv6 address assigned to a network interface.</li><li>6 NAUs per Lambda function with VPC integration.</li><li>6 NAUs per NAT Gateway and VPC endpoint.</li></ul><p>While diving into the details about the new metrics, we learned about the following VPC quotas, that we never heared about before.</p><p>Each VPC can have up to 64,000 NAU units by default and up to 256,000 by requesting a quota increase.<br>If a VPC is peered with other VPCs, the VPCs combined can have up to 128,000 NAU units by default. You can request a quota increase up to 512,000.<br>VPCs that are peered across different regions or with Transit Gateway do not contribute to this limit.<br>The most intersting fact is, that the NAU quotas do not correlate directly with the number of availble IP addresses.</p><h2 id="​Amazon-Aurora-Serverless-v2-now-supports-AWS-CloudFormation​"><a href="#​Amazon-Aurora-Serverless-v2-now-supports-AWS-CloudFormation​" class="headerlink" title="​Amazon Aurora Serverless v2 now supports AWS CloudFormation​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/10/amazon-aurora-serverless-v2-supports-aws-cloudformation/" target="_blank" rel="noopener">​Amazon Aurora Serverless v2 now supports AWS CloudFormation​</a></h2><p><a href="/review-aurora-serverless-v2/">​We reviewed Aurora Serverless v2 about 6 months ago.</a> One of the issues, we identified was the missing CloudFormation support. This is now a thing of the past!</p><p>However, there are still two main issues why we do not recommend Aurora Serverless v2 for most scenarios.</p><p>First, Aurora Serverless v2 is quite expensive. Our statement from the review is still up to date:</p><blockquote><p>[…] using Aurora Serverless v2 makes sense for workloads that are idling for more than 77% of the time compared to on-demand instances. Or even worse, only for workloads idling more than 96% of the time compared to reserved instances with a three-year term and all-upfront payment.<br>Second, there is still no Data API available, which is a must-have for connecting Lambda with Aurora Serverless v2, in our opinion.</p></blockquote><h2 id="​IAM-Access-Analyzer-now-reviews-your-AWS-CloudTrail-history-to-identify-actions-used-across-140-AWS-services-and-generates-fine-grained-policies​"><a href="#​IAM-Access-Analyzer-now-reviews-your-AWS-CloudTrail-history-to-identify-actions-used-across-140-AWS-services-and-generates-fine-grained-policies​" class="headerlink" title="​IAM Access Analyzer now reviews your AWS CloudTrail history to identify actions used across 140 AWS services and generates fine-grained policies​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/10/iam-access-analyzer-cloudtrail-history-identify-actions-140-aws-services-fine-grained-policies/" target="_blank" rel="noopener">​IAM Access Analyzer now reviews your AWS CloudTrail history to identify actions used across 140 AWS services and generates fine-grained policies​</a></h2><p>The part of IAM Access Analyzer that generates IAM policies based on CloudTrail is useless. The data set, that AWS uses to generate the policies is incomplete. Many so called data events are missing. For example, DynamoDB reads, SQS messages, and many more. And don’t get us started about the costs for S3 and Lambda data events.</p><p>The other part of IAM Access Analyzer, that checks IAM policies is a good starting point when reviewing the security of your AWS accounts.</p><h2 id="​AWS-Lambda-Functions-powered-by-AWS-Graviton2-now-available-in-12-additional-regions​"><a href="#​AWS-Lambda-Functions-powered-by-AWS-Graviton2-now-available-in-12-additional-regions​" class="headerlink" title="​AWS Lambda Functions powered by AWS Graviton2 now available in 12 additional regions​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/10/aws-lambda-functions-graviton2-12-regions/" target="_blank" rel="noopener">​AWS Lambda Functions powered by AWS Graviton2 now available in 12 additional regions​</a></h2><p>We are huge fans of the ARM processor archtitecture in general and Graviton2 in particular. That’s why we switched marbot, our AWS monitoring chatbot, to Graviton2. Doing requried us to modify a few YAML&#x2F;SAM files and took about 10 minutes.</p><p>AWS promises a better performance and lower costs when switching to Graviton2.</p><blockquote><p>[…] Graviton2, using an Arm-based processor architecture, are designed to deliver up to 19% better performance at 20% lower cost for a variety of Serverless workloads […]<br>We could not noticy any performance improvements. But we are happy about the fact, that our monthly Lambda bill will drop from $3 to $2.40.</p></blockquote><h2 id="​AWS-IQ-now-supports-partners-and-independent-consultants-in-Australia-Europe-Japan-and-other-regions​"><a href="#​AWS-IQ-now-supports-partners-and-independent-consultants-in-Australia-Europe-Japan-and-other-regions​" class="headerlink" title="​AWS IQ now supports partners and independent consultants in Australia, Europe, Japan, and other regions​"></a>​<a href="https://aws.amazon.com/about-aws/whats-new/2022/10/aws-iq-supports-partners-independent-consultants-australia-europe-japan-other-regions/" target="_blank" rel="noopener">AWS IQ now supports partners and independent consultants in Australia, Europe, Japan, and other regions​</a></h2><p>On the one hand, that’s great news for independent consultans and small consulting firms.</p><p>On the other hand, be warned: this will be a race to the bottom. There is basically nothing to differentiate yourself from the competitors besides AWS certifications and reviews. So basically, you are competing on price with consultants from all over the world.</p><p>If you decide to particpate in the race to the bottom, think about where you will end up when you are winning.</p><h2 id="​​AWS-Glue-Crawlers-support-incremental-Amazon-S3-crawling-on-existing-AWS-Glue-Data-Catalog-tables​"><a href="#​​AWS-Glue-Crawlers-support-incremental-Amazon-S3-crawling-on-existing-AWS-Glue-Data-Catalog-tables​" class="headerlink" title="​​AWS Glue Crawlers support incremental Amazon S3 crawling on existing AWS Glue Data Catalog tables​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/10/aws-glue-crawlers-incremental-amazon-s3-crawling-existing-aws-glue-data-catalog-tables/" target="_blank" rel="noopener">​​AWS Glue Crawlers support incremental Amazon S3 crawling on existing AWS Glue Data Catalog tables​</a></h2><p>Instead of crawling all objects within a bucket, Glue now supports incremental crawling. That’s great because all those ListBucket and GetObject calls can become expensive. Here is how incremental crawling works:</p><ol><li>S3 sends event notifications to SQS</li><li>Glue crawler starts periodically (e.g., once a day)</li><li>Glue crawler fetches event notifications from SQS</li><li>Glue crawler only scans through modified S3 objects</li></ol><h2 id="​AWS-Lambda-now-supports-event-filtering-for-Amazon-MSK-Self-Manged-Kafka-Amazon-MQ-for-Apache-ActiveMQ-and-Amazon-MQ-for-RabbitMQ-as-event-sources​"><a href="#​AWS-Lambda-now-supports-event-filtering-for-Amazon-MSK-Self-Manged-Kafka-Amazon-MQ-for-Apache-ActiveMQ-and-Amazon-MQ-for-RabbitMQ-as-event-sources​" class="headerlink" title="​AWS Lambda now supports event filtering for Amazon MSK, Self-Manged Kafka, Amazon MQ for Apache ActiveMQ, and Amazon MQ for RabbitMQ as event sources​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/10/aws-lambda-event-filtering-amazon-msk-kafka-mq-apache-activemq-amazon-mq-rabbit-mq/" target="_blank" rel="noopener">​AWS Lambda now supports event filtering for Amazon MSK, Self-Manged Kafka, Amazon MQ for Apache ActiveMQ, and Amazon MQ for RabbitMQ as event sources</a>​</h2><p>Lambda now supports filtering events from Kafka, ActiveMQ, and RabbitMQ. As filtering is free of charge, this allows you to reduce your Lambda costs, in case you had to implement filtering yourself before.</p><p>Interesting to know: the syntax for defining filters for Kafka, ActiveMQ, and RabbitMQ is the same as for EventBridge rules.</p><p>Be warned when developing and testing filter rules: it can take up to 15 minutes for changes to filter rules to take effect. So be patient!</p><h2 id="​Amazon-EC2-adds-Service-Quotas-for-Amazon-Machine-Images-AMIs-​"><a href="#​Amazon-EC2-adds-Service-Quotas-for-Amazon-Machine-Images-AMIs-​" class="headerlink" title="​Amazon EC2 adds Service Quotas for Amazon Machine Images (AMIs)​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/10/amazon-ec2-service-quotas-amazon-machine-images-amis/" target="_blank" rel="noopener">​Amazon EC2 adds Service Quotas for Amazon Machine Images (AMIs)​</a></h2><p>From now on, the following quotas apply per account and region:</p><ul><li>Public AMIs: 5</li><li>Number of entities to share an AMI with: 1,000</li><li>Public and private AMIs: 50,000</li></ul><p>All three quotas are adjustable upon request.</p><p>We guess that AWS introduced these quotas to a) spot issues with public AMIs that should be private and b) avoid expensive costs due to large amounts of AMIs.</p><h2 id="​Amazon-Athena-announces-upgraded-query-engine​"><a href="#​Amazon-Athena-announces-upgraded-query-engine​" class="headerlink" title="​Amazon Athena announces upgraded query engine​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/10/amazon-athena-announces-upgraded-query-engine/" target="_blank" rel="noopener">​Amazon Athena announces upgraded query engine​</a></h2><p>The query engine v3 introduces new features and built-in functions. None of those were out of interest for our use cases, but there are engineers out there who have been waiting to exact these features.</p><p>Besides that, AWS promises 20% performance improvement. However, our queries take about 10% longer when running on v3. Therefore, we cannot yet recommend v3 without hesitation.</p><h2 id="​Announcing-a-new-Cost-Explorer-console-experience​"><a href="#​Announcing-a-new-Cost-Explorer-console-experience​" class="headerlink" title="​Announcing a new Cost Explorer console experience​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/10/new-cost-explorer-console-experience/" target="_blank" rel="noopener">​Announcing a new Cost Explorer console experience​</a></h2><p>This announcement sounded great! However, the Cost Explorer did not change significantly. We could not identify any new features or significant modifications. AWS rebuilt the Cost Explorer based on their latest UI kit. A bit disappointing.</p><h2 id="​Amazon-SQS-announces-increased-throughput-quota-for-FIFO-High-Throughput-HT-mode-to-up-to-6-000-Transactions-Per-Second-TPS-​"><a href="#​Amazon-SQS-announces-increased-throughput-quota-for-FIFO-High-Throughput-HT-mode-to-up-to-6-000-Transactions-Per-Second-TPS-​" class="headerlink" title="​Amazon SQS announces increased throughput quota for FIFO High Throughput (HT) mode to up to 6,000 Transactions Per Second (TPS)​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/10/amazon-sqs-increased-throughput-quota-fifo-high-throughput-ht-mode-6000-transactions-per-second-tps/" target="_blank" rel="noopener">​Amazon SQS announces increased throughput quota for FIFO High Throughput (HT) mode to up to 6,000 Transactions Per Second (TPS)​</a></h2><p>This announcement made us think about replacing Kinesis Data Streams, which we currently use as the backbone for marbot, our AWS Monitoring chatbot. With Kinesis Data Streams, we benefit from ordered events and a built-in retry mechanism. Kinesis requires provisioning shards, whereas SQS charges per request. Therefore, we could save money by switching from Kinesis to SQS.</p><p>A Kinesis shard supports up to 1,000 transactions per second with the possibility to add additional shards to a stream to scale the throughput. Compared to that, an SQS FIFO queue now supports up to 6,000 transactions per second. Scaling beyond that requires creating an additional queue. To support that, the sender needs some logic to distribute events between two or multiple queues. We use a Lambda function to process the messages, so implementing the receiver side should be simple: adding another event source mapping pointing to the same function.</p><p>What are your thoughts on Kinesis Data Streams vs. SQS FIFO HT?</p><p>​</p><h2 id="​Announcing-AWS-Parameters-and-Secrets-Lambda-Extension​"><a href="#​Announcing-AWS-Parameters-and-Secrets-Lambda-Extension​" class="headerlink" title="​Announcing AWS Parameters and Secrets Lambda Extension​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/10/aws-parameters-secrets-lambda-extension/" target="_blank" rel="noopener">​Announcing AWS Parameters and Secrets Lambda Extension​</a></h2><p>Until now, it was common that values from the parameter store or secrets manager were passed to a Lambda function via environment variables. However, by doing so, the values were handed over unencrypted.</p><p>The new extension Parameters and Secrets remedies this by introducing a local endpoint to retrieve values on-the-fly. For example, the following HTTP request returns a value from the Parameter Store:</p><figure class="highlight awk"><table><tr><td class="code"><pre><span class="line">​http:<span class="regexp">//</span>localhost:<span class="number">2773</span><span class="regexp">/systemsmanager/</span>parameters<span class="regexp">/get/</span>?name=MyParameter&amp;version=<span class="number">5</span>​</span><br></pre></td></tr></table></figure><p>The parameters and secrets get cached for 5 minutes.</p><p>What we don’t like about the solution is that it is not a feature of Lambda but a Lambda layer that you deploy along with your function. You are running code provided by AWS in the form of a Lambda layer. In our experience, that also means that we, as a customer, are in charge when things go wrong, as this is not part of the managed service.</p><p>​<br>​##  <a href="https://aws.amazon.com/about-aws/whats-new/2022/10/amazon-interactive-video-service-web-mobile-sdks-ivs-stream-chat/" target="_blank" rel="noopener">Amazon Interactive Video Service now includes web and mobile SDKs for IVS stream chat​</a></p><p>The Amazon Interactive Video Service (IVS) allows you to build a streaming experience as you know from YouTube or Twitch. When AWS announced the service two years ago, we played a little with video streaming. Back then, the issue we identified was that it was pretty tricky to embed the video player and chat into a web application. A lot of custom JavaScript code was required to get the video and chat up and running.</p><p>Since then, AWS has released SDKs simplifying the process of integrating IVS into a web or mobile app. Now, there is even an SDK to embed a stream chat.</p><p>We will definitely play around with that and might host our live streams on AWS instead of YouTube in the future.</p><h2 id="​IAM-Identity-Center-adds-session-management-features-for-improved-user-experience-and-cloud-security​"><a href="#​IAM-Identity-Center-adds-session-management-features-for-improved-user-experience-and-cloud-security​" class="headerlink" title="​IAM Identity Center adds session management features for improved user experience and cloud security​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/10/iam-identity-center-session-management-features-improved-user-experience-cloud-security/" target="_blank" rel="noopener">​IAM Identity Center adds session management features for improved user experience and cloud security​</a></h2><p>This announcement got our attention because we’d love to configure the session timeout, especially for temporary credentials fetched by aws sso login.</p><p>However, it seems like extending the session timeout does not have any effect on the temporary AWS credentials.</p><p>Also, we tested deleting the session of a user authenticated via Google. After deleting the session, the user could still access the portal and the management console. Therefore, we created a bug report and asked AWS for clarification.</p><h2 id="​Announcing-dark-mode-support-in-the-AWS-Management-Console​"><a href="#​Announcing-dark-mode-support-in-the-AWS-Management-Console​" class="headerlink" title="​Announcing dark mode support in the AWS Management Console​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/10/dark-mode-support-aws-management-console/" target="_blank" rel="noopener">​Announcing dark mode support in the AWS Management Console​</a></h2><p>In theory, adding a dark mode for your web application should not be a big deal. However, AWS celebrates this announcement with exaggerated enthusiasm. Maybe releasing dark mode was much harder than it should be.</p><p>Anyway, we can’t recommend dark mode. Unless you like being blinded by a white screen now and then.</p><h2 id="​AWS-Nitro-Enclaves-is-now-supported-on-AWS-Graviton​"><a href="#​AWS-Nitro-Enclaves-is-now-supported-on-AWS-Graviton​" class="headerlink" title="​AWS Nitro Enclaves is now supported on AWS Graviton​"></a><a href="https://aws.amazon.com/about-aws/whats-new/2022/10/aws-nitro-enclaves-now-supported-aws-graviton/" target="_blank" rel="noopener">​AWS Nitro Enclaves is now supported on AWS Graviton​</a></h2><p>We have been reading through the documentation of AWS Nitro Enclaves. To be honest, it is not that easy to get your head around the secure enclaves by AWS. In summary, Nitro Enclaves provide a secure virtual machine coupled with your EC2 instance that you can use to process sensitive data.</p><p>Now, almost all modern instance types support Nitro Enclaves. In our opinion, the most important use case is support for Amazon Certificate Manager (ACM) certificates for EC2 instances. AWS provides a service that you can run to update the certificates for Apache&#x2F;NGINX. To access ACM, the EC2 instance uses a Nitro Enclave.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Migrating CodePipeline to GitHub Actions to improve performance</title>
      <link>https://cloudonaut.io/migrating-codepipeline-to-github-actions-to-improve-performance/</link>
      <description>
        <![CDATA[<p>Recently, we have become increasingly dissatisfied with the time our AWS CodePipeline pipeline takes to deploy a change to production. Th]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/codepipeline/">codepipeline</category>
      <category domain="https://cloudonaut.io/tag/codebuild/">codebuild</category>
      <category domain="https://cloudonaut.io/tag/github/">github</category>
      <guid isPermaLink="true">https://cloudonaut.io/migrating-codepipeline-to-github-actions-to-improve-performance/</guid>
      <pubDate>Fri, 28 Oct 2022 17:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Recently, we have become increasingly dissatisfied with the time our AWS CodePipeline pipeline takes to deploy a change to production. The pipeline builds the code, runs unit tests, deploys to a test environment, runs acceptance tests in the test environment, and deploys to production. It takes 27 minutes for a full run of our pipeline—too long for impatient developers like me. We analyzed the performance in detail and decided to migrate to GitHub Actions. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/10/impatient@730w.webp 730w, /images/2022/10/impatient@730w2x.webp 1460w, /images/2022/10/impatient@610w.webp 610w, /images/2022/10/impatient@610w2x.webp 1220w, /images/2022/10/impatient@450w.webp 450w, /images/2022/10/impatient@450w2x.webp 900w, /images/2022/10/impatient@330w.webp 330w, /images/2022/10/impatient@330w2x.webp 660w, /images/2022/10/impatient@545w.webp 545w, /images/2022/10/impatient@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/10/impatient@730w.jpg 730w, /images/2022/10/impatient@730w2x.jpg 1460w, /images/2022/10/impatient@610w.jpg 610w, /images/2022/10/impatient@610w2x.jpg 1220w, /images/2022/10/impatient@450w.jpg 450w, /images/2022/10/impatient@450w2x.jpg 900w, /images/2022/10/impatient@330w.jpg 330w, /images/2022/10/impatient@330w2x.jpg 660w, /images/2022/10/impatient@545w.jpg 545w, /images/2022/10/impatient@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/10/impatient.jpg" alt="Impatient developer" title="Impatient developer"></picture></p><p>Read on to learn about the pitfalls and reasons for the migration.</p><h2 id="Pipeline"><a href="#Pipeline" class="headerlink" title="Pipeline"></a>Pipeline</h2><p>Before we start, here is an overview of the pipeline (click on the image for a huge CodePipeline screenshot). The pipeline deploys our serverless application: <a href="https://marbot.io/" target="_blank" rel="noopener">ChatOps for AWS - marbot</a>.</p><p><a href="/images/2022/10/marbot-codepipeline-full.png"><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/10/marbot-pipeline@730w.webp 730w, /images/2022/10/marbot-pipeline@730w2x.webp 1460w, /images/2022/10/marbot-pipeline@610w.webp 610w, /images/2022/10/marbot-pipeline@610w2x.webp 1220w, /images/2022/10/marbot-pipeline@450w.webp 450w, /images/2022/10/marbot-pipeline@450w2x.webp 900w, /images/2022/10/marbot-pipeline@330w.webp 330w, /images/2022/10/marbot-pipeline@330w2x.webp 660w, /images/2022/10/marbot-pipeline@545w.webp 545w, /images/2022/10/marbot-pipeline@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/10/marbot-pipeline@730w.png 730w, /images/2022/10/marbot-pipeline@730w2x.png 1460w, /images/2022/10/marbot-pipeline@610w.png 610w, /images/2022/10/marbot-pipeline@610w2x.png 1220w, /images/2022/10/marbot-pipeline@450w.png 450w, /images/2022/10/marbot-pipeline@450w2x.png 900w, /images/2022/10/marbot-pipeline@330w.png 330w, /images/2022/10/marbot-pipeline@330w2x.png 660w, /images/2022/10/marbot-pipeline@545w.png 545w, /images/2022/10/marbot-pipeline@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/10/marbot-pipeline.png" alt="marbot's high-level pipeline " title="marbot's high-level pipeline "></picture></a></p><h2 id="Why-CodePipeline-and-CodeBuild-are-slow"><a href="#Why-CodePipeline-and-CodeBuild-are-slow" class="headerlink" title="Why CodePipeline and CodeBuild are slow"></a>Why CodePipeline and CodeBuild are slow</h2><p>The following table lists the runtime of each stage of our CodePipeline pipeline.</p><table class="table table-striped table-responsive"><thead><tr><th>Stage</th><th style="text-align:right">Runtime (mm:ss)</th></tr></thead><tbody><tr><td>Commit</td><td style="text-align:right">07:01</td></tr><tr><td>Acceptance</td><td style="text-align:right">09:35</td></tr><tr><td>Production</td><td style="text-align:right">09:56</td></tr><tr><td><strong>Total</strong></td><td style="text-align:right">26:32</td></tr></tbody></table><p>We identified the following performance issues:</p><ol><li>CodePipeline adds a tiny but noticeable overhead with each action. E.g., deploying a CloudFormation stack is usually a second slower than calling the CloudFormation API directly. This adds up if you run actions in sequence because of dependencies (e.g., database deployment must happen before app deployment).</li><li>CodePipeline only orchestrates the pipeline. To run a script, you need to integrate CodePipeline with CodeBuild. CodeBuild adds significant overhead. Queuing times of 60 seconds and provisioning times of 10 seconds are not unusual (us-east-1). We use seven CodeBuild actions and see more than 7 minutes of overhead because of that.</li><li>We use <code>aws cloudformation package</code> and the CloudFormation transform <code>AWS::Serverless-2016-10-31</code>, aka SAM, to deploy our application. <code>aws cloudformation package</code> uses a <a href="https://github.com/aws/aws-cli/issues/3131" target="_blank" rel="noopener">simple implementation</a> to detect code changes that trigger a deployment of Lambda functions. We use <code>esbuild</code> to build our source code. Even if the content of the output files stay the same, the changing file creation date triggers a deployment.</li></ol><p>Issues 1 and 2 are easier to fix with migrating to a different technology. Issue 2 could be partly addressed by combining multiple CodeBuild projects into one, making it harder to identify what went wrong quickly (downloading dependencies failed, tests failed, package failed, …) without checking the logs. Issue 3 has nothing to do with CodePipeline. To address issues 1 and 2, we decided to migrate to GitHub Actions. I also found a workaround for issue 3.</p><p>After we decided to deploy our AWS infrastructure and serverless app with GitHub Actions instead of CodePipeline, we learned a few things we want to share with you.</p><h2 id="Artifacts"><a href="#Artifacts" class="headerlink" title="Artifacts"></a>Artifacts</h2><p>In CodePipeline, each action takes input artifacts (exceptions are the source actions at the very beginning of the pipeline) and optionally produces output artifacts. You must ensure (via <code>runOrder</code>) that action output artifacts are created before they are used as input artifacts in other actions.</p><p>GitHub Actions works differently. First, there is a difference between a job and a step. A job typically consists of multiple steps. All steps share the same runner environment and can access files created by previous steps (like in a CodeBuild build). If you want to pass artifacts from one job to another, you must use the GitHub Action <a href="https://github.com/actions/upload-artifact" target="_blank" rel="noopener">upload-artifact</a> and <a href="https://github.com/actions/download-artifact" target="_blank" rel="noopener">download-artifact</a>.</p><h2 id="AWS-Authentication"><a href="#AWS-Authentication" class="headerlink" title="AWS Authentication"></a>AWS Authentication</h2><p>Our pipeline deploys both: our serverless application and the required AWS infrastructure. Therefore, the pipeline requires access to the AWS APIs to configure the API Gateway or Lambda. Consequently, we are using AWS IAM to configure access permissions via policies.</p><p>In CodePipeline, you define the IAM role (via <code>roleArn</code>) that CodePipeline assumes to run your pipeline. Keep in mind that each CodeBuild project requires its own IAM Role.</p><p>In GitHub Actions, you also define the IAM role, but you need a small piece of AWS infrastructure to make it work called an OpenID Connect (OIDC) identity provider, part of the IAM service.</p><ol><li>Check out our <a href="https://github.com/widdix/aws-cf-templates/blob/master/operations/github-openid-connect.yaml" target="_blank" rel="noopener">CloudFormation template to set everything up in a minute</a>.</li><li>Add the following permissions to your workflows at the top level:</li></ol><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">permissions:</span></span><br><span class="line">  <span class="attr">id-token:</span> <span class="string">write</span></span><br><span class="line">  <span class="attr">contents:</span> <span class="string">read</span></span><br></pre></td></tr></table></figure><ol start="3"><li>Use the GitHub Action <a href="https://github.com/aws-actions/configure-aws-credentials" target="_blank" rel="noopener">aws-actions&#x2F;configure-aws-credentials</a> to assume the role in your workflow like this:</li></ol><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">permissions:</span> &#123;&#125; <span class="comment"># shortened</span></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">demo:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-22.04</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">aws-actions/configure-aws-credentials@v4</span></span><br><span class="line">      <span class="attr">with:</span></span><br><span class="line">        <span class="attr">role-to-assume:</span> <span class="string">arn:aws:iam::123456789012:role/github-openid-connect</span></span><br><span class="line">        <span class="attr">role-session-name:</span> <span class="string">github-actions</span></span><br><span class="line">        <span class="attr">aws-region:</span> <span class="string">us-east-1</span></span><br></pre></td></tr></table></figure><h2 id="Running-a-script"><a href="#Running-a-script" class="headerlink" title="Running a script"></a>Running a script</h2><p>I already mentioned that CodePipeline only orchestrates the pipeline. To run a script, you need to integrate CodePipeline with CodeBuild. The following CloudFormation snippet shows the required AWS resources and the mind-blowing complexity:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">ArtifactsBucket:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::S3::Bucket&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span> &#123;&#125; <span class="comment"># shortened</span></span><br><span class="line">  <span class="attr">CodeBuildRole:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">Service:</span> <span class="string">&#x27;codebuild.amazonaws.com&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">      <span class="attr">Policies:</span> [] <span class="comment"># shortened</span></span><br><span class="line">  <span class="attr">UnitTestProject:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodeBuild::Project&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Artifacts:</span></span><br><span class="line">        <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">      <span class="attr">Environment:</span></span><br><span class="line">        <span class="attr">ComputeType:</span> <span class="string">&#x27;BUILD_GENERAL1_SMALL&#x27;</span></span><br><span class="line">        <span class="attr">Image:</span> <span class="string">&#x27;aws/codebuild/standard:6.0&#x27;</span></span><br><span class="line">        <span class="attr">Type:</span> <span class="string">&#x27;LINUX_CONTAINER&#x27;</span></span><br><span class="line">      <span class="attr">Name:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-unit-test&#x27;</span></span><br><span class="line">      <span class="attr">ServiceRole:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CodeBuildRole.Arn&#x27;</span></span><br><span class="line">      <span class="attr">Source:</span></span><br><span class="line">        <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">        <span class="attr">BuildSpec:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">          version: &#x27;0.2&#x27;</span></span><br><span class="line"><span class="string">          phases:</span></span><br><span class="line"><span class="string">            install:</span></span><br><span class="line"><span class="string">              runtime-versions:</span></span><br><span class="line"><span class="string">                nodejs: 16</span></span><br><span class="line"><span class="string">            build:</span></span><br><span class="line"><span class="string">              commands:</span></span><br><span class="line"><span class="string">              - &#x27;npm ci&#x27;</span></span><br><span class="line"><span class="string">              - &#x27;npm run test-with-results&#x27;</span></span><br><span class="line"><span class="string">          artifacts:</span></span><br><span class="line"><span class="string">            files:</span></span><br><span class="line"><span class="string">            - &#x27;test-results.xml&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;coverage/**/*&#x27;</span></span><br><span class="line"><span class="string">            enable-symlinks: yes</span></span><br><span class="line"><span class="string"></span>      <span class="attr">TimeoutInMinutes:</span> <span class="number">5</span></span><br><span class="line">  <span class="attr">PipelineRole:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">Service:</span> <span class="string">&#x27;codepipeline.amazonaws.com&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">      <span class="attr">ManagedPolicyArns:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;arn:aws:iam::aws:policy/AdministratorAccess&#x27;</span></span><br><span class="line">  <span class="attr">Pipeline:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodePipeline::Pipeline&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">ArtifactStore:</span></span><br><span class="line">        <span class="attr">Type:</span> <span class="string">S3</span></span><br><span class="line">        <span class="attr">Location:</span> <span class="type">!Ref</span> <span class="string">ArtifactsBucket</span></span><br><span class="line">      <span class="attr">Name:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line">      <span class="attr">RestartExecutionOnUpdate:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;PipelineRole.Arn&#x27;</span></span><br><span class="line">      <span class="attr">Stages:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="attr">Actions:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">FetchSource</span></span><br><span class="line">          <span class="comment"># shortened</span></span><br><span class="line">          <span class="attr">OutputArtifacts:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">          <span class="attr">RunOrder:</span> <span class="number">1</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">&#x27;Commit-Test&#x27;</span></span><br><span class="line">        <span class="attr">Actions:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Test</span></span><br><span class="line">          <span class="attr">ActionTypeId:</span></span><br><span class="line">            <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">            <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">            <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">            <span class="attr">Version:</span> <span class="string">&#x27;1&#x27;</span></span><br><span class="line">          <span class="attr">Configuration:</span></span><br><span class="line">            <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">UnitTestProject</span></span><br><span class="line">          <span class="attr">InputArtifacts:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">          <span class="attr">OutputArtifacts:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">UnitTestReports</span></span><br><span class="line">          <span class="attr">RunOrder:</span> <span class="number">1</span></span><br><span class="line">  <span class="attr">PipelineTriggerRole:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span> &#123;&#125; <span class="comment"># shortened</span></span><br><span class="line">  <span class="attr">PipelineTriggerRule:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Events::Rule&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">EventPattern:</span></span><br><span class="line">        <span class="attr">source:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&#x27;aws.codecommit&#x27;</span></span><br><span class="line">        <span class="attr">&#x27;detail-type&#x27;:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&#x27;CodeCommit Repository State Change&#x27;</span></span><br><span class="line">        <span class="attr">resources:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:codecommit:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:marbot&#x27;</span></span><br><span class="line">        <span class="attr">detail:</span></span><br><span class="line">          <span class="attr">referenceType:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">branch</span></span><br><span class="line">          <span class="attr">referenceName:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">master</span></span><br><span class="line">      <span class="attr">State:</span> <span class="string">ENABLED</span></span><br><span class="line">      <span class="attr">Targets:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Arn:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:codepipeline:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:$&#123;Pipeline&#125;&#x27;</span></span><br><span class="line">        <span class="attr">Id:</span> <span class="string">pipeline</span></span><br><span class="line">        <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;PipelineTriggerRole.Arn&#x27;</span></span><br></pre></td></tr></table></figure><p>GitHub Actions has native support for running scripts. The following workflow installs Node.js dependencies via <code>npm</code> and runs unit tests on each push to master. The test reports are archived as an artifact and can be inspected manually:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">unit-test</span></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">master</span></span><br><span class="line"><span class="attr">defaults:</span></span><br><span class="line">  <span class="attr">run:</span></span><br><span class="line">    <span class="attr">shell:</span> <span class="string">bash</span></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">unit-test:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-22.04</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/setup-node@v4</span></span><br><span class="line">      <span class="attr">with:</span></span><br><span class="line">        <span class="attr">node-version:</span> <span class="number">16</span></span><br><span class="line">        <span class="attr">cache:</span> <span class="string">npm</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">unit-test</span></span><br><span class="line">      <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">        npm ci</span></span><br><span class="line"><span class="string">        npm run test-with-results</span></span><br><span class="line"><span class="string"></span>    <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/upload-artifact@v4</span></span><br><span class="line">      <span class="attr">with:</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">unit-test-reports</span></span><br><span class="line">        <span class="attr">path:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          test-results.xml</span></span><br><span class="line"><span class="string">          coverage/**/*</span></span><br></pre></td></tr></table></figure><h3 id="The-default-shell-for-CodeBuild-and-GitHub-Actions"><a href="#The-default-shell-for-CodeBuild-and-GitHub-Actions" class="headerlink" title="The default shell for CodeBuild and GitHub Actions"></a>The default shell for CodeBuild and GitHub Actions</h3><p>CodeBuild defaults to <code>sh</code> and stops when a “command” from the commands list fails.</p><p>GitHub Actions defaults to <code>bash</code> with the following settings:</p><ul><li><code>-e</code>: Exit immediately if a command exits with a non-zero status.</li></ul><p>I usually set the default shell to <strong>bash</strong> in my workflows at the top level:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">defaults:</span></span><br><span class="line">  <span class="attr">run:</span></span><br><span class="line">    <span class="attr">shell:</span> <span class="string">bash</span></span><br></pre></td></tr></table></figure><p>GitHub Actions now runs <code>bash</code> with the following settings:</p><ul><li><code>--noprofile: Do not read either the system-wide startup file </code>&#x2F;etc&#x2F;profile<code>or any of the personal initialization files</code><del>&#x2F;.bash_profile<code>, </code></del>&#x2F;.bash_login<code>, or </code>~&#x2F;.profile&#96;.</li><li><code>--norc</code>: Do not read and execute the personal initialization file ~&#x2F;.bashrc if the shell is interactive.</li><li><code>-e</code>: Exit immediately if a command exits with a non-zero status.</li><li><code>-o pipefail</code>: The return value of a pipeline is the status of the last command to exit with a non-zero status, or zero if no command exited with a non-zero status</li></ul><h2 id="AWS-Integrations"><a href="#AWS-Integrations" class="headerlink" title="AWS Integrations"></a>AWS Integrations</h2><p>CodePiepline integrates with a bunch of AWS services natively:</p><ul><li>Amazon ECR</li><li>Amazon ECS</li><li>Amazon S3</li><li>AWS CloudFormation</li><li>AWS CodeBuild</li><li>AWS CodeCommit</li><li>AWS CodeDeploy</li><li>AWS Device Farm</li><li>AWS Elastic Beanstalk</li><li>AWS Lambda</li><li>AWS OpsWorks Stacks</li><li>AWS Service</li></ul><p>AWS offers the following actions for GitHub Actions (some of them are not maintained):</p><ul><li><a href="https://github.com/aws-actions/configure-aws-credentials" target="_blank" rel="noopener">AWS IAM</a></li><li><a href="https://github.com/aws-actions/aws-cloudformation-github-deploy" target="_blank" rel="noopener">AWS CloudFormation</a> (not maintained anymore)</li><li><a href="https://github.com/aws-actions/amazon-eks-fargate" target="_blank" rel="noopener">Amazon EKS</a></li><li><a href="https://github.com/aws-actions/action-cloudwatch-metrics" target="_blank" rel="noopener">Amazon CloudWatch</a> (not maintained anymore)</li><li><a href="https://github.com/aws-actions/codeguru-reviewer" target="_blank" rel="noopener">Amazon CodeGuru</a></li><li><a href="https://github.com/aws-actions/aws-codebuild-run-build" target="_blank" rel="noopener">AWS CodeBuild</a></li><li><a href="https://github.com/aws-actions/aws-secretsmanager-get-secrets" target="_blank" rel="noopener">AWS Secrets Manager</a></li><li><a href="https://github.com/aws-actions/amazon-ecs-deploy-task-definition" target="_blank" rel="noopener">Amazon ECS</a></li><li><a href="https://github.com/aws-actions/amazon-ecr-login" target="_blank" rel="noopener">Amazon ECR</a></li></ul><p>On top of that, you can find many 3rd party actions in the <a href="https://github.com/marketplace?type=actions" target="_blank" rel="noopener">GitHub Marketplace</a>.</p><p>For example, we <a href="https://github.com/widdix/aws-cloudformation-github-deploy" target="_blank" rel="noopener">forked the official AWS CloudFormation action and added parallel stack deployments</a>.</p><h2 id="Parallelization"><a href="#Parallelization" class="headerlink" title="Parallelization"></a>Parallelization</h2><p>We learned that executing steps in parallel is not so easy in GitHub Actions. CodePipline provides much better support for that! That’s why we added parallel stack deployments to our <a href="https://github.com/widdix/aws-cloudformation-github-deploy" target="_blank" rel="noopener">fork of the official AWS CloudFormation action</a>.</p><h2 id="Reusability"><a href="#Reusability" class="headerlink" title="Reusability"></a>Reusability</h2><p>In CodePipeline, there is no way to reuse a stage. My <code>Acceptance</code> and <code>Production</code> stages are very similar. If I make a change to one of them, I have to remember to make the change to the other stage as well. There is no way to reuse a stage or a pipeline.</p><p>GitHub Actions provides a way to <a href="https://docs.github.com/en/actions/using-workflows/reusing-workflows" target="_blank" rel="noopener">reuse workflows</a>.</p><p>I have created a deploy workflow like this with an input to indicate if dev or prod should be deployed:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">deploy</span></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">workflow_call:</span></span><br><span class="line">    <span class="attr">inputs:</span></span><br><span class="line">      <span class="attr">stage:</span></span><br><span class="line">        <span class="attr">required:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">type:</span> <span class="string">string</span></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">deploy:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-22.04</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line">    <span class="comment"># shortened</span></span><br><span class="line">   <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">cloudformation-dashboard</span></span><br><span class="line">      <span class="attr">uses:</span> <span class="string">widdix/aws-cloudformation-github-deploy@v3</span></span><br><span class="line">      <span class="attr">with:</span></span><br><span class="line">        <span class="attr">name:</span> <span class="string">marbot-$&#123;&#123;</span> <span class="string">inputs.stage</span> <span class="string">&#125;&#125;-dashboard</span></span><br><span class="line">        <span class="attr">template:</span> <span class="string">template-dashboard.yml</span></span><br><span class="line">        <span class="attr">parameter-overrides:</span> <span class="string">ParentApiStack=marbot-$&#123;&#123;</span> <span class="string">inputs.stage</span> <span class="string">&#125;&#125;-api,Stage=$&#123;&#123;</span> <span class="string">inputs.stage</span> <span class="string">&#125;&#125;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">smoke-test</span></span><br><span class="line">      <span class="attr">if:</span> <span class="string">inputs.stage</span> <span class="string">==</span> <span class="string">&#x27;dev&#x27;</span></span><br><span class="line">      <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line">        <span class="string">npm</span> <span class="string">run</span> <span class="string">newman</span></span><br></pre></td></tr></table></figure><h2 id="Manual-approval"><a href="#Manual-approval" class="headerlink" title="Manual approval"></a>Manual approval</h2><p>CodePipeline supports manual approvals.</p><p>Unfortunately, GitHub Actions supports <a href="https://docs.github.com/en/actions/managing-workflow-runs/reviewing-deployments" target="_blank" rel="noopener">manual approvals</a> only for organizations subscribed to the Enterprise plan.</p><p>Our workaround for marbot is this:</p><ul><li>We deploy to dev when the master branch changes.</li><li>We create a tag v1.y.z to deploy to prod.</li></ul><p>The main limitation of this workaround is that we have to use two workflows. There is no easy way to share artifacts between workflows (workarounds <a href="https://github.com/dawidd6/action-download-artifact" target="_blank" rel="noopener">exist</a>). For now, we run <code>npm ci &amp;&amp; npm run build</code> twice and hope that the outcome is the same.</p><h2 id="Fixing-aws-cloudformation-package"><a href="#Fixing-aws-cloudformation-package" class="headerlink" title="Fixing aws cloudformation package"></a>Fixing aws cloudformation package</h2><p>If you agree with me that only a change in the content of a file&#x2F;files is relevant to decide if a new deployment of a Lambda function is needed, you can add the following line before your <code>aws cloudformation package</code> command (assuming your build output is stored in the <code>build</code> folder):</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">find build/ -<span class="built_in">exec</span> <span class="built_in">touch</span> -m --<span class="built_in">date</span>=<span class="string">&quot;2020-01-01&quot;</span> &#123;&#125; \;</span><br></pre></td></tr></table></figure><p>The above command will set the creation time of all files inside the <code>build</code> folder to 2020-01-01 (the date doesn’t matter as long as it stays constant). Therefore, <code>aws cloudformation package</code> will only trigger a deployment of the Lambda function in case you made changes to your code.</p><h2 id="Outcome"><a href="#Outcome" class="headerlink" title="Outcome"></a>Outcome</h2><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/10/marbot-gha@730w.webp 730w, /images/2022/10/marbot-gha@730w2x.webp 1460w, /images/2022/10/marbot-gha@610w.webp 610w, /images/2022/10/marbot-gha@610w2x.webp 1220w, /images/2022/10/marbot-gha@450w.webp 450w, /images/2022/10/marbot-gha@450w2x.webp 900w, /images/2022/10/marbot-gha@330w.webp 330w, /images/2022/10/marbot-gha@330w2x.webp 660w, /images/2022/10/marbot-gha@545w.webp 545w, /images/2022/10/marbot-gha@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/10/marbot-gha@730w.png 730w, /images/2022/10/marbot-gha@730w2x.png 1460w, /images/2022/10/marbot-gha@610w.png 610w, /images/2022/10/marbot-gha@610w2x.png 1220w, /images/2022/10/marbot-gha@450w.png 450w, /images/2022/10/marbot-gha@450w2x.png 900w, /images/2022/10/marbot-gha@330w.png 330w, /images/2022/10/marbot-gha@330w2x.png 660w, /images/2022/10/marbot-gha@545w.png 545w, /images/2022/10/marbot-gha@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/10/marbot-gha.png" alt="marbot's GitHub Actions pipeline" title="marbot's GitHub Actions pipeline"></picture></p><p>Migrating from CodePipeline to GitHub Actions and optimizing the deployment process reduced the deployment time from 27 minutes to 9 minutes (down 66%). Optimizing the existing CodePipeline pipeline would have yielded 20-50% performance improvements. We are delighted with the outcome of the migration.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Builder's Diary Vol. 2: Serverless ETL with Airflow and Athena</title>
      <link>https://cloudonaut.io/builders-diary-vol2-serverless-etl-with-airflow-athena/</link>
      <description>
        <![CDATA[<p>Get insights into the day-to-day challenges of builders. In this issue, Peter Reitz from our partner tecRacer talks about how to build Se]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/athena/">athena</category>
      <guid isPermaLink="true">https://cloudonaut.io/builders-diary-vol2-serverless-etl-with-airflow-athena/</guid>
      <pubDate>Wed, 19 Oct 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Get insights into the day-to-day challenges of builders. In this issue, Peter Reitz from our partner tecRacer talks about how to build Serverless ETL (extract, transform and load) pipelines with the help of Amazon Managed Workflows for Apache Airflow (MWAA) and Amazon Athena.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/diary@730w.webp 730w, /images/2022/06/diary@730w2x.webp 1460w, /images/2022/06/diary@610w.webp 610w, /images/2022/06/diary@610w2x.webp 1220w, /images/2022/06/diary@450w.webp 450w, /images/2022/06/diary@450w2x.webp 900w, /images/2022/06/diary@330w.webp 330w, /images/2022/06/diary@330w2x.webp 660w, /images/2022/06/diary@545w.webp 545w, /images/2022/06/diary@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/diary@730w.jpg 730w, /images/2022/06/diary@730w2x.jpg 1460w, /images/2022/06/diary@610w.jpg 610w, /images/2022/06/diary@610w2x.jpg 1220w, /images/2022/06/diary@450w.jpg 450w, /images/2022/06/diary@450w2x.jpg 900w, /images/2022/06/diary@330w.jpg 330w, /images/2022/06/diary@330w2x.jpg 660w, /images/2022/06/diary@545w.jpg 545w, /images/2022/06/diary@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/diary.jpg" alt="Builder's Diary " title="Builder's Diary "></picture></p><p>If you prefer a video or podcast instead of reading, here you go.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=u6QY1A1XV2w">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/u6QY1A1XV2w" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/55-serverless-etl-with-athena-and-airflow-builders-diary-vol-2/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="What-sparked-your-interest-in-cloud-computing"><a href="#What-sparked-your-interest-in-cloud-computing" class="headerlink" title="What sparked your interest in cloud computing?"></a>What sparked your interest in cloud computing?</h2><p>Computers have always held a great fascination for me. I taught myself how to program. That’s how I ended up working as a web developer during my economics studies. When I first stumbled upon Amazon Web Services, I was intrigued by the technology and wide variety of services.</p><h2 id="How-did-you-grow-into-the-role-of-a-cloud-consultant"><a href="#How-did-you-grow-into-the-role-of-a-cloud-consultant" class="headerlink" title="How did you grow into the role of a cloud consultant?"></a>How did you grow into the role of a cloud consultant?</h2><p>After completing my economics degree, I was looking for a job. By chance, a job ad drew my attention to a vacancy for a cloud consultant at tecRacer. To be honest, my skills didn’t match the requirements very well. But because I found the topic exciting, I applied anyway. Right from the job interview, I felt right at home at tecRacer in Duisburg. Since I had no experience with AWS, there was a lot to learn within the first months. My first goal was to achieve the <em>AWS Certified Solutions Architect - Associate</em> certification. The entire team supported and motivated me during this intensive learning phase. After that, I joined a small team working on a project for one of our consulting clients. This allowed me to gain practical experience at a very early stage.</p><h2 id="What-does-your-day-to-day-work-as-a-cloud-consultant-at-tecRacer-look-like"><a href="#What-does-your-day-to-day-work-as-a-cloud-consultant-at-tecRacer-look-like" class="headerlink" title="What does your day-to-day work as a cloud consultant at tecRacer look like?"></a>What does your day-to-day work as a cloud consultant at tecRacer look like?</h2><p>As a cloud consultant, I work on projects for our clients. I specialize in machine learning and data analytics. Since tecRacer has a 100% focus on AWS, I invest in my knowledge of related AWS services like S3, Athena, EMR, SageMaker, and more. I work remotely or at our office in Hamburg and am at the customer’s site every now and then. For example, to analyze the requirements for a project in workshops.</p><h2 id="What-project-are-you-currently-working-on"><a href="#What-project-are-you-currently-working-on" class="headerlink" title="What project are you currently working on?"></a>What project are you currently working on?</h2><p>I’m currently working on building an ETL pipeline. Several data providers upload CSV files to an S3 bucket. My client’s challenge is to extract and transform 3 billion data points and store them in a way that allows efficient data analytics. This process can be roughly described as follows.</p><ol><li>Fetch CSV files from S3.</li><li>Parse CSV files.</li><li>Filter, transform, and enrich columns.</li><li>Partition data to enable efficient queries in the future.</li><li>Transform to a file format optimized for data analytics.</li><li>Upload data to S3.</li></ol><p>I’ve been implementing similar data pipelines in the past. My preferred solution consists of the following building blocks:</p><ul><li>S3 storing the input data.</li><li>Apache Airflow to orchestrate the ETL pipeline.</li><li>Athena to extract, transform, and load the data.</li><li>S3 storing the output data.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/09/serverless-etl-airflow-athena@730w.webp 730w, /images/2022/09/serverless-etl-airflow-athena@730w2x.webp 1460w, /images/2022/09/serverless-etl-airflow-athena@610w.webp 610w, /images/2022/09/serverless-etl-airflow-athena@610w2x.webp 1220w, /images/2022/09/serverless-etl-airflow-athena@450w.webp 450w, /images/2022/09/serverless-etl-airflow-athena@450w2x.webp 900w, /images/2022/09/serverless-etl-airflow-athena@330w.webp 330w, /images/2022/09/serverless-etl-airflow-athena@330w2x.webp 660w, /images/2022/09/serverless-etl-airflow-athena@545w.webp 545w, /images/2022/09/serverless-etl-airflow-athena@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/09/serverless-etl-airflow-athena@730w.png 730w, /images/2022/09/serverless-etl-airflow-athena@730w2x.png 1460w, /images/2022/09/serverless-etl-airflow-athena@610w.png 610w, /images/2022/09/serverless-etl-airflow-athena@610w2x.png 1220w, /images/2022/09/serverless-etl-airflow-athena@450w.png 450w, /images/2022/09/serverless-etl-airflow-athena@450w2x.png 900w, /images/2022/09/serverless-etl-airflow-athena@330w.png 330w, /images/2022/09/serverless-etl-airflow-athena@330w2x.png 660w, /images/2022/09/serverless-etl-airflow-athena@545w.png 545w, /images/2022/09/serverless-etl-airflow-athena@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/09/serverless-etl-airflow-athena.png" alt="Architecture of a Serverless ETL pipeline on AWS: Airflow, Athena, and S3" title="Architecture of a Serverless ETL pipeline on AWS: Airflow, Athena, and S3"></picture></p><h2 id="How-do-you-build-an-ETL-pipeline-based-on-Athena"><a href="#How-do-you-build-an-ETL-pipeline-based-on-Athena" class="headerlink" title="How do you build an ETL pipeline based on Athena?"></a>How do you build an ETL pipeline based on Athena?</h2><p>Amazon Athena enables me to query data stored on S3 on-demand using SQL. The remarkable thing about Athena is that the service is serverless, which means we only have to pay for the processed data when running a query. There are no idle costs except the S3 storage costs.</p><p>As mentioned before, in my current project, the challenge is to extract data from CSV files and store the data in a way that is optimized for data analytics. My approach is transforming the CSV files into more efficient formats such as <em>Parquet</em>. The <em>Parquet</em> file format is designed for efficient data analysis and organizes data in rows, not columns, as CSV does. Therefore, Athena skips fetching and processing all other columns when querying only a subset of the available columns. Also, <em>Parquet</em> compresses the data to minimize storage and network consumption.</p><p>I like using Athena for ETL jobs because of its simplicity and pay-per-use pricing mode. The <code>CREATE TABLE AS SELECT</code> (CTAS) statement implements ETL as described in the following:</p><ol><li>Extract: Load data from CSV files stored on S3 (<code>SELECT FROM &quot;awsmp&quot;.&quot;cas_daily_business_usage_by_instance_type&quot;</code>)</li><li>Transform: Filter and enrich columns. (<code>SELECT product_code, SUM(estimated_revenue) AS revenue, concat(year, &#39;-&#39;, month, &#39;-01&#39;) as date</code>)</li><li>Load: Store results in Parquet file format on S3 (<code>CREATE TABLE monthly_recurring_revenue</code>).</li></ol><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE TABLE</span> monthly_recurring_revenue <span class="keyword">WITH</span> (</span><br><span class="line"> format <span class="operator">=</span> <span class="string">&#x27;Parquet&#x27;</span>,</span><br><span class="line"> external_location <span class="operator">=</span> <span class="string">&#x27;s3://demo-datalake/monthly_recurring_revenue/&#x27;</span>,</span><br><span class="line"> partitioned_by <span class="operator">=</span> <span class="keyword">ARRAY</span>[<span class="string">&#x27;date&#x27;</span>]</span><br><span class="line">) <span class="keyword">AS</span> <span class="keyword">SELECT</span> product_code, <span class="built_in">SUM</span>(estimated_revenue) <span class="keyword">AS</span> revenue, concat(<span class="keyword">year</span>, <span class="string">&#x27;-&#x27;</span>, <span class="keyword">month</span>, <span class="string">&#x27;-01&#x27;</span>) <span class="keyword">as</span> <span class="type">date</span> <span class="keyword">FROM</span> (</span><br><span class="line"> <span class="keyword">SELECT</span> <span class="keyword">year</span>, <span class="keyword">month</span>, <span class="keyword">day</span>, product_code, estimated_revenue <span class="keyword">FROM</span> &quot;awsmp&quot;.&quot;cas_daily_business_usage_by_instance_type&quot; <span class="keyword">ORDER</span> <span class="keyword">BY</span> <span class="keyword">year</span>, <span class="keyword">month</span>, <span class="keyword">day</span></span><br><span class="line">) <span class="keyword">GROUP</span> <span class="keyword">BY</span> <span class="keyword">year</span>, <span class="keyword">month</span>, product_code <span class="keyword">ORDER</span> <span class="keyword">BY</span> <span class="keyword">year</span>, <span class="keyword">month</span>, product_code</span><br></pre></td></tr></table></figure><p>Besides converting the data into the <em>Parquet</em> file format, the statement also partitions the data. This means the keys of the objects start with something like <code>date=2022-08-01</code>, which allows Athena to only fetch relevant files from S3 when querying by <code>date</code>.</p><h2 id="Why-did-you-choose-Athena-instead-of-Amazon-EMR-to-build-an-ETL-pipeline"><a href="#Why-did-you-choose-Athena-instead-of-Amazon-EMR-to-build-an-ETL-pipeline" class="headerlink" title="Why did you choose Athena instead of Amazon EMR to build an ETL pipeline?"></a>Why did you choose Athena instead of Amazon EMR to build an ETL pipeline?</h2><p>I’ve been using EMR for some projects in the past. But nowadays, I prefer adding Athena to the mix, wherever feasible. That’s because compared to EMR, Athena is a lightweight solution. Using Athena is less complex than running jobs on EMR. For example, I prefer using SQL to transform data instead of writing Python code. It takes me less time to build an ETL pipeline with Athena compared to EMR. </p><p>Also, accessing Athena is much more convenient as all functionality is available via the AWS Management Console and API. In contrast, it requires a VPN connection to interact efficiently with EMR when developing a pipeline.</p><p>I prefer a serverless solution due to its cost implications. With Athena, our customer only pays for the processed data. There are no idling costs. As an example, I migrated a workload from EMR to Athena, which reduced costs from $3,000 to $100.</p><h2 id="What-is-Apache-Airflow"><a href="#What-is-Apache-Airflow" class="headerlink" title="What is Apache Airflow?"></a>What is Apache Airflow?</h2><p>Apache Airflow is a popular open-source project providing a workflow management platform for data engineering pipelines. As a data engineer, I describe an ETL pipeline in Python as a directed acyclic graph (DAG).</p><p>Here is a straightforward directed acyclic graph (DAG). The workflow consists of two steps:</p><ol><li>Creating an Athena query.</li><li>Awaiting results from the Athena query.</li></ol><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> airflow.models <span class="keyword">import</span> DAG</span><br><span class="line"><span class="keyword">from</span> airflow.providers.amazon.aws.operators.athena <span class="keyword">import</span> AWSAthenaOperator</span><br><span class="line"><span class="keyword">from</span> airflow.providers.amazon.aws.sensors.athena <span class="keyword">import</span> AthenaSensor</span><br><span class="line"></span><br><span class="line"><span class="keyword">with</span> DAG(dag_id=<span class="string">&#x27;demo&#x27;</span>) <span class="keyword">as</span> dag:</span><br><span class="line"></span><br><span class="line"> read_table = AWSAthenaOperator(</span><br><span class="line"> task_id=<span class="string">&#x27;read_table&#x27;</span>,</span><br><span class="line"> query=<span class="string">&#x27;SELECT * FROM &quot;sampledb&quot;.&quot;elb_logs&quot; limit 10;&#x27;</span>,</span><br><span class="line"> output_location=<span class="string">&#x27;s3://aws-athena-query-results-486555357186-eu-west-1/airflow/&#x27;</span>,</span><br><span class="line"> database=<span class="string">&#x27;sampledb&#x27;</span></span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"> await_query = AthenaSensor(</span><br><span class="line"> task_id=<span class="string">&#x27;await_query&#x27;</span>,</span><br><span class="line"> query_execution_id=read_table.output,</span><br><span class="line"> )</span><br></pre></td></tr></table></figure><p>Airflow allows you to run a DAG manually, via an API, or based on a schedule.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/09/airflow-demo@730w.webp 730w, /images/2022/09/airflow-demo@730w2x.webp 1460w, /images/2022/09/airflow-demo@610w.webp 610w, /images/2022/09/airflow-demo@610w2x.webp 1220w, /images/2022/09/airflow-demo@450w.webp 450w, /images/2022/09/airflow-demo@450w2x.webp 900w, /images/2022/09/airflow-demo@330w.webp 330w, /images/2022/09/airflow-demo@330w2x.webp 660w, /images/2022/09/airflow-demo@545w.webp 545w, /images/2022/09/airflow-demo@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/09/airflow-demo@730w.png 730w, /images/2022/09/airflow-demo@730w2x.png 1460w, /images/2022/09/airflow-demo@610w.png 610w, /images/2022/09/airflow-demo@610w2x.png 1220w, /images/2022/09/airflow-demo@450w.png 450w, /images/2022/09/airflow-demo@450w2x.png 900w, /images/2022/09/airflow-demo@330w.png 330w, /images/2022/09/airflow-demo@330w2x.png 660w, /images/2022/09/airflow-demo@545w.png 545w, /images/2022/09/airflow-demo@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/09/airflow-demo.png" alt="Airflow in Action" title="Airflow in Action"></picture></p><p>Airflow consists of multiple components:</p><ul><li>Scheduler</li><li>Worker</li><li>Web Server</li><li>PostgreSQL database</li><li>Redis in-memory database</li></ul><p>Operating such a distributed system is complex. Luckily, AWS provides a managed service called Amazon Managed Workflows for Apache Airflow (MWAA), which we use in my current project.</p><h2 id="What-does-your-development-workflow-for-Airflow-DAGs-look-like"><a href="#What-does-your-development-workflow-for-Airflow-DAGs-look-like" class="headerlink" title="What does your development workflow for Airflow DAGs look like?"></a>What does your development workflow for Airflow DAGs look like?</h2><p>We built a deployment pipeline for the project I’m currently involved in. So you can think of developing the ETL pipeline like any other software delivery process.</p><ol><li>The engineer pushes changes of DAGs to a Git repository.</li><li>The deployment pipeline validates the Python code.</li><li>The deployment pipeline spins up a container based on <a href="https://github.com/aws/aws-mwaa-local-runner" target="_blank" rel="noopener">aws-mwaa-local-runner</a> and verifies whether all dependencies are working as expected.</li><li>The deployment pipeline runs an integration test.</li><li>The deployment pipeline uploads the DAGs to S3.</li><li>Airflow refreshes the DAGs.</li></ol><p>The deployment pipeline significantly speeds up the ETL pipeline’s development process, as many issues are spotted before deploying to AWS.</p><h2 id="Why-do-you-use-Airflow-instead-of-AWS-Step-Functions"><a href="#Why-do-you-use-Airflow-instead-of-AWS-Step-Functions" class="headerlink" title="Why do you use Airflow instead of AWS Step Functions?"></a>Why do you use Airflow instead of AWS Step Functions?</h2><p>In general, Airflow is similar to AWS Step Functions. However, there are two crucial differences.</p><p>First, Airflow is a popular choice for building ETL pipelines. Therefore, many engineers in the field of data analytics have already gained experience with the tool. And besides, the open-source community creates many integrations that help build ETL pipelines.</p><p>Second, unlike Step Functions, Airflow is not only available on AWS. Being able to move ETL pipelines to another cloud vendor or on-premises is a plus.</p><h2 id="Why-do-you-specialize-in-machine-learning-and-data-analytics"><a href="#Why-do-you-specialize-in-machine-learning-and-data-analytics" class="headerlink" title="Why do you specialize in machine learning and data analytics?"></a>Why do you specialize in machine learning and data analytics?</h2><p>I enjoy working with data. Being able to answer questions by analyzing huge amounts of data and enabling better decisions backed by data motivates me. Also, I’m a huge fan of Athena. It’s one of the most powerful services offered by AWS. On top of that, machine learning, in general, and reinforced learning, in particular, fascinates me, as it allows us to recognize correlations that were not visible before.</p><blockquote><p>Would you like to join Peter’s team to implement solutions with the help of machine learning and data analytics? tecRacer is hiring Cloud Consultants focusing on machine learning and data analytics. <a href="https://jobs.tecracer.com/?utm_source=cloudonaut&utm_medium=blog&utm_campaign=cloudonaut_talent_2022_05" target="_blank" rel="noopener">Apply now!</a></p></blockquote>]]>
      </content:encoded>
    </item>
    <item>
      <title>🎧 AWS-to-go: Podcast series to get started with AWS</title>
      <link>https://cloudonaut.io/aws-to-go-podcast/</link>
      <description>Listen to the AWS-to-go podcast series to get started with AWS.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-to-go-podcast/</guid>
      <pubDate>Wed, 21 Sep 2022 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Get started with AWS or broaden your knowledge while walking, biking, running, driving, or whenever you enjoy listening to podcasts. Our podcast series <strong>AWS-to-go</strong> will guide you through the most important aspect of AWS. To do so, we are going through each chapter of our book <a href="https://www.manning.com/books/amazon-web-services-in-action-third-edition?utm_source=mwittig&utm_medium=affiliate&utm_campaign=book_wittig2_amazon_3_25_22&a_aid=mwittig&a_bid=7e8f8a13" target="_blank" rel="noopener">Amazon Web Services in Action 3rd edition</a> together with Steffen, a web and front-end developer who just gets started with all things AWS.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/09/aws-to-go-header@730w.webp 730w, /images/2022/09/aws-to-go-header@730w2x.webp 1460w, /images/2022/09/aws-to-go-header@610w.webp 610w, /images/2022/09/aws-to-go-header@610w2x.webp 1220w, /images/2022/09/aws-to-go-header@450w.webp 450w, /images/2022/09/aws-to-go-header@450w2x.webp 900w, /images/2022/09/aws-to-go-header@330w.webp 330w, /images/2022/09/aws-to-go-header@330w2x.webp 660w, /images/2022/09/aws-to-go-header@545w.webp 545w, /images/2022/09/aws-to-go-header@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/09/aws-to-go-header@730w.jpg 730w, /images/2022/09/aws-to-go-header@730w2x.jpg 1460w, /images/2022/09/aws-to-go-header@610w.jpg 610w, /images/2022/09/aws-to-go-header@610w2x.jpg 1220w, /images/2022/09/aws-to-go-header@450w.jpg 450w, /images/2022/09/aws-to-go-header@450w2x.jpg 900w, /images/2022/09/aws-to-go-header@330w.jpg 330w, /images/2022/09/aws-to-go-header@330w2x.jpg 660w, /images/2022/09/aws-to-go-header@545w.jpg 545w, /images/2022/09/aws-to-go-header@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/09/aws-to-go-header.jpg" alt="AWS-to-go: Podcast series to get started with AWS" title="AWS-to-go: Podcast series to get started with AWS"></picture></p><h2 id="Episodes"><a href="#Episodes" class="headerlink" title="Episodes"></a>Episodes</h2><p>Here are the episodes we’ve published so far. Subscribe to the <a href="https://podcast.cloudonaut.io/" target="_blank" rel="noopener">cloudonaut podcast</a> to make sure you are not missing the upcoming episode.</p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/48-48-aws-to-go-vol-1-what-is-amazon-web-services/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/49-aws-to-go-vol-2-wordpress-in-fifteen-minutes-an-example/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/50-aws-to-go-vol-3-using-virtual-machines-ec2/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>By the way, all episodes are available on YouTube as well. Check out the <a href="https://youtube.com/playlist?list=PLmr0YjdHUwbHX5wXsiksko4vmJyRoQsrI" target="_blank" rel="noopener">AWS-to-go playlist</a>.</p><h2 id="Share"><a href="#Share" class="headerlink" title="Share"></a>Share</h2><p>Please tell your friends and coworkers about the AWS-to-go podcast series!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Show Me Your Architecture Vol. 1: Scanning S3 buckets for malware</title>
      <link>https://cloudonaut.io/scanning-s3-buckets-for-malware-show-me-your-architecture-vol1/</link>
      <description>
        <![CDATA[<p>Through the AWS documentation, books like AWS in Action or AWS trainings you can gain theoretical knowledge. But beyond that, it is very]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/sqs/">sqs</category>
      <category domain="https://cloudonaut.io/tag/show-me-your-architecture/">show-me-your-architecture</category>
      <guid isPermaLink="true">https://cloudonaut.io/scanning-s3-buckets-for-malware-show-me-your-architecture-vol1/</guid>
      <pubDate>Wed, 14 Sep 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Through the AWS documentation, books like AWS in Action or AWS trainings you can gain theoretical knowledge. But beyond that, it is very valuable to learn directly from practice. In this series, we inspect real-life AWS architectures. We start with Andreas, who gives us an overview of the architecture for scanning S3 buckets for malware.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/09/show-me-your-architecture-vol1@730w.webp 730w, /images/2022/09/show-me-your-architecture-vol1@730w2x.webp 1460w, /images/2022/09/show-me-your-architecture-vol1@610w.webp 610w, /images/2022/09/show-me-your-architecture-vol1@610w2x.webp 1220w, /images/2022/09/show-me-your-architecture-vol1@450w.webp 450w, /images/2022/09/show-me-your-architecture-vol1@450w2x.webp 900w, /images/2022/09/show-me-your-architecture-vol1@330w.webp 330w, /images/2022/09/show-me-your-architecture-vol1@330w2x.webp 660w, /images/2022/09/show-me-your-architecture-vol1@545w.webp 545w, /images/2022/09/show-me-your-architecture-vol1@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/09/show-me-your-architecture-vol1@730w.jpg 730w, /images/2022/09/show-me-your-architecture-vol1@730w2x.jpg 1460w, /images/2022/09/show-me-your-architecture-vol1@610w.jpg 610w, /images/2022/09/show-me-your-architecture-vol1@610w2x.jpg 1220w, /images/2022/09/show-me-your-architecture-vol1@450w.jpg 450w, /images/2022/09/show-me-your-architecture-vol1@450w2x.jpg 900w, /images/2022/09/show-me-your-architecture-vol1@330w.jpg 330w, /images/2022/09/show-me-your-architecture-vol1@330w2x.jpg 660w, /images/2022/09/show-me-your-architecture-vol1@545w.jpg 545w, /images/2022/09/show-me-your-architecture-vol1@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/09/show-me-your-architecture-vol1.jpg" alt="Show Me Your Architecture Vol. 1: Scanning S3 buckets for malware" title="Show Me Your Architecture Vol. 1: Scanning S3 buckets for malware"></picture></p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><blockquote><p>Do you like to share your architecture as well? Contact <a href="mailto:&#x61;&#x72;&#x63;&#104;&#x69;&#116;&#101;&#x63;&#116;&#x75;&#x72;&#x65;&#64;&#x63;&#x6c;&#x6f;&#x75;&#100;&#99;&#114;&#97;&#102;&#116;&#x2e;&#x63;&#111;">architecture@cloudcraft.co</a>.</p></blockquote><h2 id="Who-are-you"><a href="#Who-are-you" class="headerlink" title="Who are you?"></a>Who are you?</h2><p>My name is Andreas Wittig. I’m the co-founder of <a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV, an antivirus protection solution for Amazon S3</a>. Besides that, I’m writing for the Cloudcraft and cloudonaut blogs. I’ve been designing and implementing AWS architectures since 2013 for a wide variety of consulting clients.</p><h2 id="Which-problem-do-you-solve"><a href="#Which-problem-do-you-solve" class="headerlink" title="Which problem do you solve?"></a>Which problem do you solve?</h2><p>The challenge is to scan objects on S3 for malware. This includes scanning objects immediately after they have been uploaded to a bucket and scanning all the objects stored in a bucket based on a recurring schedule. The solution needs to be cost-efficient and scalable to work for scenarios with very little or large amounts of data.</p><h2 id="What-does-the-architecture-look-like"><a href="#What-does-the-architecture-look-like" class="headerlink" title="What does the architecture look like?"></a>What does the architecture look like?</h2><p>The architecture consists of the following building blocks:</p><ul><li>The <strong>S3 buckets</strong> are scanned for malware.</li><li>An <strong>SQS queue</strong> to store and distribute the scan tasks to a fleet of workers.</li><li>An <strong>Auto Scaling Group</strong> to increase or decrease the number of workers on-demand.</li><li><strong>EC2 Instances</strong> that are downloading and scanning objects.</li><li>An <strong>SNS topic</strong> to publish and distribute results of scanned objects.</li><li>A <strong>Lambda function</strong> to process the scan results.</li><li>A <strong>Step Functions state machine</strong> to orchestrate scheduled scans, which requires listing all objects.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/09/architecture-bucketav-overview@730w.webp 730w, /images/2022/09/architecture-bucketav-overview@730w2x.webp 1460w, /images/2022/09/architecture-bucketav-overview@610w.webp 610w, /images/2022/09/architecture-bucketav-overview@610w2x.webp 1220w, /images/2022/09/architecture-bucketav-overview@450w.webp 450w, /images/2022/09/architecture-bucketav-overview@450w2x.webp 900w, /images/2022/09/architecture-bucketav-overview@330w.webp 330w, /images/2022/09/architecture-bucketav-overview@330w2x.webp 660w, /images/2022/09/architecture-bucketav-overview@545w.webp 545w, /images/2022/09/architecture-bucketav-overview@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/09/architecture-bucketav-overview@730w.png 730w, /images/2022/09/architecture-bucketav-overview@730w2x.png 1460w, /images/2022/09/architecture-bucketav-overview@610w.png 610w, /images/2022/09/architecture-bucketav-overview@610w2x.png 1220w, /images/2022/09/architecture-bucketav-overview@450w.png 450w, /images/2022/09/architecture-bucketav-overview@450w2x.png 900w, /images/2022/09/architecture-bucketav-overview@330w.png 330w, /images/2022/09/architecture-bucketav-overview@330w2x.png 660w, /images/2022/09/architecture-bucketav-overview@545w.png 545w, /images/2022/09/architecture-bucketav-overview@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/09/architecture-bucketav-overview.png" alt="Scanning S3 bukets for malware with bucketAV" title="Scanning S3 bukets for malware with bucketAV"></picture></p><p>The SQS queue is a central part of the architecture. An S3 event notification is sent to the SQS queue whenever a user uploads an object to an S3 bucket. Also, a Lambda function sends messages to the queue to trigger scheduled scans.</p><p>First, the SQS queue decouples the EC2 instances from the senders – S3 event notifications and Lambda. The number of workers gets scaled based on the length of the queue, which allows to scale down to 0 when no objects need to be scanned. Also, the queue absorbs load peaks.</p><p>Second, the SQS queue ensures fault tolerance. After a worker scans an object, the message gets deleted from the queue. If a worker fails to scan an object, it does not delete the message from the queue. Therefore, SQS will re-deliver the message to retry. But, after five failed attempts, SQS will move the message to the dead-letter queue, triggering an alarm to inform an operator.</p><h2 id="What-other-approaches-did-you-consider"><a href="#What-other-approaches-did-you-consider" class="headerlink" title="What other approaches did you consider?"></a>What other approaches did you consider?</h2><p>We thought about using containers instead of virtual machines. However, from a cost perspective, that did not make much sense. An EC2 spot instance is by far the cheapest option to get access to compute capacity on AWS.</p><p>We also thought about using Lambda to scan objects for malware. We decided not to use Lambda because of the following reasons.</p><p>First, the malware database needs to be updated every time Lambda spins up a new <a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html" target="_blank" rel="noopener">execution environment</a>. Doing so adds a lot of overhead.</p><p>Second, we were skeptical that we would be able to meet the 15-minute execution time limit in all cases, especially for downloading and scanning large files.</p><p>Third, there is no cheaper option to access compute capacity than spot instances.</p><h2 id="What-are-the-limitations-of-the-architecture"><a href="#What-are-the-limitations-of-the-architecture" class="headerlink" title="What are the limitations of the architecture?"></a>What are the limitations of the architecture?</h2><p>By default, an SQS queue does not guarantee delivering messages in order under any circumstances. That’s not a big deal in this scenario, as there is no dependency between the objects.</p><p>Also, SQS does not guarantee to deliver messages exactly once. In rare cases, a message might be delivered more than once. Again, that’s not an issue in this scenario because scanning an object multiple times does not modify the result.</p><p>Another limitation of the architecture is that scaling takes some time. For example, it will take about 10 minutes until the first EC2 instance scans an object after the Auto Scaling Group downsized to zero. That’s because of delays added by CloudWatch, Auto Scaling, the bootstrapping of an EC2 instance, and so on. Other approaches, like using Lambda functions, could reduce the latency.</p><h2 id="How-did-the-architecture-evolve"><a href="#How-did-the-architecture-evolve" class="headerlink" title="How did the architecture evolve?"></a>How did the architecture evolve?</h2><p>The architecture consisting of SQS, Auto Scaling, and EC2 did not change. However, we added additional features primarily by adding Lambda functions over the years.</p><p>For example, to quarantine infected objects by moving them to a designated S3 bucket, we use a Lambda function subscribed to the SNS topic, which receives all the scan results.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/09/architecture-bucketav-quarantine@730w.webp 730w, /images/2022/09/architecture-bucketav-quarantine@730w2x.webp 1460w, /images/2022/09/architecture-bucketav-quarantine@610w.webp 610w, /images/2022/09/architecture-bucketav-quarantine@610w2x.webp 1220w, /images/2022/09/architecture-bucketav-quarantine@450w.webp 450w, /images/2022/09/architecture-bucketav-quarantine@450w2x.webp 900w, /images/2022/09/architecture-bucketav-quarantine@330w.webp 330w, /images/2022/09/architecture-bucketav-quarantine@330w2x.webp 660w, /images/2022/09/architecture-bucketav-quarantine@545w.webp 545w, /images/2022/09/architecture-bucketav-quarantine@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/09/architecture-bucketav-quarantine@730w.png 730w, /images/2022/09/architecture-bucketav-quarantine@730w2x.png 1460w, /images/2022/09/architecture-bucketav-quarantine@610w.png 610w, /images/2022/09/architecture-bucketav-quarantine@610w2x.png 1220w, /images/2022/09/architecture-bucketav-quarantine@450w.png 450w, /images/2022/09/architecture-bucketav-quarantine@450w2x.png 900w, /images/2022/09/architecture-bucketav-quarantine@330w.png 330w, /images/2022/09/architecture-bucketav-quarantine@330w2x.png 660w, /images/2022/09/architecture-bucketav-quarantine@545w.png 545w, /images/2022/09/architecture-bucketav-quarantine@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/09/architecture-bucketav-quarantine.png" alt="Quarantine infected files with SNS and Lambda" title="Quarantine infected files with SNS and Lambda"></picture></p><h2 id="What-surprises-did-you-encounter"><a href="#What-surprises-did-you-encounter" class="headerlink" title="What surprises did you encounter?"></a>What surprises did you encounter?</h2><p>In the beginning, bucketAV scanned objects immediately after upload. But customers were asking about the possibility of scanning all objects in a bucket based on a recurring schedule.</p><p>Turns out that it is not that easy to implement this feature. The challenge is that a process needs to page through the list of objects. A simple task for buckets with 10,000 objects. But it gets tricky when dealing with buckets containing a vast amount of objects.</p><p>For example, you may want to throttle the maximum amount of objects scanned per minute when crawling through the buckets. So you need a way to orchestrate this process.</p><p>We decided to use Step Functions for orchestration. This also allowed us to use a Lambda function to list objects and send messages to the SQS queue. A solution that works very reliably.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/09/architecture-bucketav-stepfunctions@730w.webp 730w, /images/2022/09/architecture-bucketav-stepfunctions@730w2x.webp 1460w, /images/2022/09/architecture-bucketav-stepfunctions@610w.webp 610w, /images/2022/09/architecture-bucketav-stepfunctions@610w2x.webp 1220w, /images/2022/09/architecture-bucketav-stepfunctions@450w.webp 450w, /images/2022/09/architecture-bucketav-stepfunctions@450w2x.webp 900w, /images/2022/09/architecture-bucketav-stepfunctions@330w.webp 330w, /images/2022/09/architecture-bucketav-stepfunctions@330w2x.webp 660w, /images/2022/09/architecture-bucketav-stepfunctions@545w.webp 545w, /images/2022/09/architecture-bucketav-stepfunctions@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/09/architecture-bucketav-stepfunctions@730w.png 730w, /images/2022/09/architecture-bucketav-stepfunctions@730w2x.png 1460w, /images/2022/09/architecture-bucketav-stepfunctions@610w.png 610w, /images/2022/09/architecture-bucketav-stepfunctions@610w2x.png 1220w, /images/2022/09/architecture-bucketav-stepfunctions@450w.png 450w, /images/2022/09/architecture-bucketav-stepfunctions@450w2x.png 900w, /images/2022/09/architecture-bucketav-stepfunctions@330w.png 330w, /images/2022/09/architecture-bucketav-stepfunctions@330w2x.png 660w, /images/2022/09/architecture-bucketav-stepfunctions@545w.png 545w, /images/2022/09/architecture-bucketav-stepfunctions@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/09/architecture-bucketav-stepfunctions.png" alt="Use Step Functions to list all objects of a bucket" title="Use Step Functions to list all objects of a bucket"></picture></p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Scanning S3 buckets for malware lead to a simple but powerful architecture consisting of S3, SQS, Auto Scaling, EC2, SNS, and Lambda. Additional features were added by using Lambda and Step Functions over the years. The architecture has proven to be cost-efficient, scalable, reliable, and is used by hundreds of customers worldwide.</p><p>Thanks for sharing your architecture with us, Andreas!</p><blockquote><p>Did you learn something new by reading this Show Me Your Architecture volume? Then, please share this article with a friend or coworker. Also, please consider sharing your own story. Contact <a href="mailto:&#x61;&#x72;&#99;&#x68;&#x69;&#116;&#x65;&#99;&#x74;&#x75;&#x72;&#101;&#x40;&#99;&#x6c;&#111;&#117;&#x64;&#99;&#x72;&#97;&#x66;&#x74;&#x2e;&#99;&#x6f;">architecture@cloudcraft.co</a>!</p></blockquote>]]>
      </content:encoded>
    </item>
    <item>
      <title>Enhance EFS file system protection with TLS and IAM</title>
      <link>https://cloudonaut.io/enhance-efs-file-system-protectin-with-tls-and-iam/</link>
      <description>
        <![CDATA[<p>Two significant aspects of data security are access restriction as well as confidentiality. In the following, you will learn two techniqu]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <category domain="https://cloudonaut.io/tag/efs/">efs</category>
      <guid isPermaLink="true">https://cloudonaut.io/enhance-efs-file-system-protectin-with-tls-and-iam/</guid>
      <pubDate>Wed, 07 Sep 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Two significant aspects of data security are access restriction as well as confidentiality. In the following, you will learn two techniques to increase the security of data stored on an EFS file system (Amazon Elastic File System): enabling encryption of data in transit (TLS) and adding an authentication and authorization layer with IAM.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/09/enhance-efs-file-system-protection-title@730w.webp 730w, /images/2022/09/enhance-efs-file-system-protection-title@730w2x.webp 1460w, /images/2022/09/enhance-efs-file-system-protection-title@610w.webp 610w, /images/2022/09/enhance-efs-file-system-protection-title@610w2x.webp 1220w, /images/2022/09/enhance-efs-file-system-protection-title@450w.webp 450w, /images/2022/09/enhance-efs-file-system-protection-title@450w2x.webp 900w, /images/2022/09/enhance-efs-file-system-protection-title@330w.webp 330w, /images/2022/09/enhance-efs-file-system-protection-title@330w2x.webp 660w, /images/2022/09/enhance-efs-file-system-protection-title@545w.webp 545w, /images/2022/09/enhance-efs-file-system-protection-title@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/09/enhance-efs-file-system-protection-title@730w.jpg 730w, /images/2022/09/enhance-efs-file-system-protection-title@730w2x.jpg 1460w, /images/2022/09/enhance-efs-file-system-protection-title@610w.jpg 610w, /images/2022/09/enhance-efs-file-system-protection-title@610w2x.jpg 1220w, /images/2022/09/enhance-efs-file-system-protection-title@450w.jpg 450w, /images/2022/09/enhance-efs-file-system-protection-title@450w2x.jpg 900w, /images/2022/09/enhance-efs-file-system-protection-title@330w.jpg 330w, /images/2022/09/enhance-efs-file-system-protection-title@330w2x.jpg 660w, /images/2022/09/enhance-efs-file-system-protection-title@545w.jpg 545w, /images/2022/09/enhance-efs-file-system-protection-title@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/09/enhance-efs-file-system-protection-title.jpg" alt="Enhance EFS file system protection with TLS and IAM" title="Enhance EFS file system protection with TLS and IAM"></picture></p><p>Prefer watching a video instead of reading this blog post? Here you go.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=K2mi2TzQs3o">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/K2mi2TzQs3o" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p>But let’s start with the initial situation.</p><h2 id="Security-Groups-restrict-traffic-from-EC2-to-EFS"><a href="#Security-Groups-restrict-traffic-from-EC2-to-EFS" class="headerlink" title="Security Groups restrict traffic from EC2 to EFS"></a>Security Groups restrict traffic from EC2 to EFS</h2><p>When EFS launched, the only way to restrict access to the network file system was to use security groups to control traffic tightly. As illustrated in the following figure, using a security group <code>sg-ec2</code> for the EC2 instance and another security group <code>sg-efs</code> for the mount targets of the EFS file system allows restricting access to the EFS file system on the network layer.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/09/efs-securitygroup@730w.webp 730w, /images/2022/09/efs-securitygroup@730w2x.webp 1460w, /images/2022/09/efs-securitygroup@610w.webp 610w, /images/2022/09/efs-securitygroup@610w2x.webp 1220w, /images/2022/09/efs-securitygroup@450w.webp 450w, /images/2022/09/efs-securitygroup@450w2x.webp 900w, /images/2022/09/efs-securitygroup@330w.webp 330w, /images/2022/09/efs-securitygroup@330w2x.webp 660w, /images/2022/09/efs-securitygroup@545w.webp 545w, /images/2022/09/efs-securitygroup@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/09/efs-securitygroup@730w.png 730w, /images/2022/09/efs-securitygroup@730w2x.png 1460w, /images/2022/09/efs-securitygroup@610w.png 610w, /images/2022/09/efs-securitygroup@610w2x.png 1220w, /images/2022/09/efs-securitygroup@450w.png 450w, /images/2022/09/efs-securitygroup@450w2x.png 900w, /images/2022/09/efs-securitygroup@330w.png 330w, /images/2022/09/efs-securitygroup@330w2x.png 660w, /images/2022/09/efs-securitygroup@545w.png 545w, /images/2022/09/efs-securitygroup@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/09/efs-securitygroup.png" alt="Controlling access to EFS by using security groups" title="Controlling access to EFS by using security groups"></picture></p><p>The following code snippet shows how to create an EFS file system, a mount target, as well as two security groups with the help of CloudFormation.</p><blockquote><p>All the following code examples are from our book <a href="https://bit.ly/amazon-web-services-in-action-3rd-edition" target="_blank" rel="noopener">Amazon Web Services in Action (3rd edition)</a>.</p></blockquote><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">FileSystem:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::EFS::FileSystem&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">  <span class="attr">Encrypted:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">ThroughputMode:</span> <span class="string">bursting</span></span><br><span class="line">  <span class="attr">PerformanceMode:</span> <span class="string">generalPurpose</span></span><br><span class="line"><span class="attr">EFSClientSecurityGroup:</span> <span class="comment"># Attach to EC2 instance</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::SecurityGroup&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">  <span class="attr">GroupDescription:</span> <span class="string">&#x27;EFS Mount target client&#x27;</span></span><br><span class="line">  <span class="attr">VpcId:</span> <span class="type">!Ref</span> <span class="string">VPC</span></span><br><span class="line"><span class="attr">MountTargetSecurityGroup:</span> <span class="comment"># Attach to EFS mount target</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::SecurityGroup&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">  <span class="attr">GroupDescription:</span> <span class="string">&#x27;EFS Mount target&#x27;</span></span><br><span class="line">  <span class="attr">SecurityGroupIngress:</span> <span class="comment"># Allows traffic from EC2 instance with EFSClientSecurityGroup only</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">IpProtocol:</span> <span class="string">tcp</span></span><br><span class="line">  <span class="attr">FromPort:</span> <span class="number">2049</span></span><br><span class="line">  <span class="attr">ToPort:</span> <span class="number">2049</span></span><br><span class="line">  <span class="attr">SourceSecurityGroupId:</span> <span class="type">!Ref</span> <span class="string">EFSClientSecurityGroup</span></span><br><span class="line">  <span class="attr">VpcId:</span> <span class="type">!Ref</span> <span class="string">VPC</span></span><br><span class="line"><span class="attr">MountTargetA:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::EFS::MountTarget&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">  <span class="attr">FileSystemId:</span> <span class="type">!Ref</span> <span class="string">FileSystem</span></span><br><span class="line">  <span class="attr">SecurityGroups:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">MountTargetSecurityGroup</span> <span class="comment"># Attaches the MountTargetSecurityGroup</span></span><br><span class="line">  <span class="attr">SubnetId:</span> <span class="type">!Ref</span> <span class="string">SubnetA</span></span><br></pre></td></tr></table></figure><p>By default, the data in transit between an EC2 instance and the EFS file system is not encrypted. But, even though the data does not leave your VPC in most scenarios, AWS recommends encrypting all data in transit.</p><h2 id="Encryption-in-Transit-TLS-for-EFS"><a href="#Encryption-in-Transit-TLS-for-EFS" class="headerlink" title="Encryption in Transit (TLS) for EFS"></a>Encryption in Transit (TLS) for EFS</h2><p>It is pretty simple to enable encryption of the data in transit as EFS file systems support TLS out of the box. For this to work, you must establish a TLS tunnel to EFS before mounting on the EC2 instance. Doing so is quite simple. Install <code>amazon-efs-utils</code> and use the tool to mount EFS file systems, as illustrated in the following figure.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/09/efs-tls@730w.webp 730w, /images/2022/09/efs-tls@730w2x.webp 1460w, /images/2022/09/efs-tls@610w.webp 610w, /images/2022/09/efs-tls@610w2x.webp 1220w, /images/2022/09/efs-tls@450w.webp 450w, /images/2022/09/efs-tls@450w2x.webp 900w, /images/2022/09/efs-tls@330w.webp 330w, /images/2022/09/efs-tls@330w2x.webp 660w, /images/2022/09/efs-tls@545w.webp 545w, /images/2022/09/efs-tls@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/09/efs-tls@730w.png 730w, /images/2022/09/efs-tls@730w2x.png 1460w, /images/2022/09/efs-tls@610w.png 610w, /images/2022/09/efs-tls@610w2x.png 1220w, /images/2022/09/efs-tls@450w.png 450w, /images/2022/09/efs-tls@450w2x.png 900w, /images/2022/09/efs-tls@330w.png 330w, /images/2022/09/efs-tls@330w2x.png 660w, /images/2022/09/efs-tls@545w.png 545w, /images/2022/09/efs-tls@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/09/efs-tls.png" alt="Encrypting data in transit for EFS" title="Encrypting data in transit for EFS"></picture></p><p>The following excerpt of a CloudFormation template explains the details. Direct your attention to the shell script executed via user data when the EC2 instance is first started.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">EC2InstanceB:</span></span><br><span class="line"> <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::Instance&#x27;</span></span><br><span class="line"> <span class="attr">Properties:</span></span><br><span class="line"> <span class="attr">ImageId:</span> <span class="type">!FindInMap</span> [<span class="string">RegionMap</span>, <span class="type">!Ref</span> <span class="string">&#x27;AWS::Region&#x27;</span>, <span class="string">AMI</span>]</span><br><span class="line"> <span class="attr">InstanceType:</span> <span class="string">&#x27;t2.micro&#x27;</span></span><br><span class="line"> <span class="attr">IamInstanceProfile:</span> <span class="type">!Ref</span> <span class="string">IamInstanceProfile</span></span><br><span class="line"> <span class="attr">NetworkInterfaces:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">AssociatePublicIpAddress:</span> <span class="literal">true</span></span><br><span class="line"> <span class="attr">DeleteOnTermination:</span> <span class="literal">true</span></span><br><span class="line"> <span class="attr">DeviceIndex:</span> <span class="number">0</span></span><br><span class="line"> <span class="attr">GroupSet:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">EFSClientSecurityGroup</span></span><br><span class="line"> <span class="attr">SubnetId:</span> <span class="type">!Ref</span> <span class="string">SubnetB</span></span><br><span class="line"> <span class="attr">UserData:</span></span><br><span class="line"> <span class="attr">&#x27;Fn::Base64&#x27;:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string"> #!/bin/bash -ex</span></span><br><span class="line"><span class="string"> trap &#x27;/opt/aws/bin/cfn-signal -e 1 --stack $&#123;AWS::StackName&#125; --resource EC2InstanceB --region $&#123;AWS::Region&#125;&#x27; ERR</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"> <span class="comment"># install dependencies</span></span><br><span class="line"> <span class="string">yum</span> <span class="string">install</span> <span class="string">-y</span> <span class="string">nc</span> <span class="string">amazon-efs-utils</span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"># mount EFS file system</span></span><br><span class="line"> <span class="string">echo</span> <span class="string">&quot;$&#123;FileSystem&#125;:/ /home efs _netdev,noresvport,tls 0 0&quot;</span> <span class="string">&gt;&gt;</span> <span class="string">/etc/fstab</span></span><br><span class="line"> <span class="string">mount</span> <span class="string">-a</span></span><br><span class="line"></span><br><span class="line"> <span class="string">/opt/aws/bin/cfn-signal</span> <span class="string">-e</span> <span class="string">$?</span> <span class="string">--stack</span> <span class="string">$&#123;AWS::StackName&#125;</span> <span class="string">--resource</span> <span class="string">EC2InstanceB</span> <span class="string">--region</span> <span class="string">$&#123;AWS::Region&#125;</span></span><br><span class="line"> <span class="attr">Tags:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">Key:</span> <span class="string">Name</span></span><br><span class="line"> <span class="attr">Value:</span> <span class="string">&#x27;efs-b&#x27;</span></span><br><span class="line"> <span class="attr">CreationPolicy:</span></span><br><span class="line"> <span class="attr">ResourceSignal:</span></span><br><span class="line"> <span class="attr">Timeout:</span> <span class="string">PT10M</span></span><br></pre></td></tr></table></figure><p>The user data script adds an entry to the <code>/etc/fstab</code> configuration file. The critical option is named <code>tls</code> which tells the <code>efs-utils</code> installed before establishing a TLS tunnel before mounting the network file system.</p><p>On top of that, use a file system policy to deny insecure access to the file system.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">FileSystem:</span></span><br><span class="line"> <span class="attr">Type:</span> <span class="string">&#x27;AWS::EFS::FileSystem&#x27;</span></span><br><span class="line"> <span class="attr">Properties:</span></span><br><span class="line"> <span class="attr">Encrypted:</span> <span class="literal">true</span></span><br><span class="line"> <span class="attr">ThroughputMode:</span> <span class="string">bursting</span></span><br><span class="line"> <span class="attr">PerformanceMode:</span> <span class="string">generalPurpose</span></span><br><span class="line"> <span class="attr">FileSystemPolicy:</span> <span class="comment"># resource-based policy</span></span><br><span class="line"> <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line"> <span class="attr">Statement:</span> <span class="comment"># deny access when data in transit is not encrypted</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">&#x27;Deny&#x27;</span></span><br><span class="line"> <span class="attr">Action:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line"> <span class="attr">Principal:</span></span><br><span class="line"> <span class="attr">AWS:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line"> <span class="attr">Condition:</span></span><br><span class="line"> <span class="attr">Bool:</span></span><br><span class="line"> <span class="attr">&#x27;aws:SecureTransport&#x27;:</span> <span class="string">&#x27;false&#x27;</span></span><br></pre></td></tr></table></figure><p>The resource-based policy leads us to use IAM to authenticate and authorize requests to an EFS file system.</p><h2 id="IAM-authentication-and-authorization-for-EFS"><a href="#IAM-authentication-and-authorization-for-EFS" class="headerlink" title="IAM authentication and authorization for EFS"></a>IAM authentication and authorization for EFS</h2><p>By default, mounting an EFS file system does not require any authentication. Typically, only the security groups control access to a file system on the network level. But EFS supports IAM authentication for mounting network file systems as well.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/09/efs-iam@730w.webp 730w, /images/2022/09/efs-iam@730w2x.webp 1460w, /images/2022/09/efs-iam@610w.webp 610w, /images/2022/09/efs-iam@610w2x.webp 1220w, /images/2022/09/efs-iam@450w.webp 450w, /images/2022/09/efs-iam@450w2x.webp 900w, /images/2022/09/efs-iam@330w.webp 330w, /images/2022/09/efs-iam@330w2x.webp 660w, /images/2022/09/efs-iam@545w.webp 545w, /images/2022/09/efs-iam@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/09/efs-iam@730w.png 730w, /images/2022/09/efs-iam@730w2x.png 1460w, /images/2022/09/efs-iam@610w.png 610w, /images/2022/09/efs-iam@610w2x.png 1220w, /images/2022/09/efs-iam@450w.png 450w, /images/2022/09/efs-iam@450w2x.png 900w, /images/2022/09/efs-iam@330w.png 330w, /images/2022/09/efs-iam@330w2x.png 660w, /images/2022/09/efs-iam@545w.png 545w, /images/2022/09/efs-iam@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/09/efs-iam.png" alt="Requiring IAM authentication and authorization for accessing EFS file systems" title="Requiring IAM authentication and authorization for accessing EFS file systems"></picture></p><p>IAM for EFS requires two things:</p><ul><li>Configuring a file system policy.</li><li>Attaching an IAM role to the EC2 instance.</li></ul><p>An EFS file system without a file system policy does not require any authentication. But after defining such a resource-based policy, accessing the file system requires authentication and authorization via IAM. So when we added the file system policy to deny unencrypted traffic in the previous section, we also activated IAM authentication and authorization.</p><p>Luckily, the <code>amazon-efs-utils</code> do support not only TLS but also IAM. All you have to do is to add another mount parameter called <code>iam</code> to the <code>/etc/fstab</code> configuration file.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">EC2InstanceB:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::Instance&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">  <span class="attr">ImageId:</span> <span class="type">!FindInMap</span> [<span class="string">RegionMap</span>, <span class="type">!Ref</span> <span class="string">&#x27;AWS::Region&#x27;</span>, <span class="string">AMI</span>]</span><br><span class="line">  <span class="attr">InstanceType:</span> <span class="string">&#x27;t2.micro&#x27;</span></span><br><span class="line">  <span class="attr">IamInstanceProfile:</span> <span class="type">!Ref</span> <span class="string">IamInstanceProfile</span></span><br><span class="line">  <span class="attr">NetworkInterfaces:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">AssociatePublicIpAddress:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">DeleteOnTermination:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">DeviceIndex:</span> <span class="number">0</span></span><br><span class="line">  <span class="attr">GroupSet:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">EFSClientSecurityGroup</span></span><br><span class="line">  <span class="attr">SubnetId:</span> <span class="type">!Ref</span> <span class="string">SubnetB</span></span><br><span class="line">  <span class="attr">UserData:</span></span><br><span class="line">  <span class="attr">&#x27;Fn::Base64&#x27;:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">  #!/bin/bash -ex</span></span><br><span class="line"><span class="string">  trap &#x27;/opt/aws/bin/cfn-signal -e 1 --stack $&#123;AWS::StackName&#125; --resource EC2InstanceB --region $&#123;AWS::Region&#125;&#x27; ERR</span></span><br><span class="line"><span class="string"></span> </span><br><span class="line">  <span class="comment"># install dependencies</span></span><br><span class="line">  <span class="string">yum</span> <span class="string">install</span> <span class="string">-y</span> <span class="string">nc</span> <span class="string">amazon-efs-utils</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment"># mount EFS file system</span></span><br><span class="line">  <span class="string">echo</span> <span class="string">&quot;$&#123;FileSystem&#125;:/ /home efs _netdev,noresvport,tls,iam 0 0&quot;</span> <span class="string">&gt;&gt;</span> <span class="string">/etc/fstab</span></span><br><span class="line">  <span class="string">mount</span> <span class="string">-a</span></span><br><span class="line"> </span><br><span class="line">  <span class="string">/opt/aws/bin/cfn-signal</span> <span class="string">-e</span> <span class="string">$?</span> <span class="string">--stack</span> <span class="string">$&#123;AWS::StackName&#125;</span> <span class="string">--resource</span> <span class="string">EC2InstanceB</span> <span class="string">--region</span> <span class="string">$&#123;AWS::Region&#125;</span></span><br><span class="line">  <span class="attr">Tags:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">Key:</span> <span class="string">Name</span></span><br><span class="line">  <span class="attr">Value:</span> <span class="string">&#x27;efs-b&#x27;</span></span><br><span class="line">  <span class="attr">CreationPolicy:</span></span><br><span class="line">  <span class="attr">ResourceSignal:</span></span><br><span class="line">  <span class="attr">Timeout:</span> <span class="string">PT10M</span></span><br></pre></td></tr></table></figure><p>Of course, you need to assign an IAM role to the EC2 instance to grant access to the EFS file system. The following code snippet shows how to do so with CloudFormation.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">IamRole:</span></span><br><span class="line"> <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line"> <span class="attr">Properties:</span></span><br><span class="line"> <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line"> <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line"> <span class="attr">Statement:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line"> <span class="attr">Principal:</span></span><br><span class="line"> <span class="attr">Service:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">&#x27;ec2.amazonaws.com&#x27;</span></span><br><span class="line"> <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line"> <span class="attr">Policies:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">efs</span></span><br><span class="line"> <span class="attr">PolicyDocument:</span></span><br><span class="line"> <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line"> <span class="attr">Statement:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line"> <span class="attr">Action:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">&#x27;elasticfilesystem:ClientRootAccess&#x27;</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">&#x27;elasticfilesystem:ClientWrite&#x27;</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">&#x27;elasticfilesystem:ClientMount&#x27;</span></span><br><span class="line"> <span class="attr">Resource:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;FileSystem.Arn&#x27;</span></span><br></pre></td></tr></table></figure><p>That’s it. With IAM, you added authentication and authorization at the application layer. So you are not only relying on security groups to control access to EFS file systems anymore.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>To protect your data stored on EFS (Amazon Elastic File System), I recommend enabling and enforcing encryption in transit (TLS) as well as IAM authentication and authorization. The <code>amazon-efs-utils</code> support TLS and IAM out of the box.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Step Functions pitfall: The execution reached the maximum number of history events (25000)</title>
      <link>https://cloudonaut.io/step-functions-pitfall-maximum-number-of-history-events/</link>
      <description>Learn to deal with Step Function's &quot;The execution reached the maximum number of history events (25000).&quot; error</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/step-functions/">step-functions</category>
      <guid isPermaLink="true">https://cloudonaut.io/step-functions-pitfall-maximum-number-of-history-events/</guid>
      <pubDate>Wed, 31 Aug 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><a href="https://aws.amazon.com/step-functions/" target="_blank" rel="noopener">AWS Step Functions</a> is an execution environment for finite state machines. Lately, I was running into the error “The execution reached the maximum number of history events (25000).” when listing all objects in an S3 bucket page by page. This blog post will teach you why the error happens and how to avoid it.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/08/history-events-error@730w.webp 730w, /images/2022/08/history-events-error@730w2x.webp 1460w, /images/2022/08/history-events-error@610w.webp 610w, /images/2022/08/history-events-error@610w2x.webp 1220w, /images/2022/08/history-events-error@450w.webp 450w, /images/2022/08/history-events-error@450w2x.webp 900w, /images/2022/08/history-events-error@330w.webp 330w, /images/2022/08/history-events-error@330w2x.webp 660w, /images/2022/08/history-events-error@545w.webp 545w, /images/2022/08/history-events-error@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/08/history-events-error@730w.jpg 730w, /images/2022/08/history-events-error@730w2x.jpg 1460w, /images/2022/08/history-events-error@610w.jpg 610w, /images/2022/08/history-events-error@610w2x.jpg 1220w, /images/2022/08/history-events-error@450w.jpg 450w, /images/2022/08/history-events-error@450w2x.jpg 900w, /images/2022/08/history-events-error@330w.jpg 330w, /images/2022/08/history-events-error@330w2x.jpg 660w, /images/2022/08/history-events-error@545w.jpg 545w, /images/2022/08/history-events-error@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/08/history-events-error.jpg" alt="The execution reached the maximum number of history events" title="The execution reached the maximum number of history events"></picture></p><h2 id="Introducing-Step-Functions"><a href="#Introducing-Step-Functions" class="headerlink" title="Introducing Step Functions"></a>Introducing Step Functions</h2><p>In Step Functions, a state machine is a collection of states. A state does some work (e.g., invoke a Lambda function, terminate the state machine, if&#x2F;else statement, and many more) and points to the next state. When executing a state machine, you can hand over data used as the input into the first state. Each state gets input data and outputs data as well. The following figure shows four states:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/08/state-machine@730w.webp 730w, /images/2022/08/state-machine@730w2x.webp 1460w, /images/2022/08/state-machine@610w.webp 610w, /images/2022/08/state-machine@610w2x.webp 1220w, /images/2022/08/state-machine@450w.webp 450w, /images/2022/08/state-machine@450w2x.webp 900w, /images/2022/08/state-machine@330w.webp 330w, /images/2022/08/state-machine@330w2x.webp 660w, /images/2022/08/state-machine@545w.webp 545w, /images/2022/08/state-machine@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/08/state-machine@730w.png 730w, /images/2022/08/state-machine@730w2x.png 1460w, /images/2022/08/state-machine@610w.png 610w, /images/2022/08/state-machine@610w2x.png 1220w, /images/2022/08/state-machine@450w.png 450w, /images/2022/08/state-machine@450w2x.png 900w, /images/2022/08/state-machine@330w.png 330w, /images/2022/08/state-machine@330w2x.png 660w, /images/2022/08/state-machine@545w.png 545w, /images/2022/08/state-machine@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/08/state-machine.png" alt="State Machine to page trough all objects in an S3 bucket" title="State Machine to page trough all objects in an S3 bucket"></picture></p><ul><li><strong>Run</strong>: Invokes a Lambda function to call the S3 API to list objects starting at <code>NextKeyMarker</code> from the input. Outputs the new <code>NextKeyMarker</code> as well as the <code>IsTruncated</code> response of the S3 API. Next state: <strong>CheckEndOfPage</strong>.</li><li><strong>CheckEndOfPage</strong>: Checks if <code>IsTruncated</code> is set to true. Outputs the input. If yes, transition to <strong>Wait</strong>. If no, transition to <strong>Done</strong>.</li><li><strong>Wait</strong>: Waits for a couple of seconds. Outputs the input. Transition to <strong>Run</strong>.</li><li><strong>Done</strong>: All objects are fetched. Successfully terminates the state machine. Outputs the input.</li></ul><h2 id="The-pitfall"><a href="#The-pitfall" class="headerlink" title="The pitfall"></a>The pitfall</h2><p>The number of state transitions allowed in the execution of the state machine is limited. But the limit is not well defined. Instead, Step Functions limits the number of history events per execution to 25,000. Wait? What are history events?<br>The official <a href="https://docs.aws.amazon.com/step-functions/latest/dg/welcome.html" target="_blank" rel="noopener">developer guide</a> does not mention them. But you can see them in the UI, as the screenshot shows.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/08/history-events-ui@730w.webp 730w, /images/2022/08/history-events-ui@730w2x.webp 1460w, /images/2022/08/history-events-ui@610w.webp 610w, /images/2022/08/history-events-ui@610w2x.webp 1220w, /images/2022/08/history-events-ui@450w.webp 450w, /images/2022/08/history-events-ui@450w2x.webp 900w, /images/2022/08/history-events-ui@330w.webp 330w, /images/2022/08/history-events-ui@330w2x.webp 660w, /images/2022/08/history-events-ui@545w.webp 545w, /images/2022/08/history-events-ui@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/08/history-events-ui@730w.png 730w, /images/2022/08/history-events-ui@730w2x.png 1460w, /images/2022/08/history-events-ui@610w.png 610w, /images/2022/08/history-events-ui@610w2x.png 1220w, /images/2022/08/history-events-ui@450w.png 450w, /images/2022/08/history-events-ui@450w2x.png 900w, /images/2022/08/history-events-ui@330w.png 330w, /images/2022/08/history-events-ui@330w2x.png 660w, /images/2022/08/history-events-ui@545w.png 545w, /images/2022/08/history-events-ui@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/08/history-events-ui.png" alt="AWS Step Functions UII history events" title="AWS Step Functions UII history events"></picture></p><p>The execution listed the objects of an S3 bucket with a handful of objects. Only one page was available. But still, 11 history events are emitted. The <strong>Run</strong> state emits 5 history events, and <strong>CheckEndOfPage</strong> emits two. <strong>Wait</strong> was never reached, but it would emit two history events. For each page of S3 objects, 9 history events are emitted. I divide the maximum allowed number of history events by 9, and I get a maximum of 2,777 (25,000&#x2F;9) pages I can fetch. The S3 API returns not more than 1,000 objects per API call. Doing the math again reveals that my state machine can list buckets with no more than 2.7 mio objects. My bucket was, of course, larger than that. The execution errored: “The execution reached the maximum number of history events (25000).”</p><h2 id="Solving-the-issue"><a href="#Solving-the-issue" class="headerlink" title="Solving the issue"></a>Solving the issue</h2><p>The 25,000 history events limit will not go away. We have to work around it. I suggest first understanding the relationship between the state types in your state machine and the number of history events emitted. I was stunned to learn that invoking a Lambda function emits 5 history events. I was looking for a table that tells me how many history events I can expect for a given state type. Unfortunately, such a table does not exist, so I created my own (please <a href="mailto:&#104;&#101;&#108;&#108;&#x6f;&#x40;&#99;&#108;&#111;&#x75;&#100;&#111;&#110;&#97;&#117;&#116;&#x2e;&#x69;&#x6f;">reach out to me</a> to fill the <code>?</code> gaps if your state machine uses these state types):</p><table class="table table-striped table-responsive"><thead><tr><th>State Type</th><th>Task Type</th><th>Expected number of history events</th></tr></thead><tbody><tr><td>Choice</td><td>-</td><td>2</td></tr><tr><td>Fail</td><td>-</td><td>?</td></tr><tr><td>Map</td><td>-</td><td>?</td></tr><tr><td>Parallel</td><td>-</td><td>?</td></tr><tr><td>Pass</td><td>-</td><td>?</td></tr><tr><td>Succeed</td><td>-</td><td>2</td></tr><tr><td>Task</td><td><code>servicename</code></td><td>?</td></tr><tr><td>Task</td><td>activity</td><td>?</td></tr><tr><td>Task</td><td>function</td><td>5</td></tr><tr><td>Wait</td><td>-</td><td>2</td></tr></tbody></table><p>Remember that one history event is emitted at the beginning of the execution and one at the end.</p><p>Now that we understand how many history events we emit, we can choose between two strategies to solve the issue.</p><ol><li>Get more work done in a state.</li><li>Start a new state machine execution before we reach the limit.</li></ol><h3 id="Get-more-work-done-in-a-state"><a href="#Get-more-work-done-in-a-state" class="headerlink" title="Get more work done in a state."></a>Get more work done in a state.</h3><p>This was the strategy I implemented to list more S3 objects. Instead of making a single S3 API call to fetch 1,000 objects, I now call the S3 API 100 times and fetch up to 100,000 objects in one Lambda function execution.</p><p>Downsides:</p><ol><li>The Lambda timeout limit must be taken into account. In my case, listing 100,000 objets takes under 1 minute and is well below the 15-minute limit.</li><li>Step Functions is great at retrying a state if something goes wrong. E.g., imagine one S3 API call fails with a 500 error. The state machine can be configured to retry the Lambda function execution. The more work you do in your state, the more work is retried. In my case, that’s okay because I only read a lot of data. If you perform expensive writes, that might be different.</li></ol><h3 id="Start-a-new-state-machine-execution-before-we-reach-the-limit"><a href="#Start-a-new-state-machine-execution-before-we-reach-the-limit" class="headerlink" title="Start a new state machine execution before we reach the limit."></a>Start a new state machine execution before we reach the limit.</h3><p>This strategy is <a href="https://docs.aws.amazon.com/step-functions/latest/dg/bp-history-limit.html" target="_blank" rel="noopener">recommended by AWS</a> and can be implemented <a href="https://docs.aws.amazon.com/step-functions/latest/dg/concepts-nested-workflows.html" target="_blank" rel="noopener">like this</a>.</p><p>Downsides:</p><ol><li>You can create an expensive infinite loop.</li></ol><p>I hope this article will help you avoid the pitfall.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Monitor VPC NAT gateways with CloudWatch metrics and alarms</title>
      <link>https://cloudonaut.io/nat-gateway-monitoring-with-cloudwatch-metrics-and-alarms/</link>
      <description>
        <![CDATA[<p>Many VPC designs make use of public and private subnets. A NAT gateway is needed to communicate from a private subnet with the Internet.<]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <guid isPermaLink="true">https://cloudonaut.io/nat-gateway-monitoring-with-cloudwatch-metrics-and-alarms/</guid>
      <pubDate>Mon, 15 Aug 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Many VPC designs make use of public and private subnets. A NAT gateway is needed to communicate from a private subnet with the Internet.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/08/network-device@730w.webp 730w, /images/2022/08/network-device@730w2x.webp 1460w, /images/2022/08/network-device@610w.webp 610w, /images/2022/08/network-device@610w2x.webp 1220w, /images/2022/08/network-device@450w.webp 450w, /images/2022/08/network-device@450w2x.webp 900w, /images/2022/08/network-device@330w.webp 330w, /images/2022/08/network-device@330w2x.webp 660w, /images/2022/08/network-device@545w.webp 545w, /images/2022/08/network-device@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/08/network-device@730w.jpg 730w, /images/2022/08/network-device@730w2x.jpg 1460w, /images/2022/08/network-device@610w.jpg 610w, /images/2022/08/network-device@610w2x.jpg 1220w, /images/2022/08/network-device@450w.jpg 450w, /images/2022/08/network-device@450w2x.jpg 900w, /images/2022/08/network-device@330w.jpg 330w, /images/2022/08/network-device@330w2x.jpg 660w, /images/2022/08/network-device@545w.jpg 545w, /images/2022/08/network-device@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/08/network-device.jpg" alt="Monitor VPC NAT gateways with CloudWatch metrics and alarms" title="Monitor VPC NAT gateways with CloudWatch metrics and alarms"></picture></p><p>A <a href="https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html" target="_blank" rel="noopener">VPC NAT gateway</a> is a finite resource that can be exhausted. That’s why you need to add monitoring to be alerted if the NAT gateway gets a bottleneck.</p><h2 id="CloudWatch-metrics"><a href="#CloudWatch-metrics" class="headerlink" title="CloudWatch metrics"></a>CloudWatch metrics</h2><p>Each NAT gateway sends <a href="https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway-cloudwatch.html" target="_blank" rel="noopener">metrics to CloudWatch</a> that we can monitor with CloudWatch alarms. We recommend creating alarms for the following metrics:</p><ul><li><code>ErrorPortAllocation</code>: The number of times the NAT gateway could not allocate a source port.</li><li><code>PacketsDropCount</code>: The number of packets dropped by the NAT gateway.</li></ul><h2 id="Monitoring-throughput-utilization"><a href="#Monitoring-throughput-utilization" class="headerlink" title="Monitoring throughput utilization"></a>Monitoring throughput utilization</h2><p>Unfortunately, NAT gateways do not report a single metric on the throughput utilization of bandwidth and packets. The maximum bandwidth is 100 Gbit&#x2F;second and 10,000,000 packets&#x2F;second. Luckily, we can calculate throughput by using <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/using-metric-math.html" target="_blank" rel="noopener">CloudWatch metric math</a>.</p><p>To calculate the bandwidth utilization, we use the following metrics:</p><table class="table table-striped table-responsive"><thead><tr><th>ID</th><th>metric</th><th>statistic</th><th>period</th></tr></thead><tbody><tr><td>in1</td><td>BytesInFromDestination</td><td>Sum</td><td>60</td></tr><tr><td>in2</td><td>BytesInFromSource</td><td>Sum</td><td>60</td></tr><tr><td>out1</td><td>BytesOutToDestination</td><td>Sum</td><td>60</td></tr><tr><td>out2</td><td>BytesOutToSource</td><td>Sum</td><td>60</td></tr></tbody></table><p>And the following expressions:</p><table class="table table-striped table-responsive"><thead><tr><th>ID</th><th>expression</th><th>comment</th></tr></thead><tbody><tr><td>bandwidth</td><td>(in1+in2+out1+out2)/60*8/1000/1000/1000</td><td>Bytes/min to Gbit/s</td></tr><tr><td>utilization</td><td>bandwidth/100*100</td><td>to %; 100 Gbit/s is the hard limit</td></tr></tbody></table><h2 id="marbot’s-Monitoring-Setup-Assistant"><a href="#marbot’s-Monitoring-Setup-Assistant" class="headerlink" title="marbot’s Monitoring Setup Assistant"></a>marbot’s Monitoring Setup Assistant</h2><p>CloudWatch metric math sounds complicated? We have you covered! <a href="https://marbot.io/" target="_blank" rel="noopener">Monitor NAT gateways and receive alerts in Slack or Microsoft Teams with our ChatOps bot marbot</a>!</p><p>PS: If marbot is not your thing, you can still find inspiration in marbot’s <a href="https://github.com/marbot-io/monitoring-jump-start/blob/master/marbot-nat-gateway.yml" target="_blank" rel="noopener">CloudFormation template</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>CloudFormation cfn-init pitfall: Auto scaling and throttling error rate exceeded</title>
      <link>https://cloudonaut.io/cloudformation-cfn-init-pitfall-throttling-error-rate-exceeded/</link>
      <description>
        <![CDATA[<p><code>cfn-init</code> is a little helper to install and configure EC2 instances managed with CloudFormation. Lately, I was running into i]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <guid isPermaLink="true">https://cloudonaut.io/cloudformation-cfn-init-pitfall-throttling-error-rate-exceeded/</guid>
      <pubDate>Thu, 11 Aug 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><code>cfn-init</code> is a little helper to install and configure EC2 instances managed with CloudFormation. Lately, I was running into issues when starting a more significant amount of EC2 (let’s say 50) during an auto scaling event. This blog post will teach you why the error happens and how to avoid it.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/08/throttling-error@730w.webp 730w, /images/2022/08/throttling-error@730w2x.webp 1460w, /images/2022/08/throttling-error@610w.webp 610w, /images/2022/08/throttling-error@610w2x.webp 1220w, /images/2022/08/throttling-error@450w.webp 450w, /images/2022/08/throttling-error@450w2x.webp 900w, /images/2022/08/throttling-error@330w.webp 330w, /images/2022/08/throttling-error@330w2x.webp 660w, /images/2022/08/throttling-error@545w.webp 545w, /images/2022/08/throttling-error@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/08/throttling-error@730w.jpg 730w, /images/2022/08/throttling-error@730w2x.jpg 1460w, /images/2022/08/throttling-error@610w.jpg 610w, /images/2022/08/throttling-error@610w2x.jpg 1220w, /images/2022/08/throttling-error@450w.jpg 450w, /images/2022/08/throttling-error@450w2x.jpg 900w, /images/2022/08/throttling-error@330w.jpg 330w, /images/2022/08/throttling-error@330w2x.jpg 660w, /images/2022/08/throttling-error@545w.jpg 545w, /images/2022/08/throttling-error@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/08/throttling-error.jpg" alt="Throttling error rate exceeded" title="Throttling error rate exceeded"></picture></p><h2 id="Introducing-cfn-init"><a href="#Introducing-cfn-init" class="headerlink" title="Introducing cfn-init"></a>Introducing cfn-init</h2><p><code>cfn-init</code> configuration is added as metadata to a resource using the <code>AWS::CloudFormation::Init</code> key. The following example configures <code>cfn-init</code> to</p><ul><li>create&#x2F;update the file <code>/etc/sample.conf</code>.</li><li>enable &amp; start the service <code>sample</code> (also restarts the service if <code>/etc/sample.conf</code> is changed).</li></ul><p><code>cfn-init</code> is usually executed in the user data script.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">VirtualMachine:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::Instance&#x27;</span></span><br><span class="line">  <span class="attr">Metadata:</span></span><br><span class="line">    <span class="attr">&#x27;AWS::CloudFormation::Init&#x27;:</span></span><br><span class="line">      <span class="attr">config:</span></span><br><span class="line">        <span class="attr">files:</span></span><br><span class="line">          <span class="string">&#x27;/etc/sample.conf&#x27;</span><span class="string">:</span></span><br><span class="line">            <span class="attr">content:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">              [main]</span></span><br><span class="line"><span class="string">              region=$&#123;AWS::Region&#125;</span></span><br><span class="line"><span class="string"></span>            <span class="attr">mode:</span> <span class="string">&#x27;000400&#x27;</span></span><br><span class="line">            <span class="attr">owner:</span> <span class="string">root</span></span><br><span class="line">            <span class="attr">group:</span> <span class="string">root</span></span><br><span class="line">        <span class="attr">services:</span></span><br><span class="line">          <span class="attr">sysvinit:</span></span><br><span class="line">            <span class="attr">sample:</span></span><br><span class="line">              <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">              <span class="attr">ensureRunning:</span> <span class="literal">true</span></span><br><span class="line">              <span class="attr">files:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="string">&#x27;/etc/sample.conf&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line">    <span class="attr">UserData:</span></span><br><span class="line">      <span class="attr">&#x27;Fn::Base64&#x27;:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">        #!/bin/bash -ex</span></span><br><span class="line"><span class="string">        /opt/aws/bin/cfn-init -v --stack $&#123;AWS::StackName&#125; --resource VirtualMachine --region $&#123;AWS::Region&#125;</span></span><br></pre></td></tr></table></figure><h2 id="The-pitfall"><a href="#The-pitfall" class="headerlink" title="The pitfall"></a>The pitfall</h2><p>The way <code>cfn-init</code>  is implemented is this: </p><ol><li>Call the CloudFormation API <code>DescribeStackResource</code> to read the metadata.</li><li>Validate and parse the metadata.</li><li>Apply the configuration to the EC2 instance.</li></ol><p>Unfortunately, the CloudFormation API has notorious low API rate limits, <strong>and</strong> <code>cfn-init</code> does not retry in the case of a rate exceeded error. Therefore, when many EC2 instances run <code>cfn-init</code> more or less at the same time, you will see the following error:</p><figure class="highlight erlang-repl"><table><tr><td class="code"><pre><span class="line"><span class="number">2022</span>-<span class="number">06</span>-<span class="number">02</span> <span class="number">07</span>:<span class="number">13</span>:<span class="number">14</span>,<span class="number">838</span> [DEBUG] Response: <span class="number">400</span> https://cloudformation.us-east-<span class="number">1</span>.amazonaws.com/?Action=DescribeStackResource&amp;LogicalResourceId=ScanLaunchTemplate&amp;ContentType=JSON&amp;StackName=***&amp;Version=<span class="number">2010</span>-<span class="number">05</span>-<span class="number">15</span> [headers: &#123;<span class="string">&#x27;x-amzn-RequestId&#x27;</span>: <span class="string">&#x27;***&#x27;</span>, <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span>, <span class="string">&#x27;Content-Length&#x27;</span>: <span class="string">&#x27;124&#x27;</span>, <span class="string">&#x27;Date&#x27;</span>: <span class="string">&#x27;Thu, 02 Jun 2022 07:13:14 GMT&#x27;</span>, <span class="string">&#x27;Connection&#x27;</span>: <span class="string">&#x27;close&#x27;</span>&#125;]</span><br><span class="line"><span class="number">2022</span>-<span class="number">06</span>-<span class="number">02</span> <span class="number">07</span>:<span class="number">13</span>:<span class="number">14</span>,<span class="number">838</span> [DEBUG] Response error: b&#x27;&#123;<span class="string">&quot;&quot;</span>Error<span class="string">&quot;&quot;</span>:&#123;<span class="string">&quot;&quot;</span>Code<span class="string">&quot;&quot;</span>:<span class="string">&quot;&quot;</span>Throttling<span class="string">&quot;&quot;</span>,<span class="string">&quot;&quot;</span>Message<span class="string">&quot;&quot;</span>:<span class="string">&quot;&quot;</span>Rate exceeded<span class="string">&quot;&quot;</span>,<span class="string">&quot;&quot;</span>Type<span class="string">&quot;&quot;</span>:<span class="string">&quot;&quot;</span>Sender<span class="string">&quot;&quot;</span>&#125;,<span class="string">&quot;&quot;</span>RequestId<span class="string">&quot;&quot;</span>:<span class="string">&quot;&quot;</span>***<span class="string">&quot;&quot;</span>&#125;<span class="string">&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="Solving-the-issue"><a href="#Solving-the-issue" class="headerlink" title="Solving the issue"></a>Solving the issue</h2><p>How can we solve the issue?</p><ol><li>Do not use <code>cfn-init</code> at all. </li><li>Load the metadata from a file.</li></ol><p>To load the metadata from a file and not the CloudFormation API, create a file (e.g., <code>metadata.json</code>) like this:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;AWS::CloudFormation::Init&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;config&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;files&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;/etc/sample.conf&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[main]\nregion=eu-west-1\n&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;mode&quot;</span><span class="punctuation">:</span> <span class="string">&quot;000400&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;owner&quot;</span><span class="punctuation">:</span> <span class="string">&quot;root&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;group&quot;</span><span class="punctuation">:</span> <span class="string">&quot;root&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;services&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;sysvinit&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;sample&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;ensureRunning&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;files&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">              <span class="string">&quot;/etc/sample.conf&quot;</span></span><br><span class="line">            <span class="punctuation">]</span></span><br><span class="line">          <span class="punctuation">&#125;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>And invoke <code>cfn-init</code> like this:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">/opt/aws/bin/cfn-init -v metadata.json</span><br></pre></td></tr></table></figure><p>I hope this article will help you avoid the pitfall.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Selling an AMI and a CloudFormation template as an alternative to SaaS</title>
      <link>https://cloudonaut.io/selling-ami-cloudformation-alternative-saas/</link>
      <description>
        <![CDATA[<p>We have been selling software through AWS Marketplace since 2019. Selling SaaS is very popular nowadays, and most software vendors are mo]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <category domain="https://cloudonaut.io/tag/aws-marketplace/">aws-marketplace</category>
      <guid isPermaLink="true">https://cloudonaut.io/selling-ami-cloudformation-alternative-saas/</guid>
      <pubDate>Wed, 03 Aug 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>We have been selling software through AWS Marketplace since 2019. Selling SaaS is very popular nowadays, and most software vendors are moving to this model. However, we learned that there is a promising alternative to SaaS: Selling software bundled into a virtual machine image (AMI) together with the Infrastructure as Code template needed to run the software in a secure, highly available, and scalable manner.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/08/selling-ami-cloudformation-alternative-saas@730w.webp 730w, /images/2022/08/selling-ami-cloudformation-alternative-saas@730w2x.webp 1460w, /images/2022/08/selling-ami-cloudformation-alternative-saas@610w.webp 610w, /images/2022/08/selling-ami-cloudformation-alternative-saas@610w2x.webp 1220w, /images/2022/08/selling-ami-cloudformation-alternative-saas@450w.webp 450w, /images/2022/08/selling-ami-cloudformation-alternative-saas@450w2x.webp 900w, /images/2022/08/selling-ami-cloudformation-alternative-saas@330w.webp 330w, /images/2022/08/selling-ami-cloudformation-alternative-saas@330w2x.webp 660w, /images/2022/08/selling-ami-cloudformation-alternative-saas@545w.webp 545w, /images/2022/08/selling-ami-cloudformation-alternative-saas@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/08/selling-ami-cloudformation-alternative-saas@730w.jpg 730w, /images/2022/08/selling-ami-cloudformation-alternative-saas@730w2x.jpg 1460w, /images/2022/08/selling-ami-cloudformation-alternative-saas@610w.jpg 610w, /images/2022/08/selling-ami-cloudformation-alternative-saas@610w2x.jpg 1220w, /images/2022/08/selling-ami-cloudformation-alternative-saas@450w.jpg 450w, /images/2022/08/selling-ami-cloudformation-alternative-saas@450w2x.jpg 900w, /images/2022/08/selling-ami-cloudformation-alternative-saas@330w.jpg 330w, /images/2022/08/selling-ami-cloudformation-alternative-saas@330w2x.jpg 660w, /images/2022/08/selling-ami-cloudformation-alternative-saas@545w.jpg 545w, /images/2022/08/selling-ami-cloudformation-alternative-saas@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/08/selling-ami-cloudformation-alternative-saas.jpg" alt="Selling an AMI and a CloudFormation template as an alternative to SaaS" title="Selling an AMI and a CloudFormation template as an alternative to SaaS"></picture></p><p>In the following, I will share our success story and discuss why selling an AMI and CloudFormation is a promising alternative to SaaS, in my opinion.</p><h2 id="Our-success-story"><a href="#Our-success-story" class="headerlink" title="Our success story"></a>Our success story</h2><p>In 2019, we decided to sell marbot, a Software-as-a-Service solution for AWS Monitoring, through the AWS Marketplace. We liked the idea that the charges for marbot would appear on our customers’ AWS bills instead of requiring a credit card.</p><p>Shortly after, we released bucketAV, a solution for scanning S3 buckets for malware, on the AWS Marketplace. In this case, we are not selling SaaS but an AMI together with a CloudFormation template.</p><p>In 2022, we released another product: HyperEnv for Jenkins, a highly available and scalable CI&#x2F;CD infrastructure. The solution is based on an AMI and a CloudFormation template as well.</p><p>Over the years, our sales started to grow slowly but steadily. As we bootstrapped our company, we spent most of our time consulting to generate a cash flow. In 2021, selling software has become our primary business, and growth accelerated significantly. We are proudly serving more than 500 customers worldwide. That allows us slowly step back from consulting work those days.</p><p>So how does selling software on the AWS Marketplace work?</p><h2 id="How-to-sell-an-AMI-and-CloudFormation-template"><a href="#How-to-sell-an-AMI-and-CloudFormation-template" class="headerlink" title="How to sell an AMI and CloudFormation template?"></a>How to sell an AMI and CloudFormation template?</h2><p>The AWS Marketplace supports the following product types:</p><ul><li>AMI</li><li>AMI with CloudFormation template</li><li>Container</li><li>SageMaker model</li><li>SaaS</li><li>Professional Services</li></ul><p>In the following, we will focus on the <em>AMI with CloudFormation template</em> product type.</p><p>The following figure illustrates how selling an <em>AMI and CloudFormation template</em> product works. The vendor provides an AMI and CloudForamtion template via the AWS Marketplace. The customer spins up the infrastructure described in the CloudFormation template by creating a CloudFormation stack and waiting for the automation to create all the required resources like a VPC, an IAM role, an auto-scaling-group, an SQS queue, and many more. AWS charges the additional software fee for each EC2 instance based on the vendor’s AMI.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/08/aws-marketplace-ami-cloudformation@730w.webp 730w, /images/2022/08/aws-marketplace-ami-cloudformation@730w2x.webp 1460w, /images/2022/08/aws-marketplace-ami-cloudformation@610w.webp 610w, /images/2022/08/aws-marketplace-ami-cloudformation@610w2x.webp 1220w, /images/2022/08/aws-marketplace-ami-cloudformation@450w.webp 450w, /images/2022/08/aws-marketplace-ami-cloudformation@450w2x.webp 900w, /images/2022/08/aws-marketplace-ami-cloudformation@330w.webp 330w, /images/2022/08/aws-marketplace-ami-cloudformation@330w2x.webp 660w, /images/2022/08/aws-marketplace-ami-cloudformation@545w.webp 545w, /images/2022/08/aws-marketplace-ami-cloudformation@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/08/aws-marketplace-ami-cloudformation@730w.png 730w, /images/2022/08/aws-marketplace-ami-cloudformation@730w2x.png 1460w, /images/2022/08/aws-marketplace-ami-cloudformation@610w.png 610w, /images/2022/08/aws-marketplace-ami-cloudformation@610w2x.png 1220w, /images/2022/08/aws-marketplace-ami-cloudformation@450w.png 450w, /images/2022/08/aws-marketplace-ami-cloudformation@450w2x.png 900w, /images/2022/08/aws-marketplace-ami-cloudformation@330w.png 330w, /images/2022/08/aws-marketplace-ami-cloudformation@330w2x.png 660w, /images/2022/08/aws-marketplace-ami-cloudformation@545w.png 545w, /images/2022/08/aws-marketplace-ami-cloudformation@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/08/aws-marketplace-ami-cloudformation.png" alt="How to sell an AMI and CloudFormation template?" title="How to sell an AMI and CloudFormation template?"></picture></p><p>In the following video, I demonstrate the process of purchasing and spinning up a product based on an AMI and CloudFormation template.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=PZhwc8pHjpk">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/PZhwc8pHjpk" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p>Based on our experience, I will compare selling SaaS and AMI+CloudFormation in the following.</p><h2 id="Development"><a href="#Development" class="headerlink" title="Development"></a>Development</h2><p>From a vendor’s perspective, it is much easier to implement a solution and bundle it into an AMI and CloudFormation template than to build a system to serve SaaS customers.</p><ul><li>No need to worry about <strong>multi-tenancy</strong> and to isolate the workloads of different customers from each other.</li><li>No need to implement <strong>rate limiting and throttling</strong> to avoid a noisy customer affecting the experience of others.</li><li>No need to implement <strong>quotas or billing</strong> for resource consumption like storage and traffic.</li></ul><blockquote><p>There is a huge difference in complexity between our SaaS and AMI+CloudFormation products. An AMI+CloudFormation product is much easier to implement.</p></blockquote><h2 id="Onboarding"><a href="#Onboarding" class="headerlink" title="Onboarding"></a>Onboarding</h2><p>SaaS shines when it comes to onboarding new customers. Creating an account and getting started is typically done within a few clicks.</p><p>How does onboarding work with an AMI+CloudFormation product? Aren’t we burdening the customer too much by requiring them to provide the entire infrastructure and deploy the software? In former times, the customer needed to go through endless pages of installation instructions. Besides that, each customer had different starting conditions, think of the network architecture, virtual machines vs. bare metal machines, and so on. So each customer ran into different problems during the installation. But today, it’s different! The vendor delivers a CloudFormation template that spins up a production-ready infrastructure tailored to the needs of the software.</p><blockquote><p>For example, getting started with bucketAV takes less than 30 minutes. The CloudFormation template includes all the required resources and spins up the whole infrastructure in about 15 minutes. Only a few manual steps are needed to get the malware scanning for S3 buckets up and running. Even compared to our SaaS product, that’s a pleasant onboarding experience.</p></blockquote><h2 id="Operations"><a href="#Operations" class="headerlink" title="Operations"></a>Operations</h2><p>In contrast to SaaS, the vendor does not need to operate a production infrastructure for an AMI and CloudFormation-based product. So no need for on-call shifts and also no need to provision and pay for AWS resources. </p><p>On the other hand, the customer is responsible for operating the solution. However, Infrastructure as Code and automation allow the vendor to deliver an infrastructure that is very easy to operate. A few examples.</p><ul><li>CloudWatch: alarms and dashboards for monitoring</li><li>Backup: backups to make sure customers are not losing any data</li><li>Auto Scaling Group: highly available infrastructure that is capable of recovering from failure automatically</li><li>KMS: encryption-at-rest to fulfill security and regulatory requirements out-of-the-box</li><li>Auto Scaling Group: auto-scaling to make sure the infrastructure adapts to the workload automatically</li></ul><p>What’s left for the vendor is to provide outstanding support in case customers have questions or experience issues.</p><blockquote><p>The customers using our AMI+CloudFormation products are often surprised about the production-readiness of the infrastructure we provide with the help of the CloudFormation template. Our customers rarely need to solve incidents manually. One example is a CloudWatch alarm indicating that the maximum number of instances cannot handle the workload anymore, which requires the customer to increase the parameter.</p></blockquote><h2 id="Data-Gravity"><a href="#Data-Gravity" class="headerlink" title="Data Gravity"></a>Data Gravity</h2><p>A challenge of providing SaaS is data gravity. Moving data around is causing traffic costs and engineering efforts. That’s especially a problem for data-intense workloads. As a SaaS provider, you might be able to overcome this issue by deploying your infrastructure close to your customers. For example, by deploying your system into the most used AWS regions. However, doing so increases complexity and costs.</p><p>In contrast, when customers deploy an AMI and CloudFormation product into their accounts, the solution lives close to the customer’s data. No need to move data around.</p><blockquote><p>Our product bucketAV processes large amounts of data stored on S3. As the customers deploy our solution to their AWS accounts in the region they are using to store data; network traffic is free of charge.</p></blockquote><h2 id="KYC-know-your-customer-and-Data-driven-Decisions"><a href="#KYC-know-your-customer-and-Data-driven-Decisions" class="headerlink" title="KYC (know your customer) and Data-driven Decisions"></a>KYC (know your customer) and Data-driven Decisions</h2><p>An undisputed advantage of SaaS is that vendors learn a lot about their customers. Which features are used? What kind of data is processed? The vendor owns the data to answer those questions.</p><p>When shipping a solution bundled into an AMI and CloudFormation template, the vendor misses the foundation for data-driven decisions. Collecting the required data is complex and probably not appreciated by the customers.</p><blockquote><p>We know a lot about the customers of our SaaS product. For example, we check for the most common types of monitoring alarms when deciding to implement a new feature. In contrast, the only inputs we have to make decisions about features regarding HyperEnv for Jenkins are our customers’ feedback and support emails.</p></blockquote><h2 id="Data-Sovereignty"><a href="#Data-Sovereignty" class="headerlink" title="Data Sovereignty"></a>Data Sovereignty</h2><p>A vendor should avoid storing and processing sensitive data whenever possible. By the way, the GDPR even requires vendors to store data only when necessary. When vendors deliver their software bundled into an AMI and CloudFormation template, vendors do not need to store any customer data in their AWS accounts. So the vendors are reducing the risk of leaking or losing customer data.</p><p>On the other hand, the SaaS model requires a lot of trust, as the customer needs to hand over data to the vendor. Therefore, the vendor needs to build trust, for example, by presenting certifications like ISO, SOC, you name it. On the other side, the customer will ask many questions during the procurement process to find out about the security and operational excellence of the vendor’s system and team.</p><p>When providing a solution that the customers deploy to their AWS account, the data does not leave their area of control. Customers have full control and ownership of the data. In my opinion, that’s a big plus for both sides: the vendor and the customer.</p><blockquote><p>The customers of our AMI+CloudFormation products keep telling us that one of the reasons why they decided to use our solution was that data does not leave their AWS accounts.</p></blockquote><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>In my opinion, selling software bundled into an AMI and CloudFormation template is a promising alternative to the SaaS model. From the vendor’s point of view, the complexity decreases as there is no need to develop and operate a sophisticated SaaS-enabled system. Customers like that data is not leaving their AWS accounts, which keeps them in full control.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Fargate vs. App Runner</title>
      <link>https://cloudonaut.io/fargate-vs-apprunner/</link>
      <description>
        <![CDATA[<p>What’s the simplest way to run containers on AWS? My first relevant container workload was running on a fleet of EC2 instances managed by]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <category domain="https://cloudonaut.io/tag/apprunner/">apprunner</category>
      <guid isPermaLink="true">https://cloudonaut.io/fargate-vs-apprunner/</guid>
      <pubDate>Wed, 20 Jul 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>What’s the simplest way to run containers on AWS? My first relevant container workload was running on a fleet of EC2 instances managed by ECS. Maintaining and scaling the needed EC2 instances was wasting a lot of my time. So I got very excited when AWS announced Fargate, a managed compute layer for ECS. Deploying a web application with ECS and Fargate felt quite simple to me. But then, I discovered App Runner, which adds another layer of abstraction and lifts the developer experience to a new level.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/07/compare-fargate-apprunner-basic@730w.webp 730w, /images/2022/07/compare-fargate-apprunner-basic@730w2x.webp 1460w, /images/2022/07/compare-fargate-apprunner-basic@610w.webp 610w, /images/2022/07/compare-fargate-apprunner-basic@610w2x.webp 1220w, /images/2022/07/compare-fargate-apprunner-basic@450w.webp 450w, /images/2022/07/compare-fargate-apprunner-basic@450w2x.webp 900w, /images/2022/07/compare-fargate-apprunner-basic@330w.webp 330w, /images/2022/07/compare-fargate-apprunner-basic@330w2x.webp 660w, /images/2022/07/compare-fargate-apprunner-basic@545w.webp 545w, /images/2022/07/compare-fargate-apprunner-basic@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/07/compare-fargate-apprunner-basic@730w.jpg 730w, /images/2022/07/compare-fargate-apprunner-basic@730w2x.jpg 1460w, /images/2022/07/compare-fargate-apprunner-basic@610w.jpg 610w, /images/2022/07/compare-fargate-apprunner-basic@610w2x.jpg 1220w, /images/2022/07/compare-fargate-apprunner-basic@450w.jpg 450w, /images/2022/07/compare-fargate-apprunner-basic@450w2x.jpg 900w, /images/2022/07/compare-fargate-apprunner-basic@330w.jpg 330w, /images/2022/07/compare-fargate-apprunner-basic@330w2x.jpg 660w, /images/2022/07/compare-fargate-apprunner-basic@545w.jpg 545w, /images/2022/07/compare-fargate-apprunner-basic@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/07/compare-fargate-apprunner-basic.jpg" alt="Fargate vs. App Runner" title="Fargate vs. App Runner"></picture></p><h2 id="Simplicity"><a href="#Simplicity" class="headerlink" title="Simplicity"></a>Simplicity</h2><p>This is a typical architecture for a web application running on ECS and Fargate. The following figure shows the networking configuration, a load balancer, as well as different parts of the ECS service.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/07/overview-fargate@730w.webp 730w, /images/2022/07/overview-fargate@730w2x.webp 1460w, /images/2022/07/overview-fargate@610w.webp 610w, /images/2022/07/overview-fargate@610w2x.webp 1220w, /images/2022/07/overview-fargate@450w.webp 450w, /images/2022/07/overview-fargate@450w2x.webp 900w, /images/2022/07/overview-fargate@330w.webp 330w, /images/2022/07/overview-fargate@330w2x.webp 660w, /images/2022/07/overview-fargate@545w.webp 545w, /images/2022/07/overview-fargate@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/07/overview-fargate@730w.png 730w, /images/2022/07/overview-fargate@730w2x.png 1460w, /images/2022/07/overview-fargate@610w.png 610w, /images/2022/07/overview-fargate@610w2x.png 1220w, /images/2022/07/overview-fargate@450w.png 450w, /images/2022/07/overview-fargate@450w2x.png 900w, /images/2022/07/overview-fargate@330w.png 330w, /images/2022/07/overview-fargate@330w2x.png 660w, /images/2022/07/overview-fargate@545w.png 545w, /images/2022/07/overview-fargate@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/07/overview-fargate.png" alt="ECS + Fargate: VPC, Subnets, ALB, ECS cluster, task definition, service" title="ECS + Fargate: VPC, Subnets, ALB, ECS cluster, task definition, service"></picture></p><p>Compare that with the resources you need to configure to deploy a web application on App Runner, illustrated in the following figure.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/07/overview-apprunner@730w.webp 730w, /images/2022/07/overview-apprunner@730w2x.webp 1460w, /images/2022/07/overview-apprunner@610w.webp 610w, /images/2022/07/overview-apprunner@610w2x.webp 1220w, /images/2022/07/overview-apprunner@450w.webp 450w, /images/2022/07/overview-apprunner@450w2x.webp 900w, /images/2022/07/overview-apprunner@330w.webp 330w, /images/2022/07/overview-apprunner@330w2x.webp 660w, /images/2022/07/overview-apprunner@545w.webp 545w, /images/2022/07/overview-apprunner@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/07/overview-apprunner@730w.png 730w, /images/2022/07/overview-apprunner@730w2x.png 1460w, /images/2022/07/overview-apprunner@610w.png 610w, /images/2022/07/overview-apprunner@610w2x.png 1220w, /images/2022/07/overview-apprunner@450w.png 450w, /images/2022/07/overview-apprunner@450w2x.png 900w, /images/2022/07/overview-apprunner@330w.png 330w, /images/2022/07/overview-apprunner@330w2x.png 660w, /images/2022/07/overview-apprunner@545w.png 545w, /images/2022/07/overview-apprunner@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/07/overview-apprunner.png" alt="App Runner: Service with public HTTP endpoint" title="App Runner: Service with public HTTP endpoint"></picture></p><p>I want to make the difference even more apparent. Here is a comparison of the AWS resources needed to host a web application with ECS + Fargate or App Runner.</p><table class="table table-striped"><thead><tr><th>ECS + Fargate</th><th>App Runner</th></tr></thead><tbody><tr><td>VPC<br>Subnets<br>Routing Table<br>Internet Gateway<br>Security Groups<br>Application Load Balancer<br>Target Group<br>Cluster<br>Task Definition<br>Service<br>Scaling Policy</td><td>App Runner Service</td></tr></tbody></table><p>I want to highlight that App Runner is not a tool that creates and manages all the resources needed for ECS + Fargate in the background. Instead, App Runner provides an abstraction designed for developers looking for a way to deploy their web application with ease.</p><p>How is that possible? Where does the simplicity of App Runner come from? This certainly does not cover all but the majority of today’s use cases.</p><h2 id="Scenarios"><a href="#Scenarios" class="headerlink" title="Scenarios"></a>Scenarios</h2><p>App Runner focuses on a single scenario: a web application answering incoming HTTP requests synchronously. The strong focus creates a simple solution, somewhat atypical for AWS.</p><p>ECS and Fargate support a wide variety of scenarios. For example, your application needs to generate a weekly report for thousands of customers based on a complex database query. You cannot achieve that with App Runner but with ECS and Fargate.</p><table class="table table-striped"><thead><tr><th>Fargate</th><th>App Runner</th></tr></thead><tbody><tr><td>Synchronous HTTP<br>Any TCP/UDP protocol<br>Asynchronous processing<br>Batch processing<br>Event processing<br>...</td><td>Synchronous HTTP</td></tr></tbody></table><p>So, the simplicity of App Runner limits the possible use cases.</p><h2 id="Scaling-down-to-0"><a href="#Scaling-down-to-0" class="headerlink" title="Scaling down to 0"></a>Scaling down to 0</h2><p>Our industry often overemphasizes web-scale applications. Although, there are a lot of applications out there with low traffic by a manageable number of users. App Runner shines in this application area because it allows scaling down to 0.</p><p>App Runner monitors the number of concurrent requests. In case the application receives more than 100 concurrent requests per running container, App Runner will scale out by launching a container. Of course, App Runner scales down the number of containers as well. That’s a behavior you can achieve with ECS and Fargate by configuring scaling policies as well.</p><p>However, if your application does not receive any requests, App Runner will first scale down to the minimum number of containers, which is one by default. Next, App Runner will pause the running container. App Runner will start the paused container without any noticeable delay for the user as soon as a request comes in. While a container is paused, there are no costs for the CPU. You only pay for the memory during this phase.</p><p>Being able to scale down to 0 significantly reduces costs for applications with low traffic and idle periods.</p><h2 id="Pricing"><a href="#Pricing" class="headerlink" title="Pricing"></a>Pricing</h2><p>Comparing the costs for two AWS services is tricky. But I will do my very best.</p><p>Let’s compare the costs per vCPU and GB to get started. As shown in the following table, you are paying a premium of about 58% when using App Runner compared to ECS + Fargate.</p><table class="table table-striped"><thead><tr><th></th><th style="text-align:right">ECS + Fargate</th><th style="text-align:right">App Runner</th></tr></thead><tbody><tr><td>vCPU</td><td style="text-align:right">$0.04048</td><td style="text-align:right">$0.064</td></tr><tr><td>GB</td><td style="text-align:right">$0.004445</td><td style="text-align:right">$0.007</td></tr></tbody></table><p>However, there are a few details to consider.</p><p>First, traffic to an ECS service typically flows through an ALB or NLB. You have to pay for the data processed by the load balancer. App Runner does not charge for the processed traffic.</p><p>Second, Fargate does provide spot pricing with a discount of up to 70% compared to the on-demand price listed above.</p><p>Third, App Runner comes with the ability to scale down to 0, as discussed in the previous section.</p><p>With this in mind, we will compare costs between ECS + Fargate and App Runner next. If you want to model an example yourself, feel free to use my calculator (see <a href="https://docs.google.com/spreadsheets/d/1ixAmNhl1taXz31AefwopmL2S7CKXevrfUOnaLv-MJ44" target="_blank" rel="noopener">Google Sheet: Cost Estimation: Fargate vs. App Runner</a>).</p><p>Imagine a web application used by a handful of accountants to analyze the cash flow of their company weekly. The application requires 1 vCPU and 2 GB memory. As the application is not business-critical, we decide to run a single container only. Also, we are not expecting more than 1 GB of traffic. The application does not receive requests 95% of the time.</p><p>The following table shows that running the workload on App Runner is significantly cheaper than running on ECS + Fargate. That’s because of the idle period of 95% and a small number of containers.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">ECS + Fargate</th><th style="text-align:right">App Runner</th></tr></thead><tbody><tr><td>Containers (Load)</td><td style="text-align:right">$2</td><td style="text-align:right">$3</td></tr><tr><td>Containers (Idle)</td><td style="text-align:right">$34</td><td style="text-align:right">$10</td></tr><tr><td>Load Balancer (ALB)</td><td style="text-align:right">$16</td><td style="text-align:right"></td></tr><tr><td><strong>Total Costs</strong></td><td style="text-align:right"><strong>$52</strong></td><td style="text-align:right"><strong>$13</strong></td></tr></tbody></table><p>Next, picture the web application providing an internal social network for an organization with 1,000 employees. The application is used during business hours, therefore idling 40% of the time. The application requires 1 vCPU and 2 GB per container and scales from 1 to 2 containers based on load. We are expecting 400 GB of traffic per month.</p><p>The following table shows that the costs are almost the same for ECS + Fargate and App Runner in this scenario. That’s because the idle period decreased, and the maximum number of containers increased compared to the previous example.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">ECS + Fargate</th><th style="text-align:right">App Runner</th></tr></thead><tbody><tr><td>Containers (Load)</td><td style="text-align:right">$43</td><td style="text-align:right">$68</td></tr><tr><td>Containers (Idle)</td><td style="text-align:right">$14</td><td style="text-align:right">$4</td></tr><tr><td>Load Balancer (ALB)</td><td style="text-align:right">$20</td><td style="text-align:right"></td></tr><tr><td><strong>Total Costs</strong></td><td style="text-align:right"><strong>$77</strong></td><td style="text-align:right"><strong>$72</strong></td></tr></tbody></table><p>One more scenario to provide a fair comparison between the costs for ECS + Fargate and App Runner: imagine a popular blog on all things AWS. The web application requires 2 vCPU and 4 GB memory per container. We configure scaling from 2 to 6 containers based on load and expect 1,000 GB of traffic. As the blog gets traffic from around the world, the web application idles 5% of the time.</p><p>In this example, the calculation looks quite different. App Runner is significantly more expansive than ECS + Fargate.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">ECS + Fargate</th><th style="text-align:right">App Runner</th></tr></thead><tbody><tr><td>Containers (Load)</td><td style="text-align:right">$411</td><td style="text-align:right">$649</td></tr><tr><td>Containers (Idle)</td><td style="text-align:right">$7</td><td style="text-align:right">$1</td></tr><tr><td>Load Balancer (ALB)</td><td style="text-align:right">$24</td><td style="text-align:right"></td></tr><tr><td><strong>Total Costs</strong></td><td style="text-align:right"><strong>$442</strong></td><td style="text-align:right"><strong>$650</strong></td></tr></tbody></table><p>In summary, when deciding between ECS + Fargate and App Runner, estimating the costs for the concrete scenario is necessary. App Runner is generally more cost-effective for small workloads with long idle periods.</p><h2 id="Networking"><a href="#Networking" class="headerlink" title="Networking"></a>Networking</h2><p>ECS + Fargate are first-class citizens in a VPC. Each task -1 or multiple containers- launches with an ENI (Elastic Network Interface) attached. Therefore, traffic from and to containers flows through the VPC. For example, a container can connect with an RDS Aurora database, as shown in the following figure.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/07/fargate-networking@730w.webp 730w, /images/2022/07/fargate-networking@730w2x.webp 1460w, /images/2022/07/fargate-networking@610w.webp 610w, /images/2022/07/fargate-networking@610w2x.webp 1220w, /images/2022/07/fargate-networking@450w.webp 450w, /images/2022/07/fargate-networking@450w2x.webp 900w, /images/2022/07/fargate-networking@330w.webp 330w, /images/2022/07/fargate-networking@330w2x.webp 660w, /images/2022/07/fargate-networking@545w.webp 545w, /images/2022/07/fargate-networking@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/07/fargate-networking@730w.png 730w, /images/2022/07/fargate-networking@730w2x.png 1460w, /images/2022/07/fargate-networking@610w.png 610w, /images/2022/07/fargate-networking@610w2x.png 1220w, /images/2022/07/fargate-networking@450w.png 450w, /images/2022/07/fargate-networking@450w2x.png 900w, /images/2022/07/fargate-networking@330w.png 330w, /images/2022/07/fargate-networking@330w2x.png 660w, /images/2022/07/fargate-networking@545w.png 545w, /images/2022/07/fargate-networking@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/07/fargate-networking.png" alt="By default, App Runner is only able to access publicly available endpoints" title="By default, App Runner is only able to access publicly available endpoints"></picture></p><p>In contrast, App Runner provides a public HTTPS endpoint for each service. It is also worth noting that by default, an App Runner service is running in a public VPC owned by AWS. So it is possible to connect your container to public endpoints such as S3, DynamoDB, or 3rd party APIs. But it is impossible to connect to an RDS database in your VPC.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/apprunner-network-01@730w.webp 730w, /images/2022/06/apprunner-network-01@730w2x.webp 1460w, /images/2022/06/apprunner-network-01@610w.webp 610w, /images/2022/06/apprunner-network-01@610w2x.webp 1220w, /images/2022/06/apprunner-network-01@450w.webp 450w, /images/2022/06/apprunner-network-01@450w2x.webp 900w, /images/2022/06/apprunner-network-01@330w.webp 330w, /images/2022/06/apprunner-network-01@330w2x.webp 660w, /images/2022/06/apprunner-network-01@545w.webp 545w, /images/2022/06/apprunner-network-01@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/apprunner-network-01@730w.png 730w, /images/2022/06/apprunner-network-01@730w2x.png 1460w, /images/2022/06/apprunner-network-01@610w.png 610w, /images/2022/06/apprunner-network-01@610w2x.png 1220w, /images/2022/06/apprunner-network-01@450w.png 450w, /images/2022/06/apprunner-network-01@450w2x.png 900w, /images/2022/06/apprunner-network-01@330w.png 330w, /images/2022/06/apprunner-network-01@330w2x.png 660w, /images/2022/06/apprunner-network-01@545w.png 545w, /images/2022/06/apprunner-network-01@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/apprunner-network-01.png" alt="By default, App Runner is only able to access publicly available endpoints" title="By default, App Runner is only able to access publicly available endpoints"></picture></p><p>However, AWS recently announced the possibility of connecting your App Runner service with your VPC. It works a lot like VPC connectivity for Lambda. App Runner creates an ENI within every subnet you choose for the service.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/apprunner-network-02@730w.webp 730w, /images/2022/06/apprunner-network-02@730w2x.webp 1460w, /images/2022/06/apprunner-network-02@610w.webp 610w, /images/2022/06/apprunner-network-02@610w2x.webp 1220w, /images/2022/06/apprunner-network-02@450w.webp 450w, /images/2022/06/apprunner-network-02@450w2x.webp 900w, /images/2022/06/apprunner-network-02@330w.webp 330w, /images/2022/06/apprunner-network-02@330w2x.webp 660w, /images/2022/06/apprunner-network-02@545w.webp 545w, /images/2022/06/apprunner-network-02@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/apprunner-network-02@730w.png 730w, /images/2022/06/apprunner-network-02@730w2x.png 1460w, /images/2022/06/apprunner-network-02@610w.png 610w, /images/2022/06/apprunner-network-02@610w2x.png 1220w, /images/2022/06/apprunner-network-02@450w.png 450w, /images/2022/06/apprunner-network-02@450w2x.png 900w, /images/2022/06/apprunner-network-02@330w.png 330w, /images/2022/06/apprunner-network-02@330w2x.png 660w, /images/2022/06/apprunner-network-02@545w.png 545w, /images/2022/06/apprunner-network-02@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/apprunner-network-02.png" alt="A VPC connection enables the container to access resources within a VPC" title="A VPC connection enables the container to access resources within a VPC"></picture></p><p>Besides that, a VPC endpoint is available for the App Runner service. Using such an endpoint gives you the chance to connect to App Runner without letting traffic flow through the Internet. However, be warned that the App Runner service is still accessible from the Internet.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/apprunner-network-03@730w.webp 730w, /images/2022/06/apprunner-network-03@730w2x.webp 1460w, /images/2022/06/apprunner-network-03@610w.webp 610w, /images/2022/06/apprunner-network-03@610w2x.webp 1220w, /images/2022/06/apprunner-network-03@450w.webp 450w, /images/2022/06/apprunner-network-03@450w2x.webp 900w, /images/2022/06/apprunner-network-03@330w.webp 330w, /images/2022/06/apprunner-network-03@330w2x.webp 660w, /images/2022/06/apprunner-network-03@545w.webp 545w, /images/2022/06/apprunner-network-03@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/apprunner-network-03@730w.png 730w, /images/2022/06/apprunner-network-03@730w2x.png 1460w, /images/2022/06/apprunner-network-03@610w.png 610w, /images/2022/06/apprunner-network-03@610w2x.png 1220w, /images/2022/06/apprunner-network-03@450w.png 450w, /images/2022/06/apprunner-network-03@450w2x.png 900w, /images/2022/06/apprunner-network-03@330w.png 330w, /images/2022/06/apprunner-network-03@330w2x.png 660w, /images/2022/06/apprunner-network-03@545w.png 545w, /images/2022/06/apprunner-network-03@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/apprunner-network-03.png" alt="VPC Endpoints make sure traffic to App Runner does not flow through the Internet" title="VPC Endpoints make sure traffic to App Runner does not flow through the Internet"></picture></p><p>This brings me to a feature that App Runner is missing. There is no way to control incoming traffic. There is no way to restrict access to the public <code>ServiceUrl</code>. The ability to create private App Runner services only accessible from a VPC, for example, by using PrivateLink, would enable many use cases. Also, support for AWS WAF is a must.</p><h2 id="Service-Maturity"><a href="#Service-Maturity" class="headerlink" title="Service Maturity"></a>Service Maturity</h2><p>AWS announced App Runner in May 2021. Therefore, it is no wonder that some important functions are still missing. When reviewing the service recently, I’ve calculated a service maturity score of 5.7 (on a scale from 0 to 10, where 10 is a very mature service). Check out my <a href="https://cloudonaut.io/review-apprunner-simply-containers-on-aws/">review of App Runner</a> if you are interested in the details. In summary, I’d not recommend App Runner for business-critical workloads at the moment.</p><p>Compared to App Runner, ECS and Fargate have been around for years and are ready for production.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>App Runner convinces through simplicity. Also, App Runner’s capability to scale down to 0 is a crucial advantage for small workloads. On the other hand, ECS + Fargate is a production-ready and flexible solution. I’ve summarized more details in the following table.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th>ECS + Fargate</th><th>App Runner</th></tr></thead><tbody><tr><td>Service Maturity</td><td>⭐️⭐️⭐️⭐️⭐️</td><td>⭐️⭐️</td></tr><tr><td>Simplicity</td><td>⭐️⭐️</td><td>⭐️⭐️⭐️⭐️⭐️</td></tr><tr><td>Use Cases</td><td>Request/Response, Batch Processing, ...</td><td>Request/Response</td></tr><tr><td>Scaling</td><td>Based on CloudWatch metric (CPU, network I/O, requests, ...)</td><td>Based on requests per container</td></tr><tr><td>Sources</td><td>Any container image registry</td><td>Private/Public ECR, GitHub</td></tr><tr><td>Deployment Unit</td><td>Container Image</td><td>Container Image, Python/Node.js/Java project</td></tr><tr><td>Container Configurations</td><td>0.25 to 4 vCPU, 0.5 to 30 GB memory</td><td>1 to 2 vCPU, 2 to 4 GB memory</td></tr><tr><td>WAF + Private Endpoint</td><td>✅</td><td>❌</td></tr><tr><td>VPC Integration</td><td>✅</td><td>✅</td></tr><tr><td>Costs (Small Workloads)</td><td>💰💰</td><td>💰</td></tr><tr><td>Costs (Large Workload)</td><td>💰💰💰</td><td>💰💰💰💰💰💰</td></tr><tr><td>Scale down to 0</td><td>❌</td><td>✅</td></tr><tr><td>SLA</td><td>✅</td><td>❌</td></tr></tbody></table>]]>
      </content:encoded>
    </item>
    <item>
      <title>Authentication at the edge with Lambda@Edge and Cognito</title>
      <link>https://cloudonaut.io/authentication-at-the-edge-with-lambda-edge-cognito/</link>
      <description>
        <![CDATA[<p>For many years, we used a hosting partner for serving the Rapid Docker on AWS Video Course. When someone bought the video course, we crea]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/cloudfront/">cloudfront</category>
      <category domain="https://cloudonaut.io/tag/cognito/">cognito</category>
      <category domain="https://cloudonaut.io/tag/lambda-at-edge/">lambda-at-edge</category>
      <guid isPermaLink="true">https://cloudonaut.io/authentication-at-the-edge-with-lambda-edge-cognito/</guid>
      <pubDate>Wed, 13 Jul 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>For many years, we used a hosting partner for serving the Rapid Docker on AWS Video Course. When someone bought the video course, we created a user account with our partner. The hosting partner provided a website to watch the videos and a login form. For many reasons, we migrated the video course to a new home -our home- cloudonaut.io. In this article, I outline the architecture, the user flow, and go through code snippets to implement user authentication at the edge with Lambda@Edge functions and a Cognito user pool.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/07/authn@730w.webp 730w, /images/2022/07/authn@730w2x.webp 1460w, /images/2022/07/authn@610w.webp 610w, /images/2022/07/authn@610w2x.webp 1220w, /images/2022/07/authn@450w.webp 450w, /images/2022/07/authn@450w2x.webp 900w, /images/2022/07/authn@330w.webp 330w, /images/2022/07/authn@330w2x.webp 660w, /images/2022/07/authn@545w.webp 545w, /images/2022/07/authn@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/07/authn@730w.jpg 730w, /images/2022/07/authn@730w2x.jpg 1460w, /images/2022/07/authn@610w.jpg 610w, /images/2022/07/authn@610w2x.jpg 1220w, /images/2022/07/authn@450w.jpg 450w, /images/2022/07/authn@450w2x.jpg 900w, /images/2022/07/authn@330w.jpg 330w, /images/2022/07/authn@330w2x.jpg 660w, /images/2022/07/authn@545w.jpg 545w, /images/2022/07/authn@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/07/authn.jpg" alt="Authentication at the edge" title="Authentication at the edge"></picture></p><p>This post is not about video hosting on AWS. We cover <a href="/video-hosting-on-aws/">video hosting on AWS in this post</a>.</p><h2 id="Architecture"><a href="#Architecture" class="headerlink" title="Architecture"></a>Architecture</h2><p>From a high-level perspective, the following components are used:</p><ul><li><strong>Cognito user pool</strong> stores users, hosts login UI, and issues JWT tokens.</li><li><strong>Lambda@Edge functions</strong> check if the request contains a cookie with a valid JWT token and implement a tiny backend to implement the OAuth 2.0 Authorization Code Flow.</li><li><strong>CloudFront distribution</strong> delivers the content to the end-users and triggers Lambda@Edge functions.</li><li><strong>S3 bucket</strong> stores the content served by CloudFront.</li></ul><p>The following figure shows the high-level architecture.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/07/authentication-lambda-edge-cognito-architecture@730w.webp 730w, /images/2022/07/authentication-lambda-edge-cognito-architecture@730w2x.webp 1460w, /images/2022/07/authentication-lambda-edge-cognito-architecture@610w.webp 610w, /images/2022/07/authentication-lambda-edge-cognito-architecture@610w2x.webp 1220w, /images/2022/07/authentication-lambda-edge-cognito-architecture@450w.webp 450w, /images/2022/07/authentication-lambda-edge-cognito-architecture@450w2x.webp 900w, /images/2022/07/authentication-lambda-edge-cognito-architecture@330w.webp 330w, /images/2022/07/authentication-lambda-edge-cognito-architecture@330w2x.webp 660w, /images/2022/07/authentication-lambda-edge-cognito-architecture@545w.webp 545w, /images/2022/07/authentication-lambda-edge-cognito-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/07/authentication-lambda-edge-cognito-architecture@730w.png 730w, /images/2022/07/authentication-lambda-edge-cognito-architecture@730w2x.png 1460w, /images/2022/07/authentication-lambda-edge-cognito-architecture@610w.png 610w, /images/2022/07/authentication-lambda-edge-cognito-architecture@610w2x.png 1220w, /images/2022/07/authentication-lambda-edge-cognito-architecture@450w.png 450w, /images/2022/07/authentication-lambda-edge-cognito-architecture@450w2x.png 900w, /images/2022/07/authentication-lambda-edge-cognito-architecture@330w.png 330w, /images/2022/07/authentication-lambda-edge-cognito-architecture@330w2x.png 660w, /images/2022/07/authentication-lambda-edge-cognito-architecture@545w.png 545w, /images/2022/07/authentication-lambda-edge-cognito-architecture@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/07/authentication-lambda-edge-cognito-architecture.png" alt="Architecture: Authentication at the edge with Lambda@Edge and Cognito" title="Architecture: Authentication at the edge with Lambda@Edge and Cognito"></picture></p><p>Let’s continue with the user perspective.</p><h2 id="User-flow"><a href="#User-flow" class="headerlink" title="User flow"></a>User flow</h2><p>The user starts the journey by visiting a protected web page. The following figure shows what happens next.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/07/authentication-lambda-edge-cognito-flow@730w.webp 730w, /images/2022/07/authentication-lambda-edge-cognito-flow@730w2x.webp 1460w, /images/2022/07/authentication-lambda-edge-cognito-flow@610w.webp 610w, /images/2022/07/authentication-lambda-edge-cognito-flow@610w2x.webp 1220w, /images/2022/07/authentication-lambda-edge-cognito-flow@450w.webp 450w, /images/2022/07/authentication-lambda-edge-cognito-flow@450w2x.webp 900w, /images/2022/07/authentication-lambda-edge-cognito-flow@330w.webp 330w, /images/2022/07/authentication-lambda-edge-cognito-flow@330w2x.webp 660w, /images/2022/07/authentication-lambda-edge-cognito-flow@545w.webp 545w, /images/2022/07/authentication-lambda-edge-cognito-flow@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/07/authentication-lambda-edge-cognito-flow@730w.png 730w, /images/2022/07/authentication-lambda-edge-cognito-flow@730w2x.png 1460w, /images/2022/07/authentication-lambda-edge-cognito-flow@610w.png 610w, /images/2022/07/authentication-lambda-edge-cognito-flow@610w2x.png 1220w, /images/2022/07/authentication-lambda-edge-cognito-flow@450w.png 450w, /images/2022/07/authentication-lambda-edge-cognito-flow@450w2x.png 900w, /images/2022/07/authentication-lambda-edge-cognito-flow@330w.png 330w, /images/2022/07/authentication-lambda-edge-cognito-flow@330w2x.png 660w, /images/2022/07/authentication-lambda-edge-cognito-flow@545w.png 545w, /images/2022/07/authentication-lambda-edge-cognito-flow@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/07/authentication-lambda-edge-cognito-flow.png" alt="User flow: Authentication at the edge with Lambda@Edge and Cognito" title="User flow: Authentication at the edge with Lambda@Edge and Cognito"></picture></p><p>The following steps are executed:</p><ol><li>The user visits a protected page (e.g., <a href="https://cloudonaut.io/rapid-docker-on-aws/video-course/ch00-01.html">https://cloudonaut.io/rapid-docker-on-aws/video-course/ch00-01.html</a>) hosted on CloudFront.</li><li>CloudFront invokes the viewer request Lambda@Edge function.</li><li>The function inspects the cookie header, extracts the cookie named <code>token</code>, and verifies the value to check if it is a valid JWT token issued by Cognito.</li><li>Let’s assume no cookie is present. The Lambda@edge function generates an HTTP 302 response to redirect to the Cognito hosted UI.</li><li>The user’s browser follows the redirect and loads the Cognito hosted UI with a login screen.</li><li>Once the user enters a valid username and password, Cognito returns an HTTP 302 response to redirect to the cloudonaut.io backend (<code>https://cloudonaut.io/api/cognito/login/</code>).</li><li>The user’s browser follows the redirect and loads the backend. In this case, we implement the backend with an origin request Lambda@Edge function.</li><li>The function exchanges the received authorization code (query parameter <code>code</code>) with an access token (in JWT format). An HTTP 302 response is generated with the <code>Set-Cookie</code> header.</li><li>The user’s browser stores the <code>token</code> cookie and follows the redirect with the <code>Cookie</code> header containing the access token. The user can access the protected page.</li></ol><p>For a better understanding, let’s dive into Lambda@Edge.</p><h2 id="How-Lambda-Edge-works"><a href="#How-Lambda-Edge-works" class="headerlink" title="How Lambda@Edge works"></a>How Lambda@Edge works</h2><p>When CloudFront receives a request, it can invoke the so-called viewer request Lambda@Edge function. You can inspect the HTTP headers at this point and generate HTTP responses (such as a 302 redirect). The viewer request function execution is limited to 5 seconds and 128 MB of memory. The function’s code and libraries must fit into a 1 MB zip file.</p><blockquote><p><strong>Warning</strong><br>The snippets hardcode the region to eu-west-1. If your region is different, replace all occurrences of eu-west-1!</p></blockquote><p>The following snippet shows the implementation to check if a cookie <code>token</code> is part of the HTTP header and if the value is a signed JWT issued from Cognito.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// required libraries</span></span><br><span class="line"><span class="keyword">const</span> cookie = <span class="built_in">require</span>(<span class="string">&#x27;cookie&#x27;</span>); <span class="comment">// I use version 0.5.0</span></span><br><span class="line"><span class="keyword">const</span> jose = <span class="built_in">require</span>(<span class="string">&#x27;jose&#x27;</span>); <span class="comment">// I use version 4.8.3</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// TODO fill config values with outputs from CloudFormation shown later in the article</span></span><br><span class="line"><span class="keyword">const</span> config = &#123;</span><br><span class="line">  <span class="attr">cognitoUserPoolId</span>: <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">  <span class="attr">cognitoClientId</span>: <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">  <span class="attr">cognitoDomainName</span>: <span class="string">&#x27;&#x27;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// download jwks.json from https://cognito-idp.eu-west-1.amazonaws.com/$&#123;config.cognitoUserPoolId&#125;/.well-known/jwks.json</span></span><br><span class="line"><span class="comment">// according to AWS support, the keys are not rotated so you can do this once and include the file to avoid timeout issues</span></span><br><span class="line"><span class="keyword">const</span> jwks = jose.<span class="title function_">createLocalJWKSet</span>(<span class="built_in">require</span>(<span class="string">&#x27;./jwks.json&#x27;</span>)); </span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">verifyToken</span>(<span class="params">cf</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (cf.<span class="property">request</span>.<span class="property">headers</span>.<span class="property">cookie</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> cookies = cookie.<span class="title function_">parse</span>(cf.<span class="property">request</span>.<span class="property">headers</span>.<span class="property">cookie</span>[<span class="number">0</span>].<span class="property">value</span>);</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> &#123; payload &#125; = <span class="keyword">await</span> jose.<span class="title function_">jwtVerify</span>(cookies.<span class="property">token</span>, jwks, &#123;</span><br><span class="line">        <span class="attr">issuer</span>: <span class="string">`https://cognito-idp.eu-west-1.amazonaws.com/<span class="subst">$&#123;config.cognitoUserPoolId&#125;</span>`</span></span><br><span class="line">      &#125;);</span><br><span class="line">      <span class="keyword">if</span> (payload.<span class="property">client_id</span> === config.<span class="property">cognitoClientId</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span>(err) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`token error: <span class="subst">$&#123;err.name&#125;</span> <span class="subst">$&#123;err.message&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">handler</span> = <span class="keyword">async</span> <span class="keyword">function</span>(<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> cf = event.<span class="property">Records</span>[<span class="number">0</span>].<span class="property">cf</span>;</span><br><span class="line">  <span class="comment">// check if path is protected and requires the user to be logged in</span></span><br><span class="line">  <span class="keyword">if</span> (</span><br><span class="line">    cf.<span class="property">request</span>.<span class="property">uri</span>.<span class="title function_">startsWith</span>(<span class="string">&#x27;/rapid-docker-on-aws/video-course/&#x27;</span>) ||</span><br><span class="line">    cf.<span class="property">request</span>.<span class="property">uri</span>.<span class="title function_">startsWith</span>(<span class="string">&#x27;/media/cloudonaut/rapid-docker-on-aws/&#x27;</span>)</span><br><span class="line">  ) &#123;</span><br><span class="line">    <span class="keyword">const</span> valid = <span class="keyword">await</span> <span class="title function_">verifyToken</span>(cf, <span class="string">&#x27;rapid-docker-on-aws-video-course&#x27;</span>);</span><br><span class="line">    <span class="keyword">if</span> (valid === <span class="literal">true</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> cf.<span class="property">request</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">status</span>: <span class="string">&#x27;302&#x27;</span>,</span><br><span class="line">        <span class="attr">statusDescription</span>: <span class="string">&#x27;Found&#x27;</span>,</span><br><span class="line">        <span class="attr">headers</span>: &#123;</span><br><span class="line">          <span class="attr">location</span>: [&#123; <span class="comment">// instructs browser to redirect after receiving the response</span></span><br><span class="line">            <span class="attr">key</span>: <span class="string">&#x27;Location&#x27;</span>,</span><br><span class="line">            <span class="attr">value</span>: <span class="string">`https://<span class="subst">$&#123;config.cognitoDomainName&#125;</span>.auth.eu-west-1.amazoncognito.com/login?client_id=<span class="subst">$&#123;config.cognitoClientId&#125;</span>&amp;response_type=code&amp;scope=email+openid&amp;redirect_uri=https%3A%2F%2Fcloudonaut.io%2Fapi%2Fcognito%2Flogin%2F`</span>,</span><br><span class="line">          &#125;]</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// do nothing: CloudFront continues as usual</span></span><br><span class="line">  <span class="keyword">return</span> cf.<span class="property">request</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>When the viewer request function does not generate a response, the request is passed to the CloudFront origin (e.g., an S3 bucket).</p><p>When CloudFront can not serve the request from the cache, the origin request Lambda@Edge function is invoked just before the request to the origin is made. The main difference is that this function can take up to 30 seconds, use as much memory as Lambda offers, and the uploaded code archive can be up to 50 MB.</p><p>In our case, we use a Lambda@Edge function to implement a tiny backend. The backend exchanges an authorization code with an access token and ensures that the response is not cachable by setting the <code>Cache-Control</code> header to <code>no-cache</code>.</p><blockquote><p>Feel free to implement this part using an API Gateway or similar.</p></blockquote><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// required libraries</span></span><br><span class="line"><span class="keyword">const</span> querystring = <span class="built_in">require</span>(<span class="string">&#x27;querystring&#x27;</span>); <span class="comment">// included in Node.js</span></span><br><span class="line"><span class="keyword">const</span> cookie = <span class="built_in">require</span>(<span class="string">&#x27;cookie&#x27;</span>); <span class="comment">// I use version 0.5.0</span></span><br><span class="line"><span class="keyword">const</span> axios = <span class="built_in">require</span>(<span class="string">&#x27;axios&#x27;</span>); <span class="comment">// I use version 0.27.2</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// TODO fill config values with outputs from CloudFormation shown later in the article</span></span><br><span class="line"><span class="keyword">const</span> config = &#123;</span><br><span class="line">  <span class="attr">cognitoClientId</span>: <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">  <span class="attr">cognitoClientSecret</span>: <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">  <span class="attr">cognitoDomainName</span>: <span class="string">&#x27;&#x27;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">handler</span> = <span class="keyword">async</span> <span class="keyword">function</span>(<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> cf = event.<span class="property">Records</span>[<span class="number">0</span>].<span class="property">cf</span>;</span><br><span class="line">  <span class="keyword">if</span> (cf.<span class="property">request</span>.<span class="property">uri</span>.<span class="title function_">startsWith</span>(<span class="string">&#x27;/api/cognito/login/&#x27;</span>)) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123;code&#125; = querystring.<span class="title function_">parse</span>(cf.<span class="property">request</span>.<span class="property">querystring</span>);</span><br><span class="line">    <span class="keyword">const</span> res = <span class="keyword">await</span> <span class="title function_">axios</span>(&#123;</span><br><span class="line">      <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span><br><span class="line">      <span class="attr">headers</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;content-type&#x27;</span>: <span class="string">&#x27;application/x-www-form-urlencoded&#x27;</span>,</span><br><span class="line">        <span class="attr">authorization</span>: <span class="string">&#x27;Basic &#x27;</span> + <span class="title class_">Buffer</span>.<span class="title function_">from</span>(config.<span class="property">cognitoClientId</span> + <span class="string">&#x27;:&#x27;</span> + config.<span class="property">cognitoClientSecret</span>).<span class="title function_">toString</span>(<span class="string">&#x27;base64&#x27;</span>)</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">data</span>: querystring.<span class="title function_">stringify</span>(&#123;</span><br><span class="line">        <span class="attr">grant_type</span>: <span class="string">&#x27;authorization_code&#x27;</span>,</span><br><span class="line">        <span class="attr">redirect_uri</span>: <span class="string">&#x27;https://cloudonaut.io/api/cognito/login/&#x27;</span>,</span><br><span class="line">        code</span><br><span class="line">      &#125;),</span><br><span class="line">      <span class="attr">url</span>: <span class="string">`https://<span class="subst">$&#123;config.cognitoDomainName&#125;</span>.auth.eu-west-1.amazoncognito.com/oauth2/token`</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">if</span> (res.<span class="property">status</span> === <span class="number">200</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> setCookieValue = cookie.<span class="title function_">serialize</span>(<span class="string">&#x27;token&#x27;</span>, res.<span class="property">data</span>.<span class="property">access_token</span>, &#123;</span><br><span class="line">        <span class="attr">maxAge</span>: res.<span class="property">data</span>.<span class="property">expires_in</span>,</span><br><span class="line">        <span class="attr">path</span>: <span class="string">&#x27;/&#x27;</span>,</span><br><span class="line">        <span class="attr">secure</span>: <span class="literal">true</span></span><br><span class="line">      &#125;);</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">status</span>: <span class="string">&#x27;302&#x27;</span>,</span><br><span class="line">        <span class="attr">headers</span>: &#123;</span><br><span class="line">          <span class="attr">location</span>: [&#123; <span class="comment">// instructs browser to redirect after receiving the response</span></span><br><span class="line">            <span class="attr">key</span>: <span class="string">&#x27;Location&#x27;</span>,</span><br><span class="line">            <span class="attr">value</span>: <span class="string">&#x27;/rapid-docker-on-aws/video-course/ch00-01.html&#x27;</span></span><br><span class="line">          &#125;],</span><br><span class="line">          <span class="string">&#x27;set-cookie&#x27;</span>: [&#123; <span class="comment">// instructs browser to store a cookie</span></span><br><span class="line">            <span class="attr">key</span>: <span class="string">&#x27;Set-Cookie&#x27;</span>,</span><br><span class="line">            <span class="attr">value</span>: setCookieValue</span><br><span class="line">          &#125;],</span><br><span class="line">          <span class="string">&#x27;cache-control&#x27;</span>: [&#123; <span class="comment">// ensures that CloudFront does not cache the response</span></span><br><span class="line">            <span class="attr">key</span>: <span class="string">&#x27;Cache-Control&#x27;</span>,</span><br><span class="line">            <span class="attr">value</span>: <span class="string">&#x27;no-cache&#x27;</span></span><br><span class="line">          &#125;]</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;unexpected status code: &#x27;</span> + res.<span class="property">status</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// do nothing: CloudFront continues as usual</span></span><br><span class="line">  <span class="keyword">return</span> cf.<span class="property">request</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="Cognito-Infrastructure"><a href="#Cognito-Infrastructure" class="headerlink" title="Cognito Infrastructure"></a>Cognito Infrastructure</h2><p>The following CloudFormation template describes the Cognito user pool, client, and domain infrastructure needed.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">UserPool:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Cognito::UserPool&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AccountRecoverySetting:</span></span><br><span class="line">        <span class="attr">RecoveryMechanisms:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">verified_email</span></span><br><span class="line">          <span class="attr">Priority:</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">AdminCreateUserConfig:</span></span><br><span class="line">        <span class="attr">AllowAdminCreateUserOnly:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">AliasAttributes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">preferred_username</span></span><br><span class="line">      <span class="attr">AutoVerifiedAttributes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">email</span></span><br><span class="line">      <span class="attr">EnabledMfas:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">SOFTWARE_TOKEN_MFA</span></span><br><span class="line">      <span class="attr">MfaConfiguration:</span> <span class="string">OPTIONAL</span></span><br><span class="line">      <span class="attr">UserPoolName:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line">  <span class="attr">UserPoolDomain:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Cognito::UserPoolDomain&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Domain:</span> <span class="string">&#x27;cloudonaut-io&#x27;</span></span><br><span class="line">      <span class="attr">UserPoolId:</span> <span class="type">!Ref</span> <span class="string">UserPool</span></span><br><span class="line">  <span class="attr">ClientWebsite:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Cognito::UserPoolClient&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AccessTokenValidity:</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">AllowedOAuthFlows:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">code</span></span><br><span class="line">      <span class="attr">AllowedOAuthFlowsUserPoolClient:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">AllowedOAuthScopes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">phone</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">email</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">openid</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">profile</span></span><br><span class="line">      <span class="attr">CallbackURLs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;https://cloudonaut.io/api/cognito/login/&#x27;</span></span><br><span class="line">      <span class="attr">ClientName:</span> <span class="string">website</span></span><br><span class="line">      <span class="attr">DefaultRedirectURI:</span> <span class="string">&#x27;https://cloudonaut.io/api/cognito/login/&#x27;</span></span><br><span class="line">      <span class="attr">ExplicitAuthFlows:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ALLOW_USER_SRP_AUTH</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">ALLOW_REFRESH_TOKEN_AUTH</span> <span class="comment"># always on</span></span><br><span class="line">      <span class="attr">GenerateSecret:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">IdTokenValidity:</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">LogoutURLs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;https://cloudonaut.io/api/cognito/logout/&#x27;</span></span><br><span class="line">      <span class="attr">PreventUserExistenceErrors:</span> <span class="string">ENABLED</span></span><br><span class="line">      <span class="attr">RefreshTokenValidity:</span> <span class="number">30</span></span><br><span class="line">      <span class="attr">SupportedIdentityProviders:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">COGNITO</span></span><br><span class="line">      <span class="attr">TokenValidityUnits:</span></span><br><span class="line">        <span class="attr">AccessToken:</span> <span class="string">days</span></span><br><span class="line">        <span class="attr">IdToken:</span> <span class="string">days</span></span><br><span class="line">        <span class="attr">RefreshToken:</span> <span class="string">days</span></span><br><span class="line">      <span class="attr">UserPoolId:</span> <span class="type">!Ref</span> <span class="string">UserPool</span></span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">CognitoUserPoolId:</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">UserPool</span></span><br><span class="line">  <span class="attr">CognitoClientId:</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">ClientWebsite</span></span><br><span class="line">  <span class="attr">CognitoDomainName:</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">UserPoolDomain</span></span><br></pre></td></tr></table></figure><p>To get the client secret, we use the following bash snippet in our deployment pipeline:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">COGNITO_STACK_NAME=<span class="string">&quot;&quot;</span> <span class="comment"># TODO fill with your stack name</span></span><br><span class="line">USER_POOL_ID=<span class="string">&quot;<span class="subst">$(aws cloudformation describe-stacks --stack-name $COGNITO_STACK_NAME --query <span class="string">&quot;Stacks[0].Outputs[?OutputKey==&#x27;UserPoolId&#x27;].OutputValue&quot;</span> --output text)</span>&quot;</span></span><br><span class="line">CLIENT_ID=<span class="string">&quot;<span class="subst">$(aws cloudformation describe-stacks --stack-name $COGNITO_STACK_NAME --query <span class="string">&quot;Stacks[0].Outputs[?OutputKey==&#x27;Value&#x27;].OutputValue&quot;</span> --output text)</span>&quot;</span></span><br><span class="line">CLIENT_SECRET=<span class="string">&quot;<span class="subst">$(aws cognito-idp describe-user-pool-client --user-pool-id <span class="string">&quot;<span class="variable">$&#123;USER_POOL_ID&#125;</span>&quot;</span> --client-id <span class="string">&quot;<span class="variable">$&#123;CLIENT_ID&#125;</span>&quot;</span> --query UserPoolClient.ClientSecret --output text)</span>&quot;</span></span><br></pre></td></tr></table></figure><h2 id="Lambda-Edge-infrastructure"><a href="#Lambda-Edge-infrastructure" class="headerlink" title="Lambda@Edge infrastructure"></a>Lambda@Edge infrastructure</h2><p>I shared the Lambda@edge code with you already. The missing piece is how to deploy the functions using CloudFormation. To avoid using a JavaScript bundler but still include only the needed libraries, I created two folders (<code>viewer-request-src</code> and <code>origin-request-src</code>) to store the Lambda@Edge function code (<code>lambda.js</code>) together with a <code>package.json</code>.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&#x27;Static Website: Custom image optimization and routing&#x27;</span></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">LogsRetentionInDays:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;Specifies the number of days you want to retain log events in the specified log group.&#x27;</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">Number</span></span><br><span class="line">    <span class="attr">Default:</span> <span class="number">14</span></span><br><span class="line">    <span class="attr">AllowedValues:</span> [<span class="number">1</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">7</span>, <span class="number">14</span>, <span class="number">30</span>, <span class="number">60</span>, <span class="number">90</span>, <span class="number">120</span>, <span class="number">150</span>, <span class="number">180</span>, <span class="number">365</span>, <span class="number">400</span>, <span class="number">545</span>, <span class="number">731</span>, <span class="number">1827</span>, <span class="number">3653</span>]</span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">ViewerRequestRole:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">Service:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;lambda.amazonaws.com&#x27;</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;edgelambda.amazonaws.com&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">  <span class="attr">ViewerRequestLambdaPolicy:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Policy&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogStream&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;ViewerRequestLogGroup.Arn&#x27;</span></span><br><span class="line">      <span class="attr">PolicyName:</span> <span class="string">lambda</span></span><br><span class="line">      <span class="attr">Roles:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">ViewerRequestRole</span></span><br><span class="line">  <span class="attr">ViewerRequestLambdaEdgePolicy:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Policy&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;logs:CreateLogGroup&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:logs:*:$&#123;AWS::AccountId&#125;:log-group:/aws/lambda/us-east-1.$&#123;ViewerRequestFunction&#125;:log-stream:&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogStream&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:logs:*:$&#123;AWS::AccountId&#125;:log-group:/aws/lambda/us-east-1.$&#123;ViewerRequestFunction&#125;:log-stream:*&#x27;</span></span><br><span class="line">      <span class="attr">PolicyName:</span> <span class="string">&#x27;lambda-edge&#x27;</span></span><br><span class="line">      <span class="attr">Roles:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">ViewerRequestRole</span></span><br><span class="line">  <span class="attr">ViewerRequestFunction:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Lambda::Function&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Code:</span> <span class="string">&#x27;./viewer-request-src/&#x27;</span> <span class="comment"># If you change the code, rename the logical id OriginRequestVersionVX to trigger a new version creation!</span></span><br><span class="line">      <span class="attr">Handler:</span> <span class="string">&#x27;lambda.handler&#x27;</span></span><br><span class="line">      <span class="attr">MemorySize:</span> <span class="number">128</span></span><br><span class="line">      <span class="attr">Role:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;ViewerRequestRole.Arn&#x27;</span></span><br><span class="line">      <span class="attr">Runtime:</span> <span class="string">&#x27;nodejs16.x&#x27;</span></span><br><span class="line">      <span class="attr">Timeout:</span> <span class="number">5</span></span><br><span class="line">  <span class="attr">ViewerRequestLogGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Logs::LogGroup&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">LogGroupName:</span> <span class="type">!Sub</span> <span class="string">&#x27;/aws/lambda/$&#123;ViewerRequestFunction&#125;&#x27;</span></span><br><span class="line">      <span class="attr">RetentionInDays:</span> <span class="type">!Ref</span> <span class="string">LogsRetentionInDays</span></span><br><span class="line">  <span class="attr">ViewerRequestVersionV1:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Lambda::Version&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">FunctionName:</span> <span class="type">!Ref</span> <span class="string">ViewerRequestFunction</span></span><br><span class="line">  <span class="attr">OriginRequestRole:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">Service:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;lambda.amazonaws.com&#x27;</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;edgelambda.amazonaws.com&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">  <span class="attr">OriginRequestLambdaPolicy:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Policy&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogStream&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;OriginRequestLogGroup.Arn&#x27;</span></span><br><span class="line">      <span class="attr">PolicyName:</span> <span class="string">lambda</span></span><br><span class="line">      <span class="attr">Roles:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">OriginRequestRole</span></span><br><span class="line">  <span class="attr">OriginRequestLambdaEdgePolicy:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Policy&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;logs:CreateLogGroup&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:logs:*:$&#123;AWS::AccountId&#125;:log-group:/aws/lambda/us-east-1.$&#123;OriginRequestFunction&#125;:log-stream:&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogStream&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:logs:*:$&#123;AWS::AccountId&#125;:log-group:/aws/lambda/us-east-1.$&#123;OriginRequestFunction&#125;:log-stream:*&#x27;</span></span><br><span class="line">      <span class="attr">PolicyName:</span> <span class="string">&#x27;lambda-edge&#x27;</span></span><br><span class="line">      <span class="attr">Roles:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">OriginRequestRole</span></span><br><span class="line">  <span class="attr">OriginRequestFunction:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Lambda::Function&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Code:</span> <span class="string">&#x27;./origin-request-src/&#x27;</span> <span class="comment"># If you change the code, rename the logical id OriginRequestVersionVX to trigger a new version creation!</span></span><br><span class="line">      <span class="attr">Handler:</span> <span class="string">&#x27;lambda.handler&#x27;</span></span><br><span class="line">      <span class="attr">MemorySize:</span> <span class="number">1536</span></span><br><span class="line">      <span class="attr">Role:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;OriginRequestRole.Arn&#x27;</span></span><br><span class="line">      <span class="attr">Runtime:</span> <span class="string">&#x27;nodejs16.x&#x27;</span></span><br><span class="line">      <span class="attr">Timeout:</span> <span class="number">30</span></span><br><span class="line">  <span class="attr">OriginRequestLogGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Logs::LogGroup&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">LogGroupName:</span> <span class="type">!Sub</span> <span class="string">&#x27;/aws/lambda/$&#123;OriginRequestFunction&#125;&#x27;</span></span><br><span class="line">      <span class="attr">RetentionInDays:</span> <span class="type">!Ref</span> <span class="string">LogsRetentionInDays</span></span><br><span class="line">  <span class="attr">OriginRequestVersionV1:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Lambda::Version&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">FunctionName:</span> <span class="type">!Ref</span> <span class="string">OriginRequestFunction</span></span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">ViewerRequestLambdaEdgeFunctionVersionARN:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;Version ARN of Lambda@Edge viewer request function.&#x27;</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">ViewerRequestVersionV1</span></span><br><span class="line">  <span class="attr">OriginRequestLambdaEdgeFunctionVersionARN:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;Version ARN of Lambda@Edge origin request function.&#x27;</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">OriginRequestVersionV1</span></span><br></pre></td></tr></table></figure><p>To deploy the stack in <code>us-east-1</code> (region required my Lambda@Edge), run:</p><figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">aws <span class="attr">--region</span> us-east-<span class="number">1</span> cloudformation package <span class="attr">--s3-bucket</span> YOUR_S3_ARTIFACT_BUCKET_NAME <span class="attr">--template-file</span> YOUR_TEMPLATE_FILE_NAME<span class="selector-class">.yaml</span> <span class="attr">--output-template-file</span> output<span class="selector-class">.yaml</span></span><br><span class="line">aws <span class="attr">--region</span> us-east-<span class="number">1</span> cloudformation deploy <span class="attr">--template-file</span> output<span class="selector-class">.yaml</span> <span class="attr">--stack-name</span> YOUR_STACK_NAME <span class="attr">--capabilities</span> CAPABILITY_IAM</span><br><span class="line">rm output.yaml</span><br></pre></td></tr></table></figure><p>The CloudFront infrastructure can be deployed with our <a href="https://templates.cloudonaut.io/en/stable/static-website/" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a>. Set the parameters <code>ViewerRequestLambdaEdgeFunctionVersionARN</code> and <code>OriginRequestLambdaEdgeFunctionVersionARN</code> to the values from the stack you deployed before containing the Lambda@Edge functions.</p><p>One modification to the <code>static-website.yaml</code> template is needed. Inside the <code>DefaultCacheBehavior</code>, set:</p><figure class="highlight nix"><table><tr><td class="code"><pre><span class="line"><span class="params">ForwardedValues:</span></span><br><span class="line">  <span class="params">Cookies:</span></span><br><span class="line">    <span class="params">Forward:</span> whitelist</span><br><span class="line">    <span class="params">WhitelistedNames:</span> [token]</span><br><span class="line">  <span class="params">QueryString:</span> <span class="literal">true</span></span><br><span class="line">  <span class="params">QueryStringCacheKeys:</span> [code]</span><br></pre></td></tr></table></figure><p>That’s it.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>You can add authentication to any website served by CloudFront by using Lambda@Edge. You can set up a Cognito user pool if you want to use your own identity provider. The described flow works with any other identity provider as long as you receive a JWT access token.</p><p>PS: You can even add simple authorization using Cognito user groups. If you add a Cognito user to a group, the group name will show up in the <code>cognito:groups</code> claim in the JWT access token.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Builder's Diary Vol. 1: Successful Cloud Migrations</title>
      <link>https://cloudonaut.io/builders-diary-vol1-successful-cloud-migrations/</link>
      <description>
        <![CDATA[<p>Get insights into the day-to-day challenges of builders. In this issue, Monika Oblonczek from our partner tecRacer talks about how cloud]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/builders-diary/">builders-diary</category>
      <guid isPermaLink="true">https://cloudonaut.io/builders-diary-vol1-successful-cloud-migrations/</guid>
      <pubDate>Wed, 29 Jun 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Get insights into the day-to-day challenges of builders. In this issue, Monika Oblonczek from our partner tecRacer talks about how cloud migrations succeed. Monika’s career is very exciting as she transitioned from legal linguistics into cloud computing. Besides that, Monika shares her knowledge about migration projects.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/diary@730w.webp 730w, /images/2022/06/diary@730w2x.webp 1460w, /images/2022/06/diary@610w.webp 610w, /images/2022/06/diary@610w2x.webp 1220w, /images/2022/06/diary@450w.webp 450w, /images/2022/06/diary@450w2x.webp 900w, /images/2022/06/diary@330w.webp 330w, /images/2022/06/diary@330w2x.webp 660w, /images/2022/06/diary@545w.webp 545w, /images/2022/06/diary@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/diary@730w.jpg 730w, /images/2022/06/diary@730w2x.jpg 1460w, /images/2022/06/diary@610w.jpg 610w, /images/2022/06/diary@610w2x.jpg 1220w, /images/2022/06/diary@450w.jpg 450w, /images/2022/06/diary@450w2x.jpg 900w, /images/2022/06/diary@330w.jpg 330w, /images/2022/06/diary@330w2x.jpg 660w, /images/2022/06/diary@545w.jpg 545w, /images/2022/06/diary@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/diary.jpg" alt="Builder's Diary " title="Builder's Diary "></picture></p><p>In case you prefer a video or podcast instead or reading, here you go.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=nQELqpxCxgE">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/nQELqpxCxgE" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/47-builders-diary-vol1-monika-oblonczek-tecracer/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><blockquote><p>Would you like to join Monika’s team to accompany cloud migrations of SMB and Enterprise customers? tecRacer is hiring Cloud Migration Specialists.</p></blockquote><h2 id="Who-are-you"><a href="#Who-are-you" class="headerlink" title="Who are you?"></a>Who are you?</h2><p>My name is Monika Oblonczek, and I enjoy living in Vienna, the capital of Austria. I’m working for tecRacer as an AWS Migrations Specialist.</p><h2 id="What-were-the-most-important-milestones-in-your-career"><a href="#What-were-the-most-important-milestones-in-your-career" class="headerlink" title="What were the most important milestones in your career?"></a>What were the most important milestones in your career?</h2><p>I studied European Legal Linguistics, which is the science of understanding the language of written law. After my studies, I got to know my current employer, tecRacer, at a job fair. And I decided to change careers to IT. tecRacer is a consulting, training, and managed service partner of AWS in Germany, Austria, and Switzerland.</p><h2 id="How-did-you-manage-to-make-a-transition-to-IT"><a href="#How-did-you-manage-to-make-a-transition-to-IT" class="headerlink" title="How did you manage to make a transition to IT?"></a>How did you manage to make a transition to IT?</h2><p>I found a supportive environment at tecRacer. In the first few weeks, I took several AWS training sessions, watched a lot of videos, and then prepared for my AWS certifications. Besides, that, my colleagues have been very supportive and even organized 1-on-1 workshops for me. After successfully overcoming this hurdle, my next goal was to become an AWS trainer. In this way, I have learned a lot about AWS quickly. And my study of linguistics helped me learn a new language: AWSish.</p><p>After a year, an exciting new opportunity has opened up. Since then, I have been responsible for migration projects. As an AWS Migrations Specialist, I plan and execute migrations of on-premises workloads to AWS.</p><blockquote><p>Would you like to join Monika’s team to accompany cloud migrations of SMB and Enterprise customers? tecRacer is hiring Cloud Migration Specialists.</p></blockquote><h2 id="What-does-your-day-to-day-work-look-like"><a href="#What-does-your-day-to-day-work-look-like" class="headerlink" title="What does your day-to-day work look like?"></a>What does your day-to-day work look like?</h2><p>I usually ride my bike to our office in Vienna. When I arrive at the office, I meet colleagues on-site, but at the same time, I am in contact with colleagues from Hanover, Duisburg, Frankfurt, Hamburg, Munich, Lucerne, and Lisbon.</p><p>Typically, I manage 2-3 migration projects at the same time. Doing so involves a lot of communication, planning, and analysis. I work at the interface between people and technology.</p><h2 id="What-does-the-typical-cloud-architecture-for-a-migration-project-look-like"><a href="#What-does-the-typical-cloud-architecture-for-a-migration-project-look-like" class="headerlink" title="What does the typical cloud architecture for a migration project look like?"></a>What does the typical cloud architecture for a migration project look like?</h2><p>It depends on the migration strategy. Let’s assume the common <em>replatforming strategy</em>. In contrast to the <em>rehosting strategy</em> -also known as lift&amp;shift- which moves virtual machines 1:1, the <em>replatforming strategy</em> allows minor modifications to leverage additional advantages of the cloud. Most importantly, I prefer replacing self-hosted database systems with a managed service provided by AWS. For example, the Relational Database Service (RDS) hosts Oracle, MySQL, PostgreSQL, or MS SQL databases. Sometimes even ElastiCache for Redis or Memcached and the Amazon Elasticsearch Service comes into play.</p><p>Apart from this, the following services form the foundation for cloud migration.</p><ul><li><em>VPC</em> to ensure network connectivity.</li><li><em>Direct Connect</em> and <em>Site-to-Site VPN</em> to connect with the corporate network.</li><li><em>IAM</em> to control access to AWS resources.</li><li><em>Security Hub</em>, <em>GuardDuty</em>, <em>CloudTrail</em>, and <em>Config</em> to monitor security and governance.</li><li><em>Backup</em> to manage backups centrally.</li><li><em>ELB</em> to distribute incoming requests among a fleet of virtual machines.</li><li><em>EC2</em> to execute the applications.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/lift-and-reshapre-architecture@730w.webp 730w, /images/2022/06/lift-and-reshapre-architecture@730w2x.webp 1460w, /images/2022/06/lift-and-reshapre-architecture@610w.webp 610w, /images/2022/06/lift-and-reshapre-architecture@610w2x.webp 1220w, /images/2022/06/lift-and-reshapre-architecture@450w.webp 450w, /images/2022/06/lift-and-reshapre-architecture@450w2x.webp 900w, /images/2022/06/lift-and-reshapre-architecture@330w.webp 330w, /images/2022/06/lift-and-reshapre-architecture@330w2x.webp 660w, /images/2022/06/lift-and-reshapre-architecture@545w.webp 545w, /images/2022/06/lift-and-reshapre-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/lift-and-reshapre-architecture@730w.png 730w, /images/2022/06/lift-and-reshapre-architecture@730w2x.png 1460w, /images/2022/06/lift-and-reshapre-architecture@610w.png 610w, /images/2022/06/lift-and-reshapre-architecture@610w2x.png 1220w, /images/2022/06/lift-and-reshapre-architecture@450w.png 450w, /images/2022/06/lift-and-reshapre-architecture@450w2x.png 900w, /images/2022/06/lift-and-reshapre-architecture@330w.png 330w, /images/2022/06/lift-and-reshapre-architecture@330w2x.png 660w, /images/2022/06/lift-and-reshapre-architecture@545w.png 545w, /images/2022/06/lift-and-reshapre-architecture@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/lift-and-reshapre-architecture.png" alt="A typical replatforming architecture" title="A typical replatforming architecture"></picture></p><h2 id="How-does-a-cloud-migration-project-work"><a href="#How-does-a-cloud-migration-project-work" class="headerlink" title="How does a cloud migration project work?"></a>How does a cloud migration project work?</h2><p>I follow AWS best practices and organize migration projects into three phases.</p><ol><li>Assess</li><li>Mobilize</li><li>Migrate &amp; Modernize</li></ol><p>The process schema that AWS developed helps me manage migration projects in a structured way.</p><h2 id="What-should-be-considered-during-assess-phase"><a href="#What-should-be-considered-during-assess-phase" class="headerlink" title="What should be considered during assess phase?"></a>What should be considered during assess phase?</h2><p>In assess phase, I work with the customer to develop an understanding of the upcoming migration. The phase starts with Migration Immersion Day, a workshop discussing strategy, process, communication, and costs.</p><p>Another aspect of assess phase is to create an inventory of servers and applications to define the scope of the migration. Part of this is to answer the questions:</p><ul><li>Is the server&#x2F;application still needed?</li><li>Is it advisable to migrate the server&#x2F;application from a technical point of view?</li><li>What are the requirements of the application that need to be considered when migrating to the cloud?</li></ul><p>The result of creating the inventory is often surprising. For example, one customer decided during the phase to migrate only 200 servers out of 600. The remaining 400 servers were no longer needed and could be decommissioned.</p><h2 id="What-are-the-goals-in-mobilize-phase"><a href="#What-are-the-goals-in-mobilize-phase" class="headerlink" title="What are the goals in mobilize phase?"></a>What are the goals in mobilize phase?</h2><p>First, enabling the migration team is crucial. The customer must train its experts for the tasks in the cloud upfront. Therefore, I accompany the learning path of the migration team, for example, by organizing AWS training sessions.</p><p>Second, I’m orchestrating colleagues to build the foundation needed for the migration, which includes things like:</p><ul><li>A landing zone to provision AWS accounts.</li><li>VPCs and VPN&#x2F;Direct Connect to ensure network connectivity.</li><li>Security and Governance based on IAM, CloudTrail, Config, and more.</li></ul><p>Third, I do refine the inventory and migration plan. Doing so includes producing runbooks for migrating the servers and applications.</p><h2 id="How-does-migrate-modernize-phase-succeed"><a href="#How-does-migrate-modernize-phase-succeed" class="headerlink" title="How does migrate &amp; modernize phase succeed?"></a>How does migrate &amp; modernize phase succeed?</h2><p>The preparations made in the assess and mobilize phase are the basis for the upcoming implementation. It is my responsibility to track the progress of the migration and to watch out for challenges where I need to bring in a college with expert knowledge in a particular area. For example, when migrating database systems with as little downtime as possible, I ensure a database expert is available.</p><h2 id="Which-tools-are-you-using-to-plan-a-cloud-migration"><a href="#Which-tools-are-you-using-to-plan-a-cloud-migration" class="headerlink" title="Which tools are you using to plan a cloud migration?"></a>Which tools are you using to plan a cloud migration?</h2><p>During assess phase, I use the AWS Migration Evaluator, which collects information about the on-premises servers. It is worth noting that the tool tracks the CPU and memory utilization over time. Therefore, it is crucial to collect data for a few weeks. After that, the AWS Migration Evaluator derives the number and type of EC2 instances and EBS volumes required for the workload based on the collected data.</p><p>The AWS Migration Evaluator puts together a business case for the migration. The business case contrasts the total ownership costs of the on-premises infrastructure with the projected AWS costs.</p><h2 id="How-does-the-AWS-Migration-Evaluator-work"><a href="#How-does-the-AWS-Migration-Evaluator-work" class="headerlink" title="How does the AWS Migration Evaluator work?"></a>How does the AWS Migration Evaluator work?</h2><p>First, it is necessary to spin up the collector within the on-premises environment. The collector supports the following protocols and does not require installing an agent on all the servers.</p><ul><li>VMWare: vSphere SOAP API over HTTPS</li><li>Hyper-V: WMI and Active Directory</li><li>Bare Metal: WMI or SNMP</li><li>SQL Server: T-SQL</li></ul><p>The collector stores the data in a local database and transfers the data to the AWS Migration Evaluator service automatically or on-demand.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/aws-migaration-evaluator@730w.webp 730w, /images/2022/06/aws-migaration-evaluator@730w2x.webp 1460w, /images/2022/06/aws-migaration-evaluator@610w.webp 610w, /images/2022/06/aws-migaration-evaluator@610w2x.webp 1220w, /images/2022/06/aws-migaration-evaluator@450w.webp 450w, /images/2022/06/aws-migaration-evaluator@450w2x.webp 900w, /images/2022/06/aws-migaration-evaluator@330w.webp 330w, /images/2022/06/aws-migaration-evaluator@330w2x.webp 660w, /images/2022/06/aws-migaration-evaluator@545w.webp 545w, /images/2022/06/aws-migaration-evaluator@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/aws-migaration-evaluator@730w.png 730w, /images/2022/06/aws-migaration-evaluator@730w2x.png 1460w, /images/2022/06/aws-migaration-evaluator@610w.png 610w, /images/2022/06/aws-migaration-evaluator@610w2x.png 1220w, /images/2022/06/aws-migaration-evaluator@450w.png 450w, /images/2022/06/aws-migaration-evaluator@450w2x.png 900w, /images/2022/06/aws-migaration-evaluator@330w.png 330w, /images/2022/06/aws-migaration-evaluator@330w2x.png 660w, /images/2022/06/aws-migaration-evaluator@545w.png 545w, /images/2022/06/aws-migaration-evaluator@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/aws-migaration-evaluator.png" alt="AWS Migration Evaluator" title="AWS Migration Evaluator"></picture></p><p>The AWS Migration Evaluator calculates the required EC2 instances and EBS volumes with the inventory data, including CPU and memory utilization. There are two options to do so.</p><ul><li><strong>Direct Match</strong> assigns an EC2 instance type that matches the CPU and memory capacity provisioned on-premises.</li><li><strong>Right Sized</strong> assigns an EC2 instance type adjusted to the measured CPU and memory utilization with the option to configure headroom.</li></ul><p>On top of that, the AWS Migration Evaluator helps to identify zombie machines that are running but are not running any significant workload.</p><h2 id="Why-is-the-AWS-Migration-Evaluator-essential-for-the-success-of-a-cloud-migration"><a href="#Why-is-the-AWS-Migration-Evaluator-essential-for-the-success-of-a-cloud-migration" class="headerlink" title="Why is the AWS Migration Evaluator essential for the success of a cloud migration?"></a>Why is the AWS Migration Evaluator essential for the success of a cloud migration?</h2><p>In my experience, making decisions based on data is crucial for the success of a migration project. One aspect is coming up with a business case to motivate the customer. The other aspect, a detailed inventory of the on-premises environment, is critical for planning and executing the migration.</p><h2 id="What-are-the-limitations-of-the-AWS-Migration-Evaluator"><a href="#What-are-the-limitations-of-the-AWS-Migration-Evaluator" class="headerlink" title="What are the limitations of the AWS Migration Evaluator?"></a>What are the limitations of the AWS Migration Evaluator?</h2><p>Setting up the data collection can be tricky when the on-premises environment is not based on VMWare. Also, you have to keep in mind that the AWS Migration Evaluator only considers CPU, memory, and storage. The blind spot of the tool is networking and storage throughput. For example, an <code>m5.large</code> comes with 2 vCPUs and 8 GiB memory, but the baseline network throughput is 0.75 Gbit&#x2F;s only. Also, the throughput of EBS volumes depends heavily on the volume type, the volume size, and the provisioned IOPS.</p><h2 id="In-your-experience-what-are-the-three-most-important-factors-for-a-successful-migration"><a href="#In-your-experience-what-are-the-three-most-important-factors-for-a-successful-migration" class="headerlink" title="In your experience, what are the three most important factors for a successful migration?"></a>In your experience, what are the three most important factors for a successful migration?</h2><p>It’s not about the technology but the people. When migrating an on-premises workload to the cloud, people have to change. Accompanying this process of change is a challenge. Let me give you an example. One employee used to be a sort of firefighter for the IT department. If there were problems, the employee would drive to the data center. When failed hardware had been replaced, he returned to the office and was celebrated as a hero. Looking at the world from this employee’s perspective, cloud migration is a scary thing. Where will its recognition come from in the future? During the migration project, this colleague must be shown a new perspective. Otherwise, he will try to torpedo the project.</p><blockquote><p>Would you like to join Monika’s team to accompany cloud migrations of SMB and Enterprise customers? tecRacer is hiring Cloud Migration Specialists.</p></blockquote><blockquote><p>Are you looking for AWS talent too? We offer job postings and content marketing to accelerate your recruitment. Get in touch!</p></blockquote>]]>
      </content:encoded>
    </item>
    <item>
      <title>ALB vs. NLB: Which AWS load balancer fits your needs?</title>
      <link>https://cloudonaut.io/alb-vs-nlb-which-aws-load-balancer-fits-your-needs/</link>
      <description>
        <![CDATA[<p>Which load balancer fits my workload best? As is often the case, AWS offers more than one solution. Read on to learn whether to use the A]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/alb/">alb</category>
      <category domain="https://cloudonaut.io/tag/nlb/">nlb</category>
      <guid isPermaLink="true">https://cloudonaut.io/alb-vs-nlb-which-aws-load-balancer-fits-your-needs/</guid>
      <pubDate>Fri, 24 Jun 2022 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Which load balancer fits my workload best? As is often the case, AWS offers more than one solution. Read on to learn whether to use the Application Load Balancer (ALB) or the Network Load Balancer (NLB) to distribute incoming requests among a fleet of virtual machines or containers.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/choice@730w.webp 730w, /images/2022/06/choice@730w2x.webp 1460w, /images/2022/06/choice@610w.webp 610w, /images/2022/06/choice@610w2x.webp 1220w, /images/2022/06/choice@450w.webp 450w, /images/2022/06/choice@450w2x.webp 900w, /images/2022/06/choice@330w.webp 330w, /images/2022/06/choice@330w2x.webp 660w, /images/2022/06/choice@545w.webp 545w, /images/2022/06/choice@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/choice@730w.jpg 730w, /images/2022/06/choice@730w2x.jpg 1460w, /images/2022/06/choice@610w.jpg 610w, /images/2022/06/choice@610w2x.jpg 1220w, /images/2022/06/choice@450w.jpg 450w, /images/2022/06/choice@450w2x.jpg 900w, /images/2022/06/choice@330w.jpg 330w, /images/2022/06/choice@330w2x.jpg 660w, /images/2022/06/choice@545w.jpg 545w, /images/2022/06/choice@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/choice.jpg" alt="ALB vs. NLB" title="ALB vs. NLB"></picture></p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><p>I created the following diagram showing an ALB and an NLB both distributing incoming requests among two EC2 instances:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/alb-nlb@730w.webp 730w, /images/2022/06/alb-nlb@730w2x.webp 1460w, /images/2022/06/alb-nlb@610w.webp 610w, /images/2022/06/alb-nlb@610w2x.webp 1220w, /images/2022/06/alb-nlb@450w.webp 450w, /images/2022/06/alb-nlb@450w2x.webp 900w, /images/2022/06/alb-nlb@330w.webp 330w, /images/2022/06/alb-nlb@330w2x.webp 660w, /images/2022/06/alb-nlb@545w.webp 545w, /images/2022/06/alb-nlb@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/alb-nlb@730w.png 730w, /images/2022/06/alb-nlb@730w2x.png 1460w, /images/2022/06/alb-nlb@610w.png 610w, /images/2022/06/alb-nlb@610w2x.png 1220w, /images/2022/06/alb-nlb@450w.png 450w, /images/2022/06/alb-nlb@450w2x.png 900w, /images/2022/06/alb-nlb@330w.png 330w, /images/2022/06/alb-nlb@330w2x.png 660w, /images/2022/06/alb-nlb@545w.png 545w, /images/2022/06/alb-nlb@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/alb-nlb.png" alt="ALB and NLB distributing incoming requests" title="ALB and NLB distributing incoming requests"></picture></p><h2 id="The-components"><a href="#The-components" class="headerlink" title="The components"></a>The components</h2><p>The components are the same for both the ALB and the NLB. The following figure illustrates the components.</p><ul><li>The load balancer acts as the entry point into your system.</li><li>The listener listens for incoming connections</li><li>The load balancer forwards requests to a target group.</li><li>The target group consists of one or multiple targets.</li><li>A target might be an EC2 instance, container, or internal service.</li><li>The health check monitors the targets.</li></ul><p>As the components are the same, it is not a big deal to switch from an ALB to an NLB -or vice versa. Of course, you can only do so if you do not use any features of the ALB that the NLB does not provide.</p><h2 id="The-default"><a href="#The-default" class="headerlink" title="The default"></a>The default</h2><p>When in doubt, choose the ALB. There are two main reasons for that:</p><ol><li>The ALB comes with a lot of built-in features. See the comparison table at the end of the article.</li><li>The NLB comes with a few rough edges and requires more experience and care.</li></ol><p>So when deciding between an ALB and an NLB, choose the ALB unless one of the points we discuss next applies to your scenario.</p><h2 id="The-exceptions"><a href="#The-exceptions" class="headerlink" title="The exceptions"></a>The exceptions</h2><p>If the answer to one of the following questions is “yes”, consider an NLB for your workload.</p><h3 id="Do-clients-connect-via-UDP-or-non-HTTP"><a href="#Do-clients-connect-via-UDP-or-non-HTTP" class="headerlink" title="Do clients connect via UDP or non-HTTP?"></a>Do clients connect via UDP or non-HTTP?</h3><p>The ALB only supports HTTP&#x2F;1.1, HTTP&#x2F;2, or gRPC. So when clients use a different protocol to connect with your application, you need to use the NLB instead. For example, all scenarios that are using UDP do require an NLB. Also, when you want to use HTTP&#x2F;3, the NLB is currently your only choice.</p><h3 id="Do-you-need-to-optimize-for-performance"><a href="#Do-you-need-to-optimize-for-performance" class="headerlink" title="Do you need to optimize for performance?"></a>Do you need to optimize for performance?</h3><p>The ALB operates on layer 7, which means the ALB inspects the details of every incoming HTTP request. In contrast, the NLB works on layer 4. All the NLB cares about is forwarding the incoming TCP or UDP connection to a target. The NLB does not inspect an incoming HTTP request, for example.</p><p>Therefore, the NLB has much less work to do than an ALB. As a result, the NLB needs significantly less time to forward an incoming request. So when performance is crucial to your workload, you should consider using an NLB to reduce latency.</p><h3 id="Do-you-expect-unpredictable-and-huge-traffic-spikes"><a href="#Do-you-expect-unpredictable-and-huge-traffic-spikes" class="headerlink" title="Do you expect unpredictable and huge traffic spikes?"></a>Do you expect unpredictable and huge traffic spikes?</h3><p>The ALB adapts to an increase of connections and requests automatically. However, it takes minutes to do so. So, when expecting substantial traffic spikes, the ALB might not be able to scale fast enough to handle all incoming requests immediately. AWS advises informing their support team when you expect a huge traffic spike in the future to allow them to pre-warm the ALB for you. However, this approach only works when you can predict traffic spikes.</p><p>In contrast, the NLB does not need to scale the number of nodes processing incoming connections. Instead, the NLB is designed to handle unpredicted and huge traffic spikes.</p><p>So, when you expect unpredicted and substantial traffic spikes, the NLB is a better fit. Unfortunately, AWS does not define what “huge traffic spike” means. Most applications will never reach the limits of an ALB. Think of traffic spikes caused by a super-bowl ad hitting your web application or the new Playstation becoming available at your online shop as scenarios where this becomes an issue.</p><h3 id="Do-you-require-static-IP-addresses-for-inbound-traffic"><a href="#Do-you-require-static-IP-addresses-for-inbound-traffic" class="headerlink" title="Do you require static IP addresses for inbound traffic?"></a>Do you require static IP addresses for inbound traffic?</h3><p>To connect to an ALB, a client needs to resolve its DNS name. For example, one of my load balancers is reachable via <code>jenkins-YC728XLHQAVF-1237735722.eu-west-1.elb.amazonaws.com</code> resolves to <code>52.17.44.105</code> and <code>34.248.155.206</code>. The IP addresses are subject to change. For example, when the ALB needs to scale, the name would point to additional IP addresses.</p><p>So when you need static IP addresses for inbound traffic, the ALB is not an option. Luckily, that’s something the NLB comes with out-of-the-box. Typical scenarios are: a third party that insists on static IP addresses to create firewall rules, or a client that does not come with the ability to resolve hostnames.</p><h2 id="The-comparison"><a href="#The-comparison" class="headerlink" title="The comparison"></a>The comparison</h2><p>The following table compares the ALB and NLB in detail.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th>ALB</th><th>NLB</th></tr></thead><tbody><tr><td>Protocols</td><td>HTTP/1, HTTP/2, gRPC</td><td>TCP, UDP</td></tr><tr><td>Performance</td><td>Low Latency</td><td>Very Low Latency</td></tr><tr><td>Traffic Spikes</td><td>⚠️ Inform AWS Support about huge traffic spikes</td><td>✅ Deals with huge and unexpected traffic spikes</td></tr><tr><td>Static IP Addresses</td><td>❌ No. However, you could place an NLB in front of an ALB.</td><td>✅ Yes</td></tr><tr><td>TLS Termination</td><td>✅ Yes</td><td>✅ Yes</td></tr><tr><td>Targets</td><td>EC2 Instance, IP Address, Lambda</td><td>EC2 Instance, IP Address, ALB</td></tr><tr><td>Client IP preservation</td><td>Use HTTP header X-Forwarded-For</td><td>Optional, but comes with limitations</td></tr><tr><td>Routing Algorithm</td><td>Round Robin or Least Outstanding Requests</td><td>Random</td></tr><tr><td>Deregistering targets</td><td>ALB stops sending requests and waits for open requests</td><td>NLB stops opening new connections, but the application needs to terminate connections properly</td></tr><tr><td>Multiplexing</td><td>✅ Yes, reuses connections to targets</td><td>❌ No, does not reuse connections to targets</td></tr><tr><td>Maximum number of targets</td><td>1000-5000</td><td>500-1000</td></tr><tr><td>Security Group</td><td>Security group of ALB controls inbound traffic, targets reachable from ALB only</td><td>Security group of targets control inbound traffic, targets reachable from clients</td></tr><tr><td>Request based routing</td><td>✅ Yes, based on hostname, path, header, …</td><td>❌ No</td></tr><tr><td>WAF</td><td>✅ Yes</td><td>❌ No</td></tr><tr><td>Authentication</td><td>✅ Yes (OpenID Connect, SAML, …)</td><td>❌ No</td></tr><tr><td>Slow Start Mode</td><td>✅ Yes</td><td>❌ No</td></tr><tr><td>Sticky Session</td><td>✅ Yes</td><td>❌ No</td></tr><tr><td>IPv6</td><td>✅ Yes</td><td>✅ Yes</td></tr><tr><td>Costs</td><td>💰💰💰</td><td>💰💰 (But causes more connections and therefore higher load on targets.)</td></tr></tbody></table><h2 id="The-mental-model"><a href="#The-mental-model" class="headerlink" title="The mental model"></a>The mental model</h2><p>When it comes to AWS, it helps to have a mental model of the provided building blocks.</p><p>Think about the ALB as a reverse proxy distributing incoming requests among a fleet of virtual machines or containers. Besides that, the ALB provides features like request-based routing, authentication, and security. The ALB is like a fully-managed, scalable, and highly available version of NGINX, HAProxy, or Caddy.</p><p>In contrast, think about the NLB as a way to route traffic to a fleet of virtual machines or containers on the network layer. You get static anycast IP addresses pointing to a dynamic pool of targets. Similar services are the AWS Global Accelerator and the Google Cloud Load Balancer.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>If you have to pick a load balancer, choose the ALB unless you can find a good reason to go with an NLB. Typical reasons for selecting an NLB instead of an ALB are:</p><ul><li>The workload requires UDP or a non-HTTP protocol.</li><li>Latency is very crucial, and you have to optimize for every millisecond.</li><li>Unexpected and huge traffic spikes are likely to happen.</li><li>Static IP addresses are required for inbound traffic.</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>Review: App Runner - Simply containers on AWS!</title>
      <link>https://cloudonaut.io/review-apprunner-simply-containers-on-aws/</link>
      <description>
        <![CDATA[<p>How many options are there for deploying containers on AWS? ECS, EKS, Elastic Beanstalk, EC2, and Fargate, to name a few. And there’s ano]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/review/">review</category>
      <guid isPermaLink="true">https://cloudonaut.io/review-apprunner-simply-containers-on-aws/</guid>
      <pubDate>Wed, 15 Jun 2022 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>How many options are there for deploying containers on AWS? ECS, EKS, Elastic Beanstalk, EC2, and Fargate, to name a few. And there’s another service I’d like to take a closer look at in this review: AWS App Runner. And I promise you already; it’s worth it! App Runner is an underrated option for running containers on AWS.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/magnifier@730w.webp 730w, /images/2022/06/magnifier@730w2x.webp 1460w, /images/2022/06/magnifier@610w.webp 610w, /images/2022/06/magnifier@610w2x.webp 1220w, /images/2022/06/magnifier@450w.webp 450w, /images/2022/06/magnifier@450w2x.webp 900w, /images/2022/06/magnifier@330w.webp 330w, /images/2022/06/magnifier@330w2x.webp 660w, /images/2022/06/magnifier@545w.webp 545w, /images/2022/06/magnifier@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/magnifier@730w.jpg 730w, /images/2022/06/magnifier@730w2x.jpg 1460w, /images/2022/06/magnifier@610w.jpg 610w, /images/2022/06/magnifier@610w2x.jpg 1220w, /images/2022/06/magnifier@450w.jpg 450w, /images/2022/06/magnifier@450w2x.jpg 900w, /images/2022/06/magnifier@330w.jpg 330w, /images/2022/06/magnifier@330w2x.jpg 660w, /images/2022/06/magnifier@545w.jpg 545w, /images/2022/06/magnifier@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/magnifier.jpg" alt="Review: App Runner - Simply containers on AWS!" title="Review: App Runner - Simply containers on AWS!"></picture></p><p>AWS announced App Runner in May 2021. I did ignore the service for almost a year. But when writing a new chapter for the 3rd edition of our book Amazon Web Services in Action, I decided to give App Runner a try, and I was astonished by its simplicity and pricing.</p><h2 id="What-is-App-Runner"><a href="#What-is-App-Runner" class="headerlink" title="What is App Runner?"></a>What is App Runner?</h2><p>App Runner is designed to deploy containerized web applications and is built for request-response type apps. I’d classify App Runner as a Platform-as-a-Service offering targeted at developers looking for a simple way to deploy their applications.</p><h2 id="How-does-App-Runner-work"><a href="#How-does-App-Runner-work" class="headerlink" title="How does App Runner work?"></a>How does App Runner work?</h2><p>How long does it take to deploy a container image with ECS&#x2F;EKS and Fargate for the first time? I’d say at least a few hours when you are lucky. There are so many resources that you need to provision and configure: a cluster, a service, automated scaling, and a load balancer, to name a few.</p><p>In contrast, it takes 5 minutes to deploy a container image with App Runner. Don’t believe me? Just type in the following command into your terminal.</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">aws apprunner create-service --service-name simple \</span><br><span class="line">  --source-configuration \</span><br><span class="line">  &#x27;&#123;&quot;ImageRepository&quot;: &#123;&quot;ImageIdentifier&quot;: &quot;public.ecr.aws/s5r5a1t5/simple:latest&quot;, &quot;ImageRepositoryType&quot;: &quot;ECR_PUBLIC&quot;&#125;&#125;&#x27;</span><br></pre></td></tr></table></figure><p>The command will output information about the App Runner service. Most importantly, the <code>ServiecUrl</code> and the <code>ServiecArn</code>.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Service&quot;</span><span class="punctuation">:</span></span><br><span class="line">  <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;ServiceName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;simple&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;ServiceId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;f6fc1c6232324461aa96ed11cf1e37cb&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;ServiceArn&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:apprunner:us-east-1:xxx:service/simple/...&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;ServiceUrl&quot;</span><span class="punctuation">:</span> <span class="string">&quot;sprruvd23i.us-east-1.awsapprunner.com&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;CreatedAt&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2022-06-15T09:39:22.651000+02:00&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;UpdatedAt&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2022-06-15T09:39:22.651000+02:00&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;Status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;OPERATION_IN_PROGRESS&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;...&quot;</span><span class="punctuation">:</span> <span class="string">&quot;...&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;OperationId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1a1b1e72ca044acdaa74a1c7689227bf&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>It takes a few minutes until the service is up and running. After that, point your browser to the <code>ServiceUrl</code>, which is <code>https://sprruvd23i.us-east-1.awsapprunner.com</code> in my example.</p><p>If you want to check the state of the service deployment, use the following command. Make sure to use <code>ServiceArn</code> from the previous command’s output.</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">aws apprunner describe-service --service-arn arn:aws:apprunner:us-east-1:...</span><br></pre></td></tr></table></figure><p>It could not be simpler to deploy a container image on AWS.</p><p>So, App Runner provides a new layer of abstraction. Mainly, I appreciate that the whole complexity is hidden from me as the customer. In contrast to Elastic Beanstalk, App Runner does not provision resources like CloudFormation stacks, EC2 instances, and more in the background.</p><p>Also, I want to highlight that App Runner is not automation like the CDK, cfn-modules, or AWS Copilot. Instead, App Runner is an abstraction.</p><h2 id="How-much-does-App-Runner-cost"><a href="#How-much-does-App-Runner-cost" class="headerlink" title="How much does App Runner cost?"></a>How much does App Runner cost?</h2><p>An active container instance is charged per vCPU- and GB-hour.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">USD/hour</th></tr></thead><tbody><tr><td>vCPU</td><td style="text-align:right">$0.064</td></tr><tr><td>GB</td><td style="text-align:right">$0.007</td></tr></tbody></table><p>Note that there are no additional charges for load balancing.</p><p>The unique feature of App Runner is that the CPU consumption can be decreased to 0. App Runner will first reduce the number of container instances when no requests are coming in. Next, App Runner will pause the remaining container instances. A paused instance is called provisioned instance by AWS.</p><p>For provisioned container instances, you only pay for the memory, not the vCPU.</p><p>While a container instance pauses, App Runner will throttle the available CPU capacity to almost 0. But, when a request comes in, the container instance is available to process the request without any significant delay. Think of provisioned instances as hot standby.</p><p>Next, let me try to compare costs for App Runner with ECS and Fargate. As always, the calculation is not valid for all scenarios. Therefore, you should do your calculations for your specific case.</p><p>For comparison, let’s consider a small web application used by 100 users processing 200 GB of HTTPS traffic. To achieve high availability, we provision two containers with 1 vCPU and 2 GB memory. Also, the web application does not receive requests 50% of the time.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">App Runner</th><th style="text-align:right">ECS + Fargate</th></tr></thead><tbody><tr><td>Active Containers</td><td style="text-align:right">56.94</td><td style="text-align:right">72.08</td></tr><tr><td>Provisioned Containers</td><td style="text-align:right">10.22</td><td style="text-align:right"></td></tr><tr><td>Load Balancer (ALB)</td><td style="text-align:right"></td><td style="text-align:right">18.06</td></tr><tr><td>Total Costs</td><td style="text-align:right">67.16</td><td style="text-align:right">90.11</td></tr></tbody></table><p>In this scenario, App Runner is cheaper than ECS + Fargate. However, the calculation looks different if we assume that the application processes requests 24&#x2F;7.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">App Runner</th><th style="text-align:right">ECS + Fargate</th></tr></thead><tbody><tr><td>Active Containers</td><td style="text-align:right">113.88</td><td style="text-align:right">72.08</td></tr><tr><td>Provisioned Containers</td><td style="text-align:right"></td><td style="text-align:right"></td></tr><tr><td>Load Balancer (ALB)</td><td style="text-align:right"></td><td style="text-align:right">18.06</td></tr><tr><td>Total Costs</td><td style="text-align:right">113.88</td><td style="text-align:right">90.11</td></tr></tbody></table><p>I would also note that ECS and Fargate are ahead when running more than two containers.</p><p>In summary, App Runner is an exciting choice for small web applications with more than 25% idle periods.</p><h2 id="Networking"><a href="#Networking" class="headerlink" title="Networking"></a>Networking</h2><p>App Runner provides a public endpoint HTTPS endpoint for each service. The service I created as an example at the beginning of this blog post was accessible through <code>https://sprruvd23i.us-east-1.awsapprunner.com</code>. It is also worth noting that by default, an App Runner service is running in a public VPC owned by AWS. So it is possible to connect your container to public endpoints such as S3, DynamoDB, or 3rd party APIs. But it is impossible to connect to an RDS database in your VPC.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/apprunner-network-01@730w.webp 730w, /images/2022/06/apprunner-network-01@730w2x.webp 1460w, /images/2022/06/apprunner-network-01@610w.webp 610w, /images/2022/06/apprunner-network-01@610w2x.webp 1220w, /images/2022/06/apprunner-network-01@450w.webp 450w, /images/2022/06/apprunner-network-01@450w2x.webp 900w, /images/2022/06/apprunner-network-01@330w.webp 330w, /images/2022/06/apprunner-network-01@330w2x.webp 660w, /images/2022/06/apprunner-network-01@545w.webp 545w, /images/2022/06/apprunner-network-01@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/apprunner-network-01@730w.png 730w, /images/2022/06/apprunner-network-01@730w2x.png 1460w, /images/2022/06/apprunner-network-01@610w.png 610w, /images/2022/06/apprunner-network-01@610w2x.png 1220w, /images/2022/06/apprunner-network-01@450w.png 450w, /images/2022/06/apprunner-network-01@450w2x.png 900w, /images/2022/06/apprunner-network-01@330w.png 330w, /images/2022/06/apprunner-network-01@330w2x.png 660w, /images/2022/06/apprunner-network-01@545w.png 545w, /images/2022/06/apprunner-network-01@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/apprunner-network-01.png" alt="By default, App Runner is only able to access public available endpoints" title="By default, App Runner is only able to access public available endpoints"></picture></p><p>However, AWS recently announced the possibility of connecting your App Runner service with your VPC. It works a lot like VPC connectivity for Lambda. App Runner creates an ENI within every subnet you choose for the service. Please consider that containers require a NAT gateway in case outbound Internet connectivity is needed.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/apprunner-network-02@730w.webp 730w, /images/2022/06/apprunner-network-02@730w2x.webp 1460w, /images/2022/06/apprunner-network-02@610w.webp 610w, /images/2022/06/apprunner-network-02@610w2x.webp 1220w, /images/2022/06/apprunner-network-02@450w.webp 450w, /images/2022/06/apprunner-network-02@450w2x.webp 900w, /images/2022/06/apprunner-network-02@330w.webp 330w, /images/2022/06/apprunner-network-02@330w2x.webp 660w, /images/2022/06/apprunner-network-02@545w.webp 545w, /images/2022/06/apprunner-network-02@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/apprunner-network-02@730w.png 730w, /images/2022/06/apprunner-network-02@730w2x.png 1460w, /images/2022/06/apprunner-network-02@610w.png 610w, /images/2022/06/apprunner-network-02@610w2x.png 1220w, /images/2022/06/apprunner-network-02@450w.png 450w, /images/2022/06/apprunner-network-02@450w2x.png 900w, /images/2022/06/apprunner-network-02@330w.png 330w, /images/2022/06/apprunner-network-02@330w2x.png 660w, /images/2022/06/apprunner-network-02@545w.png 545w, /images/2022/06/apprunner-network-02@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/apprunner-network-02.png" alt="A VPC connection enables the container to access resources within a VPC" title="A VPC connection enables the container to access resources within a VPC"></picture></p><p>Besides that, a VPC endpoint is available for the App Runner service. Using such an endpoint gives you the chance to connect to App Runner without letting traffic flow through the Internet. However, be warned that the App Runner service is still accessible from the Internet.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/apprunner-network-03@730w.webp 730w, /images/2022/06/apprunner-network-03@730w2x.webp 1460w, /images/2022/06/apprunner-network-03@610w.webp 610w, /images/2022/06/apprunner-network-03@610w2x.webp 1220w, /images/2022/06/apprunner-network-03@450w.webp 450w, /images/2022/06/apprunner-network-03@450w2x.webp 900w, /images/2022/06/apprunner-network-03@330w.webp 330w, /images/2022/06/apprunner-network-03@330w2x.webp 660w, /images/2022/06/apprunner-network-03@545w.webp 545w, /images/2022/06/apprunner-network-03@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/apprunner-network-03@730w.png 730w, /images/2022/06/apprunner-network-03@730w2x.png 1460w, /images/2022/06/apprunner-network-03@610w.png 610w, /images/2022/06/apprunner-network-03@610w2x.png 1220w, /images/2022/06/apprunner-network-03@450w.png 450w, /images/2022/06/apprunner-network-03@450w2x.png 900w, /images/2022/06/apprunner-network-03@330w.png 330w, /images/2022/06/apprunner-network-03@330w2x.png 660w, /images/2022/06/apprunner-network-03@545w.png 545w, /images/2022/06/apprunner-network-03@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/apprunner-network-03.png" alt="VPC Endpoints make sure traffic to App Runner does not flow through the Internet" title="VPC Endpoints make sure traffic to App Runner does not flow through the Internet"></picture></p><p>This brings me to a feature that App Runner is missing. There is no way to control incoming traffic. There is no way to restrict access to the public <code>ServiceUrl</code>. The ability to create private App Runner services only accessible from a VPC, for example, by using PrivateLink, would enable many use cases. Also, support for AWS WAF is a must.</p><h2 id="How-to-automate-deployments-with-App-Runner"><a href="#How-to-automate-deployments-with-App-Runner" class="headerlink" title="How to automate deployments with App Runner?"></a>How to automate deployments with App Runner?</h2><p>App Runner supports three different sources.</p><ol><li>ECR Private</li><li>ECR Public</li><li>GitHub</li></ol><p>For the first two options, you need to manually push a container image to ECR or in an automated way, for example, by using AWS CodePipeline. App Runner supports deploying the latest image pushed to a private ECR repository.</p><p>When connecting App Runner with GitHub, App Runner will take care of setting up a deployment pipeline. All you need to do, is to add a configuration file named <code>apprunner.yaml</code> to your GitHub repository to configure the build process (see <a href="https://docs.aws.amazon.com/apprunner/latest/dg/config-file.html" target="_blank" rel="noopener">Setting App Runner service options using a configuration file</a>). App Runner comes with built-in runtime environments for Python, Node.js, and Java. After my experience with Elastic Beanstalk, I avoid relying on prebuild environments because I want full control over the lifecycle. However, using runtime environments managed by AWS might be an exciting shortcut for some scenarios.</p><h2 id="Custom-domain-names"><a href="#Custom-domain-names" class="headerlink" title="Custom domain names"></a>Custom domain names</h2><p>App Runner assigns a domain name to each service you create by default. <code>sprruvd23i.us-east-1.awsapprunner.com</code>, for example. However, you might want to point your domain name to a service. App Runner supports doing so and even creates the required SSL&#x2F;TLS certificate.</p><p>All you have to do is add two TXT and a CNAME record to your hosted zone. Unfortunately, CloudFormation does not support this feature yet.</p><h2 id="What-are-the-limitations-of-App-Runner"><a href="#What-are-the-limitations-of-App-Runner" class="headerlink" title="What are the limitations of App Runner?"></a>What are the limitations of App Runner?</h2><p>App Runner is built for request-response type apps talking HTTP. Other scenarios like batch processing are not supported yet.</p><p>On top of that, App Runner comes with a few limitations. Those seem to be hard limits for now.</p><ul><li>A container instance comes with a maximum of 2 vCPU and 4 GB memory.</li><li>A service spins up no more than 25 container instances.</li><li>A container instance cannot process more than 200 concurrent requests.</li></ul><h2 id="What’s-missing"><a href="#What’s-missing" class="headerlink" title="What’s missing?"></a>What’s missing?</h2><p>As mentioned before, the most crucial missing feature is the ability to control and restrict inbound traffic.</p><p>So far, AWS did not define a Service Level Agreement (SLA) for App Runner. That’s a must before deploying production workloads, in my opinion.</p><p>Also, App Runner is only available in the following regions so far.</p><ul><li>US East (N. Virginia)</li><li>US East (Ohio)</li><li>US West (Oregon)</li><li>Europe (Ireland)</li><li>Asia Pacific (Tokyo)</li></ul><p>The service did not expand to additional regions after launching in 2021, which is a bummer and hopefully not a sign that AWS has lost interest in this service.</p><p>Besides that, App Runner is not supported by AWS Config and does not emit CloudWatch events, which is crucial for compliance and monitoring.</p><h2 id="Service-Maturity-Table"><a href="#Service-Maturity-Table" class="headerlink" title="Service Maturity Table"></a>Service Maturity Table</h2><p>Last but not least, I would like to evaluate the service maturity of App Runner.</p><table class="table table-striped table-responsive"><thead><tr><th>Criteria</th><th>Summary</th><th style="text-align:right">Score</th></tr></thead><tbody><tr><td>Feature Completeness</td><td>⚠️</td><td style="text-align:right">5</td></tr><tr><td>Documentation detailedness</td><td>✅</td><td style="text-align:right">7</td></tr><tr><td>Tags (Grouping + Billing)</td><td>✅️</td><td style="text-align:right">10</td></tr><tr><td>CloudFormation + Terraform support</td><td>✅️️</td><td style="text-align:right">8</td></tr><tr><td>Emits CloudWatch Events</td><td>⚠️</td><td style="text-align:right">0</td></tr><tr><td>IAM granularity</td><td>✅️️</td><td style="text-align:right">10</td></tr><tr><td>Integrated with AWS Config</td><td>⚠️</td><td style="text-align:right">0</td></tr><tr><td>Auditing via AWS CloudTrail</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>Available in all commercial regions</td><td>⚠️</td><td style="text-align:right">3</td></tr><tr><td>SLA</td><td>⚠️</td><td style="text-align:right">0</td></tr><tr><td>Compliance (ISO, SOC HIPAA)</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td><strong>Total Maturity Score (0-10)</strong></td><td>⚠️</td><td style="text-align:right"><strong>5.7</strong></td></tr></tbody></table><p>Our maturity score for App Runner is 5.7 on a scale from 0 to 10. I see great potential in this service. An important feature missing is the ability to restrict access to the public endpoint. Things like a missing SLA or more vCPU and memory capacity per container instance are on the roadmap. I would not recommend App Runner for critical production workloads, but the service is definitely on my watchlist.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Building multi-architecture container images for AWS Graviton</title>
      <link>https://cloudonaut.io/building-multi-architecture-docker-container-images-for-aws-graviton/</link>
      <description>
        <![CDATA[<p>What do my MacBook Pro and my container workload running on ECS and Fargate have in common? They both run amazingly well on the ARM proce]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <guid isPermaLink="true">https://cloudonaut.io/building-multi-architecture-docker-container-images-for-aws-graviton/</guid>
      <pubDate>Wed, 01 Jun 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>What do my MacBook Pro and my container workload running on ECS and Fargate have in common? They both run amazingly well on the ARM processor architecture. However, building Docker images for Apple Silicon and AWS Graviton is challenging. Because a container image made for the <code>X86_64</code> architecture -which is good old Intel and AMD processors- does not run on the ARM processor architecture out of the box. Therefore, you will learn how to build multi-architecture images for <code>X86_64</code> as well as for <code>ARM64</code> in the following.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/06/container-ship@730w.webp 730w, /images/2022/06/container-ship@730w2x.webp 1460w, /images/2022/06/container-ship@610w.webp 610w, /images/2022/06/container-ship@610w2x.webp 1220w, /images/2022/06/container-ship@450w.webp 450w, /images/2022/06/container-ship@450w2x.webp 900w, /images/2022/06/container-ship@330w.webp 330w, /images/2022/06/container-ship@330w2x.webp 660w, /images/2022/06/container-ship@545w.webp 545w, /images/2022/06/container-ship@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/06/container-ship@730w.jpg 730w, /images/2022/06/container-ship@730w2x.jpg 1460w, /images/2022/06/container-ship@610w.jpg 610w, /images/2022/06/container-ship@610w2x.jpg 1220w, /images/2022/06/container-ship@450w.jpg 450w, /images/2022/06/container-ship@450w2x.jpg 900w, /images/2022/06/container-ship@330w.jpg 330w, /images/2022/06/container-ship@330w2x.jpg 660w, /images/2022/06/container-ship@545w.jpg 545w, /images/2022/06/container-ship@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/06/container-ship.jpg" alt="Building multi-architecture container images for AWS Graviton" title="Building multi-architecture container images for AWS Graviton"></picture></p><p>Watch the following video to learn how to build multi-architecture images locally and with AWS CodeBuild. Besides that, we will show you how to deploy a container image to ECS and Fargate running on <code>X86_64</code> and <code>ARM64</code> (AWS Graviton).</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=gZWVkI6JYCg">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/gZWVkI6JYCg" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p>You will find the commands and code snippets from the video in the following.</p><h2 id="How-to-build-a-multi-architecture-Docker-image"><a href="#How-to-build-a-multi-architecture-Docker-image" class="headerlink" title="How to build a multi-architecture Docker image"></a>How to build a multi-architecture Docker image</h2><p>To build a multi-architecture image, start with creating an ECR repository.</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">aws ecr create-repository --repository-name nodejs-express</span><br><span class="line">aws ecr get-login-password | docker login --username AWS --password-stdin 486555357186.dkr.ecr.eu-central-1.amazonaws.com</span><br></pre></td></tr></table></figure><p>Next, create a new builder instance.</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">docker buildx create --use</span><br></pre></td></tr></table></figure><p>The following <code>docker buildx build</code> command builds two container images, creates a manifest, and pushes all of that to the ECR repository.</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">docker buildx build \</span><br><span class="line">  --platform linux/amd64,linux/arm64 \</span><br><span class="line">  --push -t 486555357186.dkr.ecr.eu-central-1.amazonaws.com/nodejs-express \</span><br><span class="line">  -f docker/Dockerfile .</span><br></pre></td></tr></table></figure><p>To test the image on your local machine, use the <code>docker run</code> command.</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">docker run 486555357186.dkr.ecr.eu-central-1.amazonaws.com/nodejs-express</span><br></pre></td></tr></table></figure><p>That’s how to build a multi-architecture image locally. But how to do so as part of a CI&#x2F;CD pipeline?</p><h2 id="How-to-build-a-multi-arch-container-image-with-AWS-CodeBuild"><a href="#How-to-build-a-multi-arch-container-image-with-AWS-CodeBuild" class="headerlink" title="How to build a multi-arch container image with AWS CodeBuild"></a>How to build a multi-arch container image with AWS CodeBuild</h2><p>The following snippets give you an idea of how to build a multi-architecture image with the help of AWS CodeBuild as part of a deployment pipeline.</p><p>The following snippet shows a CloudFormation resource to configure a CodeBuild project. Check out the comments for explanations.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Project:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodeBuild::Project&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Artifacts:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">NO_ARTIFACTS</span></span><br><span class="line">    <span class="attr">Environment:</span></span><br><span class="line">      <span class="attr">ComputeType:</span> <span class="string">BUILD_GENERAL1_SMALL</span> <span class="comment"># The build job itself runs on X86_64 but builds a multi-arch image</span></span><br><span class="line">      <span class="attr">EnvironmentVariables:</span> <span class="comment"># Some environment variables needed for the build</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">ACCOUNT_ID</span></span><br><span class="line">        <span class="attr">Type:</span> <span class="string">PLAINTEXT</span></span><br><span class="line">        <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::AccountId&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">REGION</span></span><br><span class="line">        <span class="attr">Type:</span> <span class="string">PLAINTEXT</span></span><br><span class="line">        <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::Region&#x27;</span></span><br><span class="line">      <span class="comment"># ...</span></span><br><span class="line">      <span class="attr">Image:</span> <span class="string">&#x27;aws/codebuild/standard:5.0&#x27;</span> <span class="comment"># CodeBuild provides Docker images to run the build</span></span><br><span class="line">      <span class="attr">PrivilegedMode:</span> <span class="literal">true</span> <span class="comment"># Required to build Docker images</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">LINUX_CONTAINER</span>  </span><br><span class="line">    <span class="attr">LogsConfig:</span></span><br><span class="line">      <span class="attr">CloudWatchLogs:</span></span><br><span class="line">        <span class="attr">GroupName:</span> <span class="type">!Ref</span> <span class="string">ProjectLogGroup</span></span><br><span class="line">        <span class="attr">Status:</span> <span class="string">ENABLED</span></span><br><span class="line">    <span class="attr">Name:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line">    <span class="attr">ServiceRole:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;ProjectRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">Source:</span> <span class="comment"># Run the build job for each commit pushed to your CodeCommit repository</span></span><br><span class="line">      <span class="attr">Location:</span> <span class="type">!Sub</span> <span class="string">&#x27;https://git-codecommit.$&#123;AWS::Region&#125;.amazonaws.com/v1/repos/$&#123;CodeCommitRepositoryName&#125;&#x27;</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">CODECOMMIT</span></span><br><span class="line">    <span class="attr">TimeoutInMinutes:</span> <span class="number">45</span></span><br></pre></td></tr></table></figure><p>The <code>buildspec.yml</code> file is used to define the build job. The <code>aws/codebuild/standard:5.0</code> does not ship with the <code>buildx</code> plugin. That’s why we need to add the plugin during the install phase.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&#x27;0.2&#x27;</span></span><br><span class="line"><span class="attr">phases:</span></span><br><span class="line">  <span class="attr">install:</span></span><br><span class="line">    <span class="attr">commands:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;echo &quot;$&#123;DOCKER_PASSWORD&#125;&quot; | docker login -u &quot;$&#123;DOCKER_USERNAME&#125;&quot; --password-stdin&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">wget</span> <span class="string">https://github.com/docker/buildx/releases/download/v0.8.2/buildx-v0.8.2.linux-amd64</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">mkdir</span> <span class="string">-p</span> <span class="string">~/.docker/cli-plugins</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">mv</span> <span class="string">buildx-v0.8.2.linux-amd64</span> <span class="string">~/.docker/cli-plugins/docker-buildx</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">chmod</span> <span class="string">a+rx</span> <span class="string">~/.docker/cli-plugins/docker-buildx</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">docker</span> <span class="string">run</span> <span class="string">--privileged</span> <span class="string">--rm</span> <span class="string">tonistiigi/binfmt</span> <span class="string">--install</span> <span class="string">arm64,amd64</span></span><br><span class="line">  <span class="attr">pre_build:</span></span><br><span class="line">    <span class="attr">commands:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;aws ecr get-login-password | docker login --username AWS --password-stdin $&#123;ACCOUNT_ID&#125;.dkr.ecr.$&#123;REGION&#125;.amazonaws.com&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;echo &quot;$&#123;DOCKER_PASSWORD&#125;&quot; | docker login --username &quot;$&#123;DOCKER_USERNAME&#125;&quot; --password-stdin&#x27;</span></span><br><span class="line">  <span class="attr">build:</span></span><br><span class="line">    <span class="attr">commands:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;bash build.sh&#x27;</span></span><br></pre></td></tr></table></figure><p>Last but not least, the <code>build.sh</code> script uses the same commands that we used to build a multi-architecture container image locally.</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">!/bin/bash</span></span><br><span class="line"></span><br><span class="line">set -e</span><br><span class="line"></span><br><span class="line">NAME_TAG=&quot;$&#123;ACCOUNT_ID&#125;.dkr.ecr.$&#123;REGION&#125;.amazonaws.com/\</span><br><span class="line"><span class="meta prompt_">$</span><span class="language-bash">&#123;REPO_NAME&#125;:<span class="variable">$&#123;CODEBUILD_RESOLVED_SOURCE_VERSION&#125;</span><span class="string">&quot;</span></span></span><br><span class="line">docker buildx create --use</span><br><span class="line">docker buildx build --platform linux/amd64,linux/arm64 \</span><br><span class="line">  --push -t &quot;$&#123;NAME_TAG&#125;&quot; -f docker/Dockerfile .</span><br></pre></td></tr></table></figure><p>As you can now build multi-architecture images locally and with CodeBuild, one step is missing: deploying the image to ECS and Fargate.</p><h2 id="How-to-run-X86-64-and-ARM64-container-images-on-ECS-and-Fargate"><a href="#How-to-run-X86-64-and-ARM64-container-images-on-ECS-and-Fargate" class="headerlink" title="How to run X86_64 and ARM64 container images on ECS and Fargate"></a>How to run X86_64 and ARM64 container images on ECS and Fargate</h2><p>The following snippet shows a CloudFormation template using <a href="https://github.com/cfn-modules" target="_blank" rel="noopener">cfn-modules</a> to deploy an ECS service.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">AppService:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFormation::Stack&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Parameters:</span></span><br><span class="line">      <span class="attr">VpcModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Vpc.Outputs.StackName&#x27;</span></span><br><span class="line">      <span class="attr">ClusterModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Cluster.Outputs.StackName&#x27;</span></span><br><span class="line">      <span class="attr">TargetModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;AppTarget.Outputs.StackName&#x27;</span></span><br><span class="line">      <span class="attr">AlertingModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Alerting.Outputs.StackName&#x27;</span></span><br><span class="line">      <span class="attr">ClientSgModule1:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;AuroraServerlessClientSg.Outputs.StackName&#x27;</span></span><br><span class="line">      <span class="attr">AppImage:</span> <span class="type">!Ref</span> <span class="string">AppImage</span></span><br><span class="line">      <span class="attr">AppPort:</span> <span class="string">&#x27;8080&#x27;</span></span><br><span class="line">      <span class="comment"># ...</span></span><br><span class="line">      <span class="attr">Cpu:</span> <span class="string">&#x27;0.25&#x27;</span></span><br><span class="line">      <span class="attr">Memory:</span> <span class="string">&#x27;0.5&#x27;</span></span><br><span class="line">      <span class="attr">DesiredCount:</span> <span class="string">&#x27;2&#x27;</span></span><br><span class="line">      <span class="attr">MaxCapacity:</span> <span class="string">&#x27;4&#x27;</span></span><br><span class="line">      <span class="attr">MinCapacity:</span> <span class="string">&#x27;2&#x27;</span></span><br><span class="line">      <span class="attr">LogsRetentionInDays:</span> <span class="string">&#x27;14&#x27;</span></span><br><span class="line">      <span class="attr">CpuArchitecture:</span> <span class="string">&#x27;ARM64&#x27;</span> <span class="comment"># or X86_64</span></span><br><span class="line">    <span class="attr">TemplateURL:</span> <span class="string">&#x27;./node_modules/@cfn-modules/fargate-service/module.yml&#x27;</span></span><br></pre></td></tr></table></figure><p>Unfortunately, the AWS Management Console does not show whether a container runs on <code>X86_64</code> or <code>ARM64</code>. Use the following AWS CLI command to fetch more detailed information about a task, including the processor architecture.</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">aws ecs describe-tasks --tasks 122e8587c57c43f38ae1c5618c656624 \</span><br><span class="line">  --cluster nodejs-express-Cluster-1PM1YCI3HOWAH-Cluster-ZIVSeEmXEi1M</span><br></pre></td></tr></table></figure><p>That’s it. Enjoy the power and efficiency of the ARM processor architecture!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Deprecated Terraform provider template causes `Incompatible provider version` error</title>
      <link>https://cloudonaut.io/terraform-incompatible-provider-version/</link>
      <description>
        <![CDATA[<p>Did you recently switch to a Mac with Apple Silicon (ARM processor architecture)? The chances are high that you will see an <code>Error:]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/terraform/">terraform</category>
      <guid isPermaLink="true">https://cloudonaut.io/terraform-incompatible-provider-version/</guid>
      <pubDate>Wed, 25 May 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Did you recently switch to a Mac with Apple Silicon (ARM processor architecture)? The chances are high that you will see an <code>Error: Incompatible provider version</code> when running <code>terraform init</code> the next time. That’s because Hashicorp does not provide the <code>template</code> provider for the ARM platform. Luckily, there is an easy way to fix the issue.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/05/problem@730w.webp 730w, /images/2022/05/problem@730w2x.webp 1460w, /images/2022/05/problem@610w.webp 610w, /images/2022/05/problem@610w2x.webp 1220w, /images/2022/05/problem@450w.webp 450w, /images/2022/05/problem@450w2x.webp 900w, /images/2022/05/problem@330w.webp 330w, /images/2022/05/problem@330w2x.webp 660w, /images/2022/05/problem@545w.webp 545w, /images/2022/05/problem@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/05/problem@730w.jpg 730w, /images/2022/05/problem@730w2x.jpg 1460w, /images/2022/05/problem@610w.jpg 610w, /images/2022/05/problem@610w2x.jpg 1220w, /images/2022/05/problem@450w.jpg 450w, /images/2022/05/problem@450w2x.jpg 900w, /images/2022/05/problem@330w.jpg 330w, /images/2022/05/problem@330w2x.jpg 660w, /images/2022/05/problem@545w.jpg 545w, /images/2022/05/problem@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/05/problem.jpg" alt="Deprecated Terraform provider template causes &quot;Incompatible provider version&quot; error" title="Deprecated Terraform provider template causes &quot;Incompatible provider version&quot; error"></picture></p><h2 id="Terraform-Error-Incompatible-provider-version"><a href="#Terraform-Error-Incompatible-provider-version" class="headerlink" title="Terraform Error: Incompatible provider version"></a>Terraform Error: Incompatible provider version</h2><p>I got the following error when running <code>terraform init</code> on my new MacBook Pro for the first time.</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform init</span></span><br><span class="line"></span><br><span class="line">Initializing the backend...</span><br><span class="line"></span><br><span class="line">Initializing provider plugins...</span><br><span class="line">- Reusing previous version of hashicorp/template from the dependency lock file</span><br><span class="line">- Reusing previous version of hashicorp/aws from the dependency lock file</span><br><span class="line">- Installing hashicorp/aws v4.15.1...</span><br><span class="line">- Installed hashicorp/aws v4.15.1 (signed by HashiCorp)</span><br><span class="line"></span><br><span class="line">Error: Incompatible provider version</span><br><span class="line"></span><br><span class="line">Provider registry.terraform.io/hashicorp/template v2.2.0 does not have a package available for your current platform, darwin_arm64.</span><br><span class="line"></span><br><span class="line">Provider releases are separate from Terraform CLI releases, so not all providers are available for all platforms. Other versions of this provider may have different platforms supported.</span><br></pre></td></tr></table></figure><p>I understood from the error message that the <code>template</code> provider with version <code>v2.2.0</code> is not available for the ARM architecture.</p><p>So, I looked into my Terraform configuration. The <code>user_data</code> for <code>aws_instance</code> gets rendered by the data source <code>template_file</code>. The <code>template_file</code> data source reads the template file <code>userdata.sh.tpl</code> and replaces the placeholders with the variables (see <code>vars</code>).</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">terraform &#123;</span><br><span class="line">  required_providers &#123;</span><br><span class="line">    aws = &#123;</span><br><span class="line">      source = &quot;hashicorp/aws&quot;</span><br><span class="line">      version = &quot;4.15.1&quot;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">provider &quot;aws&quot; &#123;</span><br><span class="line">  region = &quot;us-east-1&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_instance&quot; &quot;demo&quot; &#123;</span><br><span class="line">  ami = &quot;ami-0022f774911c1d690&quot;</span><br><span class="line">  instance_type = &quot;t3.micro&quot;</span><br><span class="line">  user_data = data.template_file.userdata.rendered</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">data &quot;template_file&quot; &quot;userdata&quot; &#123;</span><br><span class="line">  template = &quot;$&#123;file(&quot;$&#123;path.module&#125;/userdata.sh.tpl&quot;)&#125;&quot;</span><br><span class="line">  vars = &#123;</span><br><span class="line">    msg = &quot;Hello World&quot;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The template file <code>userdata.sh.tpl</code> contains a shell script.</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">!/bin/bash</span></span><br><span class="line"></span><br><span class="line">echo &quot;$&#123;msg&#125;&quot;</span><br></pre></td></tr></table></figure><p>But, the <code>template</code> provider which implements the data source <code>template_file</code> is deprecated. Therefore, Hashicorp does not provide a version supporting the ARM platform.</p><h2 id="Terraform-Replace-deprecated-template-file-with-templatefile"><a href="#Terraform-Replace-deprecated-template-file-with-templatefile" class="headerlink" title="Terraform: Replace deprecated template_file with templatefile()"></a>Terraform: Replace deprecated template_file with templatefile()</h2><p>Therefore, I looked for a way to replace the data source  <code>template_file</code> and found the <code>templatefile</code> function. The following snippet shows my Terraform config after substituting <code>template_file</code> with <code>templatefile</code>.</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">terraform &#123;</span><br><span class="line">  required_providers &#123;</span><br><span class="line">    aws = &#123;</span><br><span class="line">      source = &quot;hashicorp/aws&quot;</span><br><span class="line">      version = &quot;4.15.1&quot;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">provider &quot;aws&quot; &#123;</span><br><span class="line">  region = &quot;us-east-1&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_instance&quot; &quot;demo&quot; &#123;</span><br><span class="line">  ami = &quot;ami-0022f774911c1d690&quot;</span><br><span class="line">  instance_type = &quot;t3.micro&quot;</span><br><span class="line">  user_data = templatefile(&quot;$&#123;path.module&#125;/userdata.sh.tpl&quot;, &#123;msg = &quot;Hello World&quot;&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>That wasn’t too hard.</p><h2 id="Terraform-Error-Incompatible-provider-version-persists"><a href="#Terraform-Error-Incompatible-provider-version-persists" class="headerlink" title="Terraform Error: Incompatible provider version persists"></a>Terraform Error: Incompatible provider version persists</h2><p>But, when I tried to run <code>terraform init</code> again, I still got the same error message as before.</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Error: Incompatible provider version</span><br><span class="line"></span><br><span class="line">Provider registry.terraform.io/hashicorp/template v2.2.0 does not have a package available for your current platform, darwin_arm64.</span><br></pre></td></tr></table></figure><p>It turns out that I had to remove the data source from my Terraform state. To do so, I used the following command.</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">terraform state <span class="built_in">rm</span> data.template_file.userdata</span></span><br><span class="line">Removed data.template_file.userdata</span><br><span class="line">Successfully removed 1 resource instance(s).</span><br></pre></td></tr></table></figure><p>After that, I ran <code>terraform init</code> and <code>terraform apply</code> without issues on my MacBook Pro.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Security Iceberg: AWS Security Hub the right way</title>
      <link>https://cloudonaut.io/security-iceberg-aws-security-hub-the-right-way/</link>
      <description>
        <![CDATA[<p>This is a warning about AWS Security Hub. Organizations that use AWS Security Hub to monitor and mitigate risks pay too much attention to]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/securityhub/">securityhub</category>
      <guid isPermaLink="true">https://cloudonaut.io/security-iceberg-aws-security-hub-the-right-way/</guid>
      <pubDate>Wed, 18 May 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>This is a warning about AWS Security Hub. Organizations that use AWS Security Hub to monitor and mitigate risks pay too much attention to the visible part of the AWS security iceberg, namely the findings. These organizations tend to overlook or underestimate the invisible part of the iceberg where critical risks are hidden.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/05/iceberg@730w.webp 730w, /images/2022/05/iceberg@730w2x.webp 1460w, /images/2022/05/iceberg@610w.webp 610w, /images/2022/05/iceberg@610w2x.webp 1220w, /images/2022/05/iceberg@450w.webp 450w, /images/2022/05/iceberg@450w2x.webp 900w, /images/2022/05/iceberg@330w.webp 330w, /images/2022/05/iceberg@330w2x.webp 660w, /images/2022/05/iceberg@545w.webp 545w, /images/2022/05/iceberg@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/05/iceberg@730w.jpg 730w, /images/2022/05/iceberg@730w2x.jpg 1460w, /images/2022/05/iceberg@610w.jpg 610w, /images/2022/05/iceberg@610w2x.jpg 1220w, /images/2022/05/iceberg@450w.jpg 450w, /images/2022/05/iceberg@450w2x.jpg 900w, /images/2022/05/iceberg@330w.jpg 330w, /images/2022/05/iceberg@330w2x.jpg 660w, /images/2022/05/iceberg@545w.jpg 545w, /images/2022/05/iceberg@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/05/iceberg.jpg" alt="Do not overlook the invisible part of the iceberg!" title="Do not overlook the invisible part of the iceberg!"></picture></p><p>Below, I go into the details of my observations and outline possible countermeasures to ensure you are using AWS Security Hub in the right way.</p><h2 id="Overwhelming-amount-of-findings"><a href="#Overwhelming-amount-of-findings" class="headerlink" title="Overwhelming amount of findings"></a>Overwhelming amount of findings</h2><p>The AWS Security Hub comes with controls for the following standards:</p><ul><li>CIS AWS Foundations: 48 controls</li><li>Payment Card Industry Data Security Standard (PCI DSS): 49 controls</li><li>AWS Foundational Security Best Practices: 162 controls</li></ul><p>It is not uncommon that you will end up with hundreds or even thousands of findings after enabling one or multiple standards for multiple AWS accounts with running workloads. Working through a huge list of findings to evaluate, suppress, or fix the issue is demotivating and uses up a lot of energy and attention.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/05/securityhub-findings@730w.webp 730w, /images/2022/05/securityhub-findings@730w2x.webp 1460w, /images/2022/05/securityhub-findings@610w.webp 610w, /images/2022/05/securityhub-findings@610w2x.webp 1220w, /images/2022/05/securityhub-findings@450w.webp 450w, /images/2022/05/securityhub-findings@450w2x.webp 900w, /images/2022/05/securityhub-findings@330w.webp 330w, /images/2022/05/securityhub-findings@330w2x.webp 660w, /images/2022/05/securityhub-findings@545w.webp 545w, /images/2022/05/securityhub-findings@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/05/securityhub-findings@730w.png 730w, /images/2022/05/securityhub-findings@730w2x.png 1460w, /images/2022/05/securityhub-findings@610w.png 610w, /images/2022/05/securityhub-findings@610w2x.png 1220w, /images/2022/05/securityhub-findings@450w.png 450w, /images/2022/05/securityhub-findings@450w2x.png 900w, /images/2022/05/securityhub-findings@330w.png 330w, /images/2022/05/securityhub-findings@330w2x.png 660w, /images/2022/05/securityhub-findings@545w.png 545w, /images/2022/05/securityhub-findings@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/05/securityhub-findings.png" alt="Don't get overwhelmed by the number of findings!" title="Don't get overwhelmed by the number of findings!"></picture></p><p>I’ve seen teams spend weeks or months fixing all the findings generated by AWS Security Hub. Don’t get me wrong, fixing the findings will improve overall security. My point is that you may be overlooking important areas where security can be significantly enhanced by spending the same time and money.</p><blockquote><p>A few ideas to avoid getting overwhelmed by the number of findings generated by the AWS Security Hub.</p><ol><li>Do not enable all security standards at once. For example, start with the <em>CIS AWS Foundations</em> standard.</li><li>Enable and monitor the AWS Security Hub at the beginning of a project before creating resources in the AWS account.</li><li>Disable irrelevant or outdated controls. Also, disable controls generating a lot of minor findings.</li></ol></blockquote><h2 id="Superficial-security-controls"><a href="#Superficial-security-controls" class="headerlink" title="Superficial security controls"></a>Superficial security controls</h2><p>Before the AWS Security Hub was a thing, I worked on a similar tool many years ago. The challenge when designing and implementing controls is abstraction and generalization. To evaluate the risk of a configuration, context is needed.</p><p>Let me illustrate this with an example. A Lambda function assumes an IAM role with the following policy attached.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2012-10-17&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Statement&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;s3:CreateBucket&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;s3:DeleteBucket&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;s3:DeleteObject&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;s3:GetObject&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;s3:PutObject&quot;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>A generic control cannot tell whether the IAM policy follows the least privilege principle because it does not know the Lambda function. The IAM policy could either be fine or add an unnecessary risk of accidental or malicious data deletion.</p><p>That’s why the controls focus on generic risks that are easy to detect. So it is crucial that you ask the question: Which parts of the iceberg are not covered by AWS Security Hub? Let me give you a few examples:</p><ul><li>Do resource- and identity-based policies follow the least privilege principle.</li><li>Are the security group rules as strict as possible?</li><li>Is the ALB publicly accessible by intention?</li><li>Should objects be protected from being manipulated or deleted by enabling S3 Object Lock?</li><li>What’s the data classification of the data stored in a database cluster? Is encryption with a customer-managed KMS key required?</li><li>Does that Lambda function (Node.js) contain a package with a critical vulnerability?</li></ul><p>Be aware that the AWS Security Hub does not check all relevant aspects of cloud security. Also, it is impossible to evaluate rules based on context information like the data classification, for example. That does not mean the service is terrible; it is just not possible to go into the details with generic controls.</p><p>Some might argue, that other AWS services or 3rd parties could jump in and provide advanced controls. For example, the IAM Access Analyzer compares CloudTrail logs with IAM polices to make suggestions on how to improve the policy. However, the IAM Access Analyzer comes with the same limitation, it does not know about your scenario. Instead, it makes guesses based on incomplete&#x2F;expensive CloudTrail logs. In my opinion, all those tools try to solve a problem, that cannot be solved without any context information about the scenario.</p><blockquote><p>Some ideas on avoiding blind spots when using the AWS Security Hub.</p><ol><li>Be aware that the AWS Security Hub does not cover all relevant aspects of cloud security.</li><li>Ask AWS security specialists to review AWS accounts regularly.</li><li>Validate whether IAM and network configuration follows the least privilege principle.</li></ol></blockquote><h2 id="False-positives"><a href="#False-positives" class="headerlink" title="False-positives"></a>False-positives</h2><p>We use the AWS Security Hub to evaluate the security risks within our AWS accounts. Every day, <a href="https://marbot.io/" target="_blank" rel="noopener">marbot -our chatbot- forwards a Security Hub finding to one of our Slacks channels</a>. The finding tells us that there is an EC2 instance with a public IP address running.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/05/securityhub-finding-marbot@730w.webp 730w, /images/2022/05/securityhub-finding-marbot@730w2x.webp 1460w, /images/2022/05/securityhub-finding-marbot@610w.webp 610w, /images/2022/05/securityhub-finding-marbot@610w2x.webp 1220w, /images/2022/05/securityhub-finding-marbot@450w.webp 450w, /images/2022/05/securityhub-finding-marbot@450w2x.webp 900w, /images/2022/05/securityhub-finding-marbot@330w.webp 330w, /images/2022/05/securityhub-finding-marbot@330w2x.webp 660w, /images/2022/05/securityhub-finding-marbot@545w.webp 545w, /images/2022/05/securityhub-finding-marbot@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/05/securityhub-finding-marbot@730w.png 730w, /images/2022/05/securityhub-finding-marbot@730w2x.png 1460w, /images/2022/05/securityhub-finding-marbot@610w.png 610w, /images/2022/05/securityhub-finding-marbot@610w2x.png 1220w, /images/2022/05/securityhub-finding-marbot@450w.png 450w, /images/2022/05/securityhub-finding-marbot@450w2x.png 900w, /images/2022/05/securityhub-finding-marbot@330w.png 330w, /images/2022/05/securityhub-finding-marbot@330w2x.png 660w, /images/2022/05/securityhub-finding-marbot@545w.png 545w, /images/2022/05/securityhub-finding-marbot@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/05/securityhub-finding-marbot.png" alt="marbot notified us about an AWS Security Hub finding" title="marbot notified us about an AWS Security Hub finding"></picture></p><p>Running an EC2 instance with a public IP address should raise a red flag. However, we have a good reason to start an EC2 instance with public IP once a day for 5 minutes. Thus, the finding is a false positive. The problem with false positives is that we get used to them. I close the warning every day, and it has happened to me that I have overlooked an important warning.</p><p>So, watch out for false positives and mark them as <code>SUPPRESSED</code>, which means you reviewed the finding and do not think any action is needed. In our case, a new finding gets generated every day because we are launching a new instance. That’s why automatically suppressing the finding is on our TODO list.</p><blockquote><p>A few ideas to ensure you do not get bothered by false positives and avoid alert fatigue.</p><ol><li>Mark false-postives as <code>SUPPRESSED</code> (see <a href="https://docs.aws.amazon.com/securityhub/latest/userguide/finding-workflow-status.html" target="_blank" rel="noopener">Setting the workflow status for findings</a>) manually.</li><li>Use a Lambda function to mark false positives as <code>SUPPRESSED</code> in case new resources result in findings regularly.</li></ol></blockquote><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Think of AWS security as an iceberg. The easy-to-find risks look out of the water, and AWS Security Hub will easily detect them. But under the water, the iceberg continues; critical vulnerabilities are hiding there. Therefore, here are a few tips for dealing with the AWS Security Hub.</p><ol><li>Avoid generating an overwhelming amount of findings. For example, do not enable all standards and controls at once.</li><li>Be aware that the AWS Security Hub does not cover all relevant aspects of cloud security. The generic controls do not guarantee that you are following the least-privilege principle, for example.</li><li>Reduce the noise generated by false positives to avoid alert fatigue. Mark findings as <code>SUPPRESSED</code> manually or automatically.</li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>Automate CloudFormation StackSets with CloudFormation</title>
      <link>https://cloudonaut.io/automate-cloudformation-stacksets-with-cloudformation/</link>
      <description>
        <![CDATA[<p><a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/what-is-cfnstacksets.html" target="_blank" rel="noopener">CloudFo]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <guid isPermaLink="true">https://cloudonaut.io/automate-cloudformation-stacksets-with-cloudformation/</guid>
      <pubDate>Fri, 13 May 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/what-is-cfnstacksets.html" target="_blank" rel="noopener">CloudFormation StackSets</a> rollout CloudFormation stacks to all or some of your AWS accounts. Also, stack sets allow you to deploy stacks to multiple regions. <strong>Therefore, CloudFormation stack sets are a great way to deploy baseline configurations to multiple accounts and regions.</strong> For example, we use CloudFormation stack sets to roll out baseline monitoring to all accounts belonging to our AWS organization. But how do you maintain CloudFormation stack sets? Amazingly, AWS announced a CloudFormation resource to manage CloudFormation stack sets recently.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/05/donuts@730w.webp 730w, /images/2022/05/donuts@730w2x.webp 1460w, /images/2022/05/donuts@610w.webp 610w, /images/2022/05/donuts@610w2x.webp 1220w, /images/2022/05/donuts@450w.webp 450w, /images/2022/05/donuts@450w2x.webp 900w, /images/2022/05/donuts@330w.webp 330w, /images/2022/05/donuts@330w2x.webp 660w, /images/2022/05/donuts@545w.webp 545w, /images/2022/05/donuts@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/05/donuts@730w.jpg 730w, /images/2022/05/donuts@730w2x.jpg 1460w, /images/2022/05/donuts@610w.jpg 610w, /images/2022/05/donuts@610w2x.jpg 1220w, /images/2022/05/donuts@450w.jpg 450w, /images/2022/05/donuts@450w2x.jpg 900w, /images/2022/05/donuts@330w.jpg 330w, /images/2022/05/donuts@330w2x.jpg 660w, /images/2022/05/donuts@545w.jpg 545w, /images/2022/05/donuts@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/05/donuts.jpg" alt="Manage CloudFormation stack sets in CloudFormation" title="Manage CloudFormation stack sets in CloudFormation"></picture></p><p>The following CloudFormation template defines a stack set that deploys one of our <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">open-source CloudFormation templates</a> to configure a password policy in all AWS accounts of an organization.</p><blockquote><p>Remember to <a href="https://docs.aws.amazon.com/organizations/latest/userguide/services-that-can-integrate-cloudformation.html" target="_blank" rel="noopener">enable stack sets for your organization</a> before you proceed.</p></blockquote><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">AccountPasswordPolicy:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFormation::StackSet&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AutoDeployment:</span></span><br><span class="line">        <span class="attr">Enabled:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">RetainStacksOnAccountRemoval:</span> <span class="literal">false</span></span><br><span class="line">      <span class="comment"># CallAs: DELEGATED_ADMIN # TODO uncomment line if you use an delegated administrator account to manage stack sets in your organization</span></span><br><span class="line">      <span class="attr">Capabilities:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;CAPABILITY_IAM&#x27;</span></span><br><span class="line">      <span class="attr">PermissionModel:</span> <span class="string">SERVICE_MANAGED</span></span><br><span class="line">      <span class="attr">StackInstancesGroup:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">DeploymentTargets:</span></span><br><span class="line">          <span class="attr">OrganizationalUnitIds:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;r-xxxx&#x27;</span> <span class="comment"># TODO replace with your organization id or unit id ou-xxxx-xxxxxxxx</span></span><br><span class="line">        <span class="attr">Regions:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&#x27;us-east-1&#x27;</span></span><br><span class="line">      <span class="attr">StackSetName:</span> <span class="string">&#x27;account-password-policy&#x27;</span></span><br><span class="line">      <span class="attr">TemplateURL:</span> <span class="type">!Sub</span> <span class="string">&#x27;https://s3-eu-west-1.amazonaws.com/widdix-aws-cf-templates-releases-eu-west-1/v13.23.0/security/account-password-policy.yaml&#x27;</span></span><br></pre></td></tr></table></figure><p>Just deploy a stack based on the above template in your organization’s management account (or delegated admin account), and that’s it.</p><p>Things that I like about the approach:</p><ul><li>Whenever you add a new account to your organization, the stack set will deploy the CloudFormation stack automatically.</li><li>The CloudFormation template lets me easily update all stack sets and stacks by bumping the version in the <code>TemplateURL</code>.</li><li>Existing stack sets can be <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resource-import.html" target="_blank" rel="noopener">imported into CloudFormation</a> to migrate manually managed stack sets.</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>Review: Aurora Serverless v2</title>
      <link>https://cloudonaut.io/review-aurora-serverless-v2/</link>
      <description>
        <![CDATA[<p>I was excited when AWS announced Aurora Serverless at re:Invent 2017. Disappointment followed shortly after. Even after Aurora Serverless]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/rds/">rds</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/review-aurora-serverless-v2/</guid>
      <pubDate>Wed, 04 May 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>I was excited when AWS announced Aurora Serverless at re:Invent 2017. Disappointment followed shortly after. Even after Aurora Serverless became a generally available service in August 2018, it missed important features like multi-AZ deployments and read replication. Unfortunately, the innovative service never achieved a breakthrough. Therefore, I used Aurora Serverless in exceptional cases only. Four years later, AWS is making a fresh start with Aurora Serverless v2. Reason enough to take a closer look at the new service.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/05/magnifying-glass@730w.webp 730w, /images/2022/05/magnifying-glass@730w2x.webp 1460w, /images/2022/05/magnifying-glass@610w.webp 610w, /images/2022/05/magnifying-glass@610w2x.webp 1220w, /images/2022/05/magnifying-glass@450w.webp 450w, /images/2022/05/magnifying-glass@450w2x.webp 900w, /images/2022/05/magnifying-glass@330w.webp 330w, /images/2022/05/magnifying-glass@330w2x.webp 660w, /images/2022/05/magnifying-glass@545w.webp 545w, /images/2022/05/magnifying-glass@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/05/magnifying-glass@730w.jpg 730w, /images/2022/05/magnifying-glass@730w2x.jpg 1460w, /images/2022/05/magnifying-glass@610w.jpg 610w, /images/2022/05/magnifying-glass@610w2x.jpg 1220w, /images/2022/05/magnifying-glass@450w.jpg 450w, /images/2022/05/magnifying-glass@450w2x.jpg 900w, /images/2022/05/magnifying-glass@330w.jpg 330w, /images/2022/05/magnifying-glass@330w2x.jpg 660w, /images/2022/05/magnifying-glass@545w.jpg 545w, /images/2022/05/magnifying-glass@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/05/magnifying-glass.jpg" alt="Review: Aurora Serverless v2" title="Review: Aurora Serverless v2"></picture></p><p>Before we start, let me explain why Aurora Serverless is a big deal.</p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/46-review-aurora-serverless-v2/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="Why-is-Aurora-Serverless-a-big-deal"><a href="#Why-is-Aurora-Serverless-a-big-deal" class="headerlink" title="Why is Aurora Serverless a big deal?"></a>Why is Aurora Serverless a big deal?</h2><p>Scaling relational databases was complicated. With Aurora, AWS provides a scalable and highly available storage layer for relational databases. No more need to provision storage upfront. The storage layer grows on-demand. Also, the storage layer replicates among availability zones.</p><p>But how to scale compute capacity of a relational database? One option is to add additional machines to the cluster, acting as read replicas. However, that works when scaling capacity for read requests only. The other option is to scale vertically by switching to a database machine with more CPU and memory. However, doing so causes a short downtime.</p><p>With Aurora Serverless, you can vertically scale a database machine. The CPU and memory capacity automatically adapts to the current workload in the background without causing downtime. That’s a game-changer because you no longer need to provision database machines based on load peaks. Think about the enormous cost savings for workloads with high load peaks and significant idle periods.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/05/aurora-serverless-v2-overview@730w.webp 730w, /images/2022/05/aurora-serverless-v2-overview@730w2x.webp 1460w, /images/2022/05/aurora-serverless-v2-overview@610w.webp 610w, /images/2022/05/aurora-serverless-v2-overview@610w2x.webp 1220w, /images/2022/05/aurora-serverless-v2-overview@450w.webp 450w, /images/2022/05/aurora-serverless-v2-overview@450w2x.webp 900w, /images/2022/05/aurora-serverless-v2-overview@330w.webp 330w, /images/2022/05/aurora-serverless-v2-overview@330w2x.webp 660w, /images/2022/05/aurora-serverless-v2-overview@545w.webp 545w, /images/2022/05/aurora-serverless-v2-overview@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/05/aurora-serverless-v2-overview@730w.png 730w, /images/2022/05/aurora-serverless-v2-overview@730w2x.png 1460w, /images/2022/05/aurora-serverless-v2-overview@610w.png 610w, /images/2022/05/aurora-serverless-v2-overview@610w2x.png 1220w, /images/2022/05/aurora-serverless-v2-overview@450w.png 450w, /images/2022/05/aurora-serverless-v2-overview@450w2x.png 900w, /images/2022/05/aurora-serverless-v2-overview@330w.png 330w, /images/2022/05/aurora-serverless-v2-overview@330w2x.png 660w, /images/2022/05/aurora-serverless-v2-overview@545w.png 545w, /images/2022/05/aurora-serverless-v2-overview@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/05/aurora-serverless-v2-overview.png" alt="Aurora Serverless v2 scales storage and compute layer on-demand" title="Aurora Serverless v2 scales storage and compute layer on-demand"></picture></p><p>In summary, Aurora Serverless brings a relational database closer to DynamoDB’s pay-per-request pricing model.</p><p>Let’s dive into the review next.</p><h2 id="Multi-AZ-and-Read-Replication"><a href="#Multi-AZ-and-Read-Replication" class="headerlink" title="Multi-AZ and Read Replication"></a>Multi-AZ and Read Replication</h2><p>When <a href="/review-amazon-aurora-serverless/">reviewing Aurora Serverless v1</a>, I could not believe AWS offered a database service that does not span multiple availability zones. Also, the missing support for read replicas was a bummer. Luckily, AWS addressed both criticisms.</p><p>Aurora Serverless v2 comes with support for multi-AZ and read replication. You start by creating an Aurora cluster to configure and provision the storage layer. Next, you add database instances to the cluster. A database instance can either be a provisioned instance, <code>db.t4g.medium</code> for example, or a serverless instance. It is even possible to mix provisioned and serverless instances within a cluster. One of the instances becomes the write node, all other instances act as read nodes.</p><p>So, Aurora Serverless v2 is the breakthrough I’ve been waiting for for years.</p><p>Unfortunately, there are some aspects of the new service that I do not like. I will discuss them next.</p><h2 id="Migrating-from-Aurora-Serverless-v1-to-v2"><a href="#Migrating-from-Aurora-Serverless-v1-to-v2" class="headerlink" title="Migrating from Aurora Serverless v1 to v2"></a>Migrating from Aurora Serverless v1 to v2</h2><p>Even though Aurora Serverless v1 did not support multi-AZ deployments, we used it in some scenarios where we could not resist because of the ability to scale the database down to 0. Although AWS brags about its customer obsession whenever possible, there is no way to upgrade from Aurora Serverless v1 to v2 without downtime and with little effort. However, AWS allows us, customers, to switch from provisioned instances to serverless instances without downtime in minutes. I’m sure it’s just a coincidence that this is also the direction that generates more revenue for AWS. More on the pricing model of Aurora Serverless v2 later.</p><p>To migrate from Aurora Serverless v1 to v2, you need to create a snapshot of v1 and launch a v2 cluster based on the snapshot. Most likely, you will need to stop write requests during this time.</p><h2 id="Serverless-without-Data-API"><a href="#Serverless-without-Data-API" class="headerlink" title="Serverless without Data API!?"></a>Serverless without Data API!?</h2><p>I prefer using DynamoDB when building a serverless application. However, many developers are familiar with relational databases, and not everyone is expecting web-scale traffic. So, there are arguments for adding a relational database to a serverless architecture. But, connecting a Lambda function with a relational database is tricky. Managing a pool of database connections from Lambda is the first challenge. The second challenge is avoiding too many database connections when scaling out the Lambda execution environments.</p><p>In summary, long-lived database connections are challenging when using Lambda. That’s why Aurora Serverless v1 introduced a Data API, which offers an HTTPS endpoint to insert, update, delete, or query data. The Data API was a natural fit for serverless applications, and I enjoyed using it for some projects.</p><p>Unfortunately, Aurora Serverless v2 does not provide a Data API. Also, I could not get any information on whether releasing the Data API feature is planned for the future.</p><p>Using an RDS Proxy solves one part of the problem: the proxy allows a Lambda to share database connections between different executions, environments, and functions. However, you still have to manage the database connections for an execution environment.</p><h2 id="Aurora-Serverless-v2-is-not-a-good-fit-for-tiny-workloads"><a href="#Aurora-Serverless-v2-is-not-a-good-fit-for-tiny-workloads" class="headerlink" title="Aurora Serverless v2 is not a good fit for tiny workloads"></a>Aurora Serverless v2 is not a good fit for tiny workloads</h2><p>Aurora Serverless v1 was a good fit for workloads, with little traffic and massive idle periods. For example, Michael is running a small application for accounting, which he logs into once a day for a few minutes. Or another example, one of our consulting clients runs an application that gets used once a week for about an hour. Aurora Serverless v1 was able to pause the compute layer. The next incoming request resumed the database automatically within about 60 seconds.</p><p>Unfortunately, Aurora Serverless v2 does not support pausing the compute layer. The baseline costs for Aurora Serverless v2 are about $43 per month. Therefore, it is not a good fit for tiny workloads.</p><h2 id="Aurora-Serverless-v2-is-too-expensive"><a href="#Aurora-Serverless-v2-is-too-expensive" class="headerlink" title="Aurora Serverless v2 is too expensive!"></a>Aurora Serverless v2 is too expensive!</h2><p>In general, I think Aurora Serverless v2 is too expensive. Let me explain why with a chart. The following chart shows the monthly costs for the compute layer of an Aurora cluster running in <code>us-east-1</code>. I’m calculating with 720 hours per month and a <code>db.r6g.large</code>, which equals 8 Serverless ACUs. The chart shows the costs depending on the idle period in percent. Watch out where the lines of Serverless and Provisioned are crossing.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/05/aurora-pricing-comparison@730w.webp 730w, /images/2022/05/aurora-pricing-comparison@730w2x.webp 1460w, /images/2022/05/aurora-pricing-comparison@610w.webp 610w, /images/2022/05/aurora-pricing-comparison@610w2x.webp 1220w, /images/2022/05/aurora-pricing-comparison@450w.webp 450w, /images/2022/05/aurora-pricing-comparison@450w2x.webp 900w, /images/2022/05/aurora-pricing-comparison@330w.webp 330w, /images/2022/05/aurora-pricing-comparison@330w2x.webp 660w, /images/2022/05/aurora-pricing-comparison@545w.webp 545w, /images/2022/05/aurora-pricing-comparison@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/05/aurora-pricing-comparison@730w.png 730w, /images/2022/05/aurora-pricing-comparison@730w2x.png 1460w, /images/2022/05/aurora-pricing-comparison@610w.png 610w, /images/2022/05/aurora-pricing-comparison@610w2x.png 1220w, /images/2022/05/aurora-pricing-comparison@450w.png 450w, /images/2022/05/aurora-pricing-comparison@450w2x.png 900w, /images/2022/05/aurora-pricing-comparison@330w.png 330w, /images/2022/05/aurora-pricing-comparison@330w2x.png 660w, /images/2022/05/aurora-pricing-comparison@545w.png 545w, /images/2022/05/aurora-pricing-comparison@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/05/aurora-pricing-comparison.png" alt="Comparing costs for Serverless v1, Serverless v2, Provisioned (On-demand), and Provisioned (Reserved)" title="Comparing costs for Serverless v1, Serverless v2, Provisioned (On-demand), and Provisioned (Reserved)"></picture></p><p>So using Aurora Serverless v2 makes sense for workloads that are idling for more than 77% of the time compared to on-demand instances. Or even worse, only for workloads idling more than 96% of the time compared to reserved instances with a three-year term and all-upfront payment.</p><p>That’s a showstopper for most scenarios, in my opinion. I’m afraid Aurora Serverless will fail not because of technical obstacles but because of the pricing model this time.</p><h2 id="Service-Maturity-Table"><a href="#Service-Maturity-Table" class="headerlink" title="Service Maturity Table"></a>Service Maturity Table</h2><p>Last but not least, I would like to evaluate the service maturity of Aurora Serverless v2.</p><table class="table table-striped table-responsive"><thead><tr><th>Criteria</th><th>Summary</th><th style="text-align:right">Score</th></tr></thead><tbody><tr><td>Feature Completeness</td><td>✅</td><td style="text-align:right">8</td></tr><tr><td>Documentation detailedness</td><td>✅</td><td style="text-align:right">8</td></tr><tr><td>Tags (Grouping + Billing)<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup></td><td>⚠️</td><td style="text-align:right">5</td></tr><tr><td>CloudFormation + Terraform support<sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup></td><td>⚠️</td><td style="text-align:right">5</td></tr><tr><td>Emits CloudWatch Events</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>IAM granularity</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>Integrated with AWS Config</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>Auditing via AWS CloudTrail</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>Available in all commercial regions</td><td>✅</td><td style="text-align:right">8</td></tr><tr><td>SLA</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>Compliance (ISO, SOC HIPAA)</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td><strong>Total Maturity Score (0-10)</strong></td><td>⚠️</td><td style="text-align:right"><strong>8.5</strong></td></tr></tbody></table><p>Our maturity score for Aurora Serverless v2 is 8.5 on a scale from 0 to 10. In my opinion, Aurora Serverless v2 is ready for production workloads. However, the very high price is why I will decide against Aurora Serverless v2. Also, I did not migrate one of our workloads from Aurora Serverless v1 to v2 because of missing CloudFormation support.</p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. Filtering by tags in the Billing and Cost Management console shows zero usage. <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. CloudFormation support for Aurora Serverless v2 is missing. <a href="#fnref:2" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Launching cloudonaut talent</title>
      <link>https://cloudonaut.io/launching-cloudonaut-talent/</link>
      <description>
        <![CDATA[<p>We launched our blog cloudonaut in 2015, and published more than 340 articles and started to record and publish a podcast episodes and vi]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/launching-cloudonaut-talent/</guid>
      <pubDate>Thu, 28 Apr 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>We launched our blog cloudonaut in 2015, and published more than 340 articles and started to record and publish a podcast episodes and videos since then. However, cloudonaut was always a side project for us. Because we would like to put more energy into our content for the AWS community, we have been looking to turn our side project into a business. Over the last few months, we have been experimenting with different approaches.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/04/talent@730w.webp 730w, /images/2022/04/talent@730w2x.webp 1460w, /images/2022/04/talent@610w.webp 610w, /images/2022/04/talent@610w2x.webp 1220w, /images/2022/04/talent@450w.webp 450w, /images/2022/04/talent@450w2x.webp 900w, /images/2022/04/talent@330w.webp 330w, /images/2022/04/talent@330w2x.webp 660w, /images/2022/04/talent@545w.webp 545w, /images/2022/04/talent@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/04/talent@730w.jpg 730w, /images/2022/04/talent@730w2x.jpg 1460w, /images/2022/04/talent@610w.jpg 610w, /images/2022/04/talent@610w2x.jpg 1220w, /images/2022/04/talent@450w.jpg 450w, /images/2022/04/talent@450w2x.jpg 900w, /images/2022/04/talent@330w.jpg 330w, /images/2022/04/talent@330w2x.jpg 660w, /images/2022/04/talent@545w.jpg 545w, /images/2022/04/talent@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/04/talent.jpg" alt="We are launching cloudonaut talent" title="We are launching cloudonaut talent"></picture></p><p>Currently, we fund cloudonaut through community support. We would like to take this opportunity to thank you for your regular and one-time donations and T-shirt purchases. Keep your motivating support coming!</p><p>Today we would like to announce a second pillar for cloudonaut’s funding: <strong>cloudonaut talent</strong>. With <em>cloudonaut talent</em>, we give employers the opportunity to reach out to AWS professionals and attract talent. Therefore, you will notice two changes at cloudonaut.</p><ul><li>We add <strong>job ads</strong> to the blog, the newsletter, and the podcast. </li><li>We do <strong>content collaborations</strong> that give you not only technical details about AWS but also an insight into the day-to-day work at our partners.</li></ul><p>We could already win the first partner for <em>cloudonaut talent</em>. Starting in May, we will help tecRacer, a Premier AWS Consulting Partner from Germany, to fill vacancies.</p><blockquote><p>Looking for AWS talent to join your organization? Please contact us!</p></blockquote>]]>
      </content:encoded>
    </item>
    <item>
      <title>Amazon Web Services in Action 3rd Edition: Early Access</title>
      <link>https://cloudonaut.io/aws-in-action-3rd-edition-early-access/</link>
      <description>
        <![CDATA[<p>About seven years ago, Michael and I started to write a book about Amazon Web Services. We were beginning our consulting business, and wr]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-in-action-3rd-edition-early-access/</guid>
      <pubDate>Wed, 30 Mar 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>About seven years ago, Michael and I started to write a book about Amazon Web Services. We were beginning our consulting business, and writing a book seemed like an excellent way to demonstrate our expertise. Our publisher Manning launched our book Amazon Web Services in Action in September 2015, shortly before re:Invent. At that time, we could not have guessed where this would lead us. Over the years, our book helped tens of thousands of people get started with AWS. Today I’m pleased to announce that we are launching the early access program for the <a href="https://www.manning.com/books/amazon-web-services-in-action-third-edition?utm_source=mwittig&utm_medium=affiliate&utm_campaign=book_wittig2_amazon_3_25_22&a_aid=mwittig&a_bid=7e8f8a13" target="_blank" rel="noopener">3rd edition of our book Amazon Web Services in Action</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/03/awsinaction-3rd-meap-mock@730w.webp 730w, /images/2022/03/awsinaction-3rd-meap-mock@730w2x.webp 1460w, /images/2022/03/awsinaction-3rd-meap-mock@610w.webp 610w, /images/2022/03/awsinaction-3rd-meap-mock@610w2x.webp 1220w, /images/2022/03/awsinaction-3rd-meap-mock@450w.webp 450w, /images/2022/03/awsinaction-3rd-meap-mock@450w2x.webp 900w, /images/2022/03/awsinaction-3rd-meap-mock@330w.webp 330w, /images/2022/03/awsinaction-3rd-meap-mock@330w2x.webp 660w, /images/2022/03/awsinaction-3rd-meap-mock@545w.webp 545w, /images/2022/03/awsinaction-3rd-meap-mock@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/03/awsinaction-3rd-meap-mock@730w.jpg 730w, /images/2022/03/awsinaction-3rd-meap-mock@730w2x.jpg 1460w, /images/2022/03/awsinaction-3rd-meap-mock@610w.jpg 610w, /images/2022/03/awsinaction-3rd-meap-mock@610w2x.jpg 1220w, /images/2022/03/awsinaction-3rd-meap-mock@450w.jpg 450w, /images/2022/03/awsinaction-3rd-meap-mock@450w2x.jpg 900w, /images/2022/03/awsinaction-3rd-meap-mock@330w.jpg 330w, /images/2022/03/awsinaction-3rd-meap-mock@330w2x.jpg 660w, /images/2022/03/awsinaction-3rd-meap-mock@545w.jpg 545w, /images/2022/03/awsinaction-3rd-meap-mock@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/03/awsinaction-3rd-meap-mock.jpg" alt="Amazon Web Services in Action, 3rd edition out now!" title="Amazon Web Services in Action, 3rd edition out now!"></picture></p><p>AWS in Action reveals best practices for security, high availability, and scalability on AWS, the world’s most popular cloud. It’s packed with techniques for automating your deployment, management, and infrastructure using Infrastructure as Code.</p><p>With the third edition, we are revising all 17 chapters. AWS has made significant progress since the second edition in 2018. As a result, there are countless new features that we need to incorporate into the third edition. Of course, we are also updating all the examples.</p><p>The most significant change is a new chapter: <em>Building modern architecture for the cloud: AppRunner, ECS, and Fargate</em>. The brand-new chapter is about deploying a web application by using containers. We start with a simple example based on AppRunner and end the chapter with a cloud-native architecture based on ALB, ECS, Fargate, and S3. We also rewrote chapter 15 <em>Automating deployment: CloudFormation, CodeDeploy, Packer</em> to provide you the tools to deploy your applications to AWS.</p><p>Get early access to the 3rd Edition of Amazon Web Services in Action. The Manning Early Access Program (MEAP) gives you access to our book while we are still writing and updating chapters. We will release additional chapters frequently. Of course, you will also get access to the final version of the book. On top of that, Manning grants you access to the 2nd edition for free.</p><p>The table of contents shows the current state of Amazon Web Services 3rd Edition:</p><ol><li>✅ What is Amazon Web Services?</li><li>✅ A simple example: WordPress in five minutes</li><li>✅ Using virtual machines: EC2</li><li>✅ Programming your infrastructure: The command-line, SDKs, and CloudFormation</li><li>✅ Securing your system: IAM, security groups, and VPC</li><li>✅️ Automating operational tasks with Lambda</li><li>✅ Storing your objects: S3 and Glacier</li><li>✅ Storing data on hard drives: EBS and instance store</li><li>✅ Sharing data volumes between machines: EFS</li><li>✅ Using a relational database service: RDS</li><li>✅ Caching data in memory: Amazon ElastiCache</li><li>✅ Programming for the NoSQL database service: DynamoDB</li><li>✅️ Achieving high availability: availability zones, auto-scaling, and CloudWatch</li><li>✅️ Decoupling your infrastructure: Elastic Load Balancing and Simple Queue Service</li><li>✅ Automating deployment: CloudFormation, CodeDeploy, Packer</li><li>✅️ Designing for fault tolerance</li><li>✅️ Scaling up and down: auto-scaling and CloudWatch</li><li>✅️ Building modern architectures for the cloud: ECS and Fargate</li></ol><p>✍️ &#x3D; Work in progress<br>🕵️‍ &#x3D; Review and Editing<br>✅ &#x3D; Available for reading via MEAP</p><blockquote><p>Get your copy of <a href="https://www.manning.com/books/amazon-web-services-in-action-third-edition?utm_source=mwittig&utm_medium=affiliate&utm_campaign=book_wittig2_amazon_3_25_22&a_aid=mwittig&a_bid=7e8f8a13" target="_blank" rel="noopener">Amazon Web Services in Action 3rd Edition</a> today. Use coupon code <code>mlwittig5</code> for 50% off, valid through April 11, 2022.</p></blockquote><p>We look forward to old and new readers and your feedback! Also, please share this news with your network.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Application Authentication and Authorization on AWS</title>
      <link>https://cloudonaut.io/application-authentication-and-authorization-on-aws/</link>
      <description>
        <![CDATA[<p>In this blog post, you will learn to implement authentication and authorization for your own HTTP(S)-based applications on AWS.</p>
<p>Mo]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/apigateway/">apigateway</category>
      <category domain="https://cloudonaut.io/tag/alb/">alb</category>
      <category domain="https://cloudonaut.io/tag/cognito/">cognito</category>
      <guid isPermaLink="true">https://cloudonaut.io/application-authentication-and-authorization-on-aws/</guid>
      <pubDate>Wed, 16 Mar 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>In this blog post, you will learn to implement authentication and authorization for your own HTTP(S)-based applications on AWS.</p><p>Most applications offer some functionality only to authenticated clients. A client can be a human or a machine. Humans usually authenticate with username, password, and optionally a time-based one-time (TOTP) password. Machine authentication works differently. E.g., using API keys or certificates (mTLS). Authentication answer the question of who you are.</p><p>Some applications allow more granular control to allow or deny certain functionality to a subset of clients only, aka authorization. Authorization answers the question of what you are allowed to do.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/03/authenticate@730w.webp 730w, /images/2022/03/authenticate@730w2x.webp 1460w, /images/2022/03/authenticate@610w.webp 610w, /images/2022/03/authenticate@610w2x.webp 1220w, /images/2022/03/authenticate@450w.webp 450w, /images/2022/03/authenticate@450w2x.webp 900w, /images/2022/03/authenticate@330w.webp 330w, /images/2022/03/authenticate@330w2x.webp 660w, /images/2022/03/authenticate@545w.webp 545w, /images/2022/03/authenticate@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/03/authenticate@730w.jpg 730w, /images/2022/03/authenticate@730w2x.jpg 1460w, /images/2022/03/authenticate@610w.jpg 610w, /images/2022/03/authenticate@610w2x.jpg 1220w, /images/2022/03/authenticate@450w.jpg 450w, /images/2022/03/authenticate@450w2x.jpg 900w, /images/2022/03/authenticate@330w.jpg 330w, /images/2022/03/authenticate@330w2x.jpg 660w, /images/2022/03/authenticate@545w.jpg 545w, /images/2022/03/authenticate@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/03/authenticate.jpg" alt="Application Authentication and Authorization on AWS" title="Application Authentication and Authorization on AWS"></picture></p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><h2 id="Authenticating-users"><a href="#Authenticating-users" class="headerlink" title="Authenticating users"></a>Authenticating users</h2><p>Nowadays, users enjoy signing in with already existing user credentials. For example, with their Google, Facebook, or corporate identity. Whether the application is internal or external, users expect to sign in using an Identity Provider (IdP) of their choice. This could be your corporate IdP, Google, or Facebook. The simplified flow looks like this: When a user uses your application for the first time, the user is redirected to the IdP. The IdP handles the authentication and redirects the user back to your application with some sort of token.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/03/auth-flow-idp@730w.webp 730w, /images/2022/03/auth-flow-idp@730w2x.webp 1460w, /images/2022/03/auth-flow-idp@610w.webp 610w, /images/2022/03/auth-flow-idp@610w2x.webp 1220w, /images/2022/03/auth-flow-idp@450w.webp 450w, /images/2022/03/auth-flow-idp@450w2x.webp 900w, /images/2022/03/auth-flow-idp@330w.webp 330w, /images/2022/03/auth-flow-idp@330w2x.webp 660w, /images/2022/03/auth-flow-idp@545w.webp 545w, /images/2022/03/auth-flow-idp@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/03/auth-flow-idp@730w.png 730w, /images/2022/03/auth-flow-idp@730w2x.png 1460w, /images/2022/03/auth-flow-idp@610w.png 610w, /images/2022/03/auth-flow-idp@610w2x.png 1220w, /images/2022/03/auth-flow-idp@450w.png 450w, /images/2022/03/auth-flow-idp@450w2x.png 900w, /images/2022/03/auth-flow-idp@330w.png 330w, /images/2022/03/auth-flow-idp@330w2x.png 660w, /images/2022/03/auth-flow-idp@545w.png 545w, /images/2022/03/auth-flow-idp@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/03/auth-flow-idp.png" alt="Authenticating users" title="Authenticating users"></picture></p><p>Your application must integrate with the IdP to verify that the token is valid. The authentication itself is done by the IdP. Luckily, most IdPs implement the standard OpenID Connect (OIDC), which improves the integration challenge slightly.</p><p>If you want your own user database (aka IdP), <a href="https://aws.amazon.com/cognito/" target="_blank" rel="noopener">Amazon Cognito</a> user pool is the service of choice. Cognito also integrates with other IdPs to offer maximum flexibility.</p><p>Luckily, some AWS services deal with implementing the IdP integration for you:</p><ul><li>Amazon API Gateway<ul><li>REST: Validates OIDC JWT token issued by Cognito user pool only.</li><li>HTTP: Validates OIDC JWT token (includes Cognito user pool issued tokens).</li></ul></li><li>AppSync: Validates OIDC JWT token (includes Cognito user pool issued tokens).</li><li>Application Load Balancer (ALB): Implements the complete OIDC authentication flow by redirecting the user to the IdP and remembering the token in a cookie (includes Cognito user pools).</li></ul><p><strong>Hint</strong>: If your application is a SPA, you can use AWS Amplify to implement the authentication flow in the browser.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/03/auth-flow-aws@730w.webp 730w, /images/2022/03/auth-flow-aws@730w2x.webp 1460w, /images/2022/03/auth-flow-aws@610w.webp 610w, /images/2022/03/auth-flow-aws@610w2x.webp 1220w, /images/2022/03/auth-flow-aws@450w.webp 450w, /images/2022/03/auth-flow-aws@450w2x.webp 900w, /images/2022/03/auth-flow-aws@330w.webp 330w, /images/2022/03/auth-flow-aws@330w2x.webp 660w, /images/2022/03/auth-flow-aws@545w.webp 545w, /images/2022/03/auth-flow-aws@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/03/auth-flow-aws@730w.png 730w, /images/2022/03/auth-flow-aws@730w2x.png 1460w, /images/2022/03/auth-flow-aws@610w.png 610w, /images/2022/03/auth-flow-aws@610w2x.png 1220w, /images/2022/03/auth-flow-aws@450w.png 450w, /images/2022/03/auth-flow-aws@450w2x.png 900w, /images/2022/03/auth-flow-aws@330w.png 330w, /images/2022/03/auth-flow-aws@330w2x.png 660w, /images/2022/03/auth-flow-aws@545w.png 545w, /images/2022/03/auth-flow-aws@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/03/auth-flow-aws.png" alt="Authenticating user with ALB" title="Authenticating user with ALB"></picture></p><p>If you are interested in the details, check out our <a href="https://blog.cloudcraft.co/comparing-api-gateways-on-aws/" target="_blank" rel="noopener">Comparing API Gateways on AWS</a> article.</p><h2 id="Authenticating-machines"><a href="#Authenticating-machines" class="headerlink" title="Authenticating machines"></a>Authenticating machines</h2><p>Let’s divide this problem into two: Machines running inside AWS and machines running outside of AWS. A machine could be a virtual machine, container, or function.</p><p>Inside AWS, you can leverage the IAM service to authenticate (and also authorize) client machines. Your machine needs IAM credentials (e.g., via EC2 Instance Profiles or ECS Task Roles), sign requests by following <a href="https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html" target="_blank" rel="noopener">SigV4</a>, and the endpoint must be capable of verifying a SigV4 request. Only AWS has the keys to do this! The following AWS services support this:</p><ul><li>API Gateway (HTTP and REST)</li><li>AppSync</li></ul><p><strong>Hint</strong>: Users can also get AWS credentials by using Cognito identity pools (instead of user pools) to use the same authentication mechanism as machines do.</p><p>If your machines are EC2 instances, you can leverage signed <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html" target="_blank" rel="noopener">Instance Identity Documents</a> for authentication. Add the identity document with the signature to the Authorization header of your requests. The receiver must validate the signed document (e.g., in a custom authorizer or your application code).</p><p><a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/rest-api-mutual-tls.html" target="_blank" rel="noopener">API Gateway REST API supports mutual TLS (mTLS)</a>. With mTLS, clients must present X.509 certificates to verify their identity to access your API. It is still a challenge to make the certificates available to your clients safely and rotate them regularly.</p><p>In all other cases, you are on your own. I recommend checking out <a href="https://github.com/open-policy-agent/opa-envoy-plugin" target="_blank" rel="noopener">Envoy and OPA</a>. Envoy receives all requests and reaches out to OPA to make an authorization decision before the request is forwarded to the backend. The authorization decision is either based on the data on the request itself (e.g., verify JWT and check the groups claim) or more involved by taking external data into account.</p><h2 id="Authorization"><a href="#Authorization" class="headerlink" title="Authorization"></a>Authorization</h2><p>So far, we have two sets of clients. Unauthenticated and authenticated clients. If you need more fine granular control, you need a way to authorize requests based on:</p><ul><li>roles such as admin</li><li>or more fine-grained attributes such as resource owner</li></ul><p>During the authentication a client requests certain scopes, that it wants to access. The IdP authorizes the client for scopes and adds the corresponding claims -which are more fine granular- to the JWT token. For example, Cognito user pools support adding users to groups. The group membership is made available in the <code>cognito:groups</code> claim in the JWT. Your application can inspect the (already verified) JWT token to check the user’s groups.</p><p>Only the API Gateway HTTP APIs support authorization based on scopes. All of the other presented AWS services do not support making authorization decisions for you. As a workaround the API Gateway (REST and HTTP) provide <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html#http-api-lambda-authorizer.payload-format-response" target="_blank" rel="noopener">custom authorizers</a>. A custom authorizer generates an IAM policy with fine-grained control over the API endpoints (HTTP resource + verb) that the client can invoke. The policy can also be cached for latency-critical applications.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Authenticating users is a solved problem on AWS. The quickest way to get authentication working is ALB + Cognito user pool. You can also leverage Cognito user groups to implement a lightweight authorization layer in your application.</p><p>Machine-to-machine communication is where things get more complicated. Unfortunately, the ALB does not cover that use case. It gets even worse if some machines run inside AWS while others run outside.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to create a security group allowing traffic from CloudFront only?</title>
      <link>https://cloudonaut.io/cloudfront-prefix-list-security-group/</link>
      <description>
        <![CDATA[<p>It is one of those problems for which there has been no satisfactory solution for years. How do you ensure that only CloudFront is grante]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/cloudfront/">cloudfront</category>
      <guid isPermaLink="true">https://cloudonaut.io/cloudfront-prefix-list-security-group/</guid>
      <pubDate>Mon, 07 Mar 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>It is one of those problems for which there has been no satisfactory solution for years. How do you ensure that only CloudFront is granted access to an Elastic Load Balancer - CLB, ALB, or NLB? Without the ability to restrict incoming traffic, all of CloudFront’s network layer protection does little good. You will learn how to use AWS-managed prefix list for Amazon CloudFront in the following.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/03/wall@730w.webp 730w, /images/2022/03/wall@730w2x.webp 1460w, /images/2022/03/wall@610w.webp 610w, /images/2022/03/wall@610w2x.webp 1220w, /images/2022/03/wall@450w.webp 450w, /images/2022/03/wall@450w2x.webp 900w, /images/2022/03/wall@330w.webp 330w, /images/2022/03/wall@330w2x.webp 660w, /images/2022/03/wall@545w.webp 545w, /images/2022/03/wall@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/03/wall@730w.jpg 730w, /images/2022/03/wall@730w2x.jpg 1460w, /images/2022/03/wall@610w.jpg 610w, /images/2022/03/wall@610w2x.jpg 1220w, /images/2022/03/wall@450w.jpg 450w, /images/2022/03/wall@450w2x.jpg 900w, /images/2022/03/wall@330w.jpg 330w, /images/2022/03/wall@330w2x.jpg 660w, /images/2022/03/wall@545w.jpg 545w, /images/2022/03/wall@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/03/wall.jpg" alt="How to create a security group allowing traffic from CloudFront only?" title="How to create a security group allowing traffic from CloudFront only?"></picture></p><p>Until recently, when using a load balancer or similar endpoint as the origin for a CloudFront distribution, you had to allow incoming HTTPS traffic from anywhere (<code>0.0.0.0/0</code>). At least, there was no simple way to maintain a list with all the IP addresses used by the CloudFront edge locations worldwide.</p><p>Therefore, probably many other AWS customers and we ended up with the following situation. The load balancer was accessible not only from CloudFront but from anywhere. This made it possible to bypass CloudFront’s protective measures.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/03/cloudfront-without-prefix-list@730w.webp 730w, /images/2022/03/cloudfront-without-prefix-list@730w2x.webp 1460w, /images/2022/03/cloudfront-without-prefix-list@610w.webp 610w, /images/2022/03/cloudfront-without-prefix-list@610w2x.webp 1220w, /images/2022/03/cloudfront-without-prefix-list@450w.webp 450w, /images/2022/03/cloudfront-without-prefix-list@450w2x.webp 900w, /images/2022/03/cloudfront-without-prefix-list@330w.webp 330w, /images/2022/03/cloudfront-without-prefix-list@330w2x.webp 660w, /images/2022/03/cloudfront-without-prefix-list@545w.webp 545w, /images/2022/03/cloudfront-without-prefix-list@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/03/cloudfront-without-prefix-list@730w.png 730w, /images/2022/03/cloudfront-without-prefix-list@730w2x.png 1460w, /images/2022/03/cloudfront-without-prefix-list@610w.png 610w, /images/2022/03/cloudfront-without-prefix-list@610w2x.png 1220w, /images/2022/03/cloudfront-without-prefix-list@450w.png 450w, /images/2022/03/cloudfront-without-prefix-list@450w2x.png 900w, /images/2022/03/cloudfront-without-prefix-list@330w.png 330w, /images/2022/03/cloudfront-without-prefix-list@330w2x.png 660w, /images/2022/03/cloudfront-without-prefix-list@545w.png 545w, /images/2022/03/cloudfront-without-prefix-list@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/03/cloudfront-without-prefix-list.png" alt="ALB accessible from anywhere" title="ALB accessible from anywhere"></picture></p><p>Luckily, <a href="https://aws.amazon.com/about-aws/whats-new/2022/02/amazon-cloudfront-managed-prefix-list/" target="_blank" rel="noopener">AWS announced managed prefix lists for CloudFront</a> on February 7, 2022. The prefix list contains all IP ranges used by CloudFront edge locations. AWS updates the prefix list when needed. You can easily use the prefix list to restrict access when configuring a security group, as shown in the following figure.</p><p>This means that CloudFront’s protection measures can no longer be bypassed. It is ensured that all incoming traffic on the load balancer comes from CloudFront.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/03/cloudfront-with-prefix-list@730w.webp 730w, /images/2022/03/cloudfront-with-prefix-list@730w2x.webp 1460w, /images/2022/03/cloudfront-with-prefix-list@610w.webp 610w, /images/2022/03/cloudfront-with-prefix-list@610w2x.webp 1220w, /images/2022/03/cloudfront-with-prefix-list@450w.webp 450w, /images/2022/03/cloudfront-with-prefix-list@450w2x.webp 900w, /images/2022/03/cloudfront-with-prefix-list@330w.webp 330w, /images/2022/03/cloudfront-with-prefix-list@330w2x.webp 660w, /images/2022/03/cloudfront-with-prefix-list@545w.webp 545w, /images/2022/03/cloudfront-with-prefix-list@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/03/cloudfront-with-prefix-list@730w.png 730w, /images/2022/03/cloudfront-with-prefix-list@730w2x.png 1460w, /images/2022/03/cloudfront-with-prefix-list@610w.png 610w, /images/2022/03/cloudfront-with-prefix-list@610w2x.png 1220w, /images/2022/03/cloudfront-with-prefix-list@450w.png 450w, /images/2022/03/cloudfront-with-prefix-list@450w2x.png 900w, /images/2022/03/cloudfront-with-prefix-list@330w.png 330w, /images/2022/03/cloudfront-with-prefix-list@330w2x.png 660w, /images/2022/03/cloudfront-with-prefix-list@545w.png 545w, /images/2022/03/cloudfront-with-prefix-list@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/03/cloudfront-with-prefix-list.png" alt="ALB accessible from CloudFront only" title="ALB accessible from CloudFront only"></picture></p><p>However, keep in mind that anyone can create a CloudFront distribution. This does not guarantee that all the requests arriving at your load balancer originate from your CloudFront distribution. Check out <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-overview.html#forward-custom-headers-restrict-access" target="_blank" rel="noopener"></a> to learn how to restrict access at the application layer.</p><p>So how do you create a security group that only allows incoming traffic from CloudFront by using an AWS-managed prefix list? We will present the Terraform and CloudFormation code in the following.</p><p>The following snippet shows the Terraform code needed to create a security group that allows incoming HTTPS traffic from CloudFront only. The data source <code>aws_ec2_managed_prefix_list</code> fetches the ID of the prefix list by name.</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">data &quot;aws_ec2_managed_prefix_list&quot; &quot;cloudfront&quot; &#123;</span><br><span class="line"> name = &quot;com.amazonaws.global.cloudfront.origin-facing&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_security_group&quot; &quot;lb&quot; &#123;</span><br><span class="line"> name = &quot;loadbalancer&quot;</span><br><span class="line"> vpc_id = data.terraform_remote_state.vpc.outputs.vpc_id</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_security_group_rule&quot; &quot;lb_ingress_cloudfront&quot; &#123;</span><br><span class="line"> description = &quot;HTTPS from CloudFront&quot;</span><br><span class="line"> security_group_id = aws_security_group.lb.id</span><br><span class="line"> type = &quot;ingress&quot;</span><br><span class="line"> from_port = 443</span><br><span class="line"> to_port = 443</span><br><span class="line"> protocol = &quot;tcp&quot;</span><br><span class="line"> prefix_list_ids = [data.aws_ec2_managed_prefix_list.cloudfront.id]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Unfortunately, things are getting a little more complicated when using CloudFormation. The ID of the prefix list <code>aws_ec2_managed_prefix_list</code> varies between the regions. Luckily, it seems to be the same for all AWS accounts. Therefore, I’m using a mapping between the region and the prefix list for CloudFront in the following snippet.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Mappings:</span></span><br><span class="line"> <span class="attr">RegionMap:</span></span><br><span class="line"> <span class="attr">&#x27;af-south-1&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-c0aa4fa9&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;eu-north-1&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-fab65393&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;ap-south-1&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-9aa247f3&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;eu-west-3&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-75b1541c&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;eu-west-2&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-93a247fa&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;eu-south-1&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-1bbc5972&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;eu-west-1&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-4fa04526&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;ap-northeast-2&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-22a6434b&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;me-south-1&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-17b2577e&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;ap-northeast-1&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-58a04531&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;sa-east-1&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-5da64334&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;ca-central-1&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-38a64351&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;ap-east-1&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-14b2577d&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;ap-southeast-1&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-31a34658&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;ap-southeast-2&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-b8a742d1&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;eu-central-1&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-a3a144ca&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;us-east-1&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-3b927c52&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;us-east-2&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-b6a144df&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;us-west-1&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-4ea04527&#x27;</span></span><br><span class="line"> <span class="attr">&#x27;us-west-2&#x27;:</span></span><br><span class="line"> <span class="attr">PrefixListCloudFront:</span> <span class="string">&#x27;pl-82a045eb&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line"> <span class="attr">LoadBalancerSecurityGroup:</span></span><br><span class="line"> <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::SecurityGroup&#x27;</span></span><br><span class="line"> <span class="attr">Properties:</span></span><br><span class="line"> <span class="attr">GroupDescription:</span> <span class="string">&#x27;loadbalancer&#x27;</span></span><br><span class="line"> <span class="attr">VpcId:</span> <span class="string">&#x27;vpc-40e43c28&#x27;</span></span><br><span class="line"> <span class="attr">LoadBalancerSecurityGroupInWorld:</span></span><br><span class="line"> <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::SecurityGroupIngress&#x27;</span></span><br><span class="line"> <span class="attr">Properties:</span></span><br><span class="line"> <span class="attr">GroupId:</span> <span class="type">!Ref</span> <span class="string">LoadBalancerSecurityGroup</span></span><br><span class="line"> <span class="attr">IpProtocol:</span> <span class="string">tcp</span></span><br><span class="line"> <span class="attr">FromPort:</span> <span class="number">443</span></span><br><span class="line"> <span class="attr">ToPort:</span> <span class="number">443</span></span><br><span class="line"> <span class="attr">SourcePrefixListId:</span> <span class="type">!FindInMap</span> [<span class="string">&#x27;RegionMap&#x27;</span>, <span class="type">!Ref</span> <span class="string">&#x27;AWS::Region&#x27;</span>, <span class="string">&#x27;PrefixListCloudFront&#x27;</span>]</span><br></pre></td></tr></table></figure><blockquote><p>There is one stumbling block to consider. The prefix list is not available in <code>ap-northeast-3</code> and <code>ap-southeast-3</code>.</p></blockquote><p>I implemented this for one of our consulting clients and our open source project <a href="http://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">widdix&#x2F;aws-cf-templates</a> right away. I highly encourage you to do the same.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Sanction Russia: Block traffic using CloudFront Geo Restriction</title>
      <link>https://cloudonaut.io/sanction-russia-block-traffic-using-cloudfront-geo-restriction/</link>
      <description>
        <![CDATA[<p>Russia attacked a sovereign state this week. Most states condemn the attack and impose sanctions. Among other things, sanctions are inten]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/cloudfront/">cloudfront</category>
      <guid isPermaLink="true">https://cloudonaut.io/sanction-russia-block-traffic-using-cloudfront-geo-restriction/</guid>
      <pubDate>Fri, 25 Feb 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Russia attacked a sovereign state this week. Most states condemn the attack and impose sanctions. Among other things, sanctions are intended to mobilize the Russian population to rise up against their aristocrat Putin. As of today, <strong>cloudonaut</strong> is no longer accessible from Russia. We want to show solidarity with the Ukrainian people and make clear to the AWS community in Russia that we stand up for peace.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/02/sanction@730w.webp 730w, /images/2022/02/sanction@730w2x.webp 1460w, /images/2022/02/sanction@610w.webp 610w, /images/2022/02/sanction@610w2x.webp 1220w, /images/2022/02/sanction@450w.webp 450w, /images/2022/02/sanction@450w2x.webp 900w, /images/2022/02/sanction@330w.webp 330w, /images/2022/02/sanction@330w2x.webp 660w, /images/2022/02/sanction@545w.webp 545w, /images/2022/02/sanction@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/02/sanction@730w.jpg 730w, /images/2022/02/sanction@730w2x.jpg 1460w, /images/2022/02/sanction@610w.jpg 610w, /images/2022/02/sanction@610w2x.jpg 1220w, /images/2022/02/sanction@450w.jpg 450w, /images/2022/02/sanction@450w2x.jpg 900w, /images/2022/02/sanction@330w.jpg 330w, /images/2022/02/sanction@330w2x.jpg 660w, /images/2022/02/sanction@545w.jpg 545w, /images/2022/02/sanction@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/02/sanction.jpg" alt="Stop Russia!" title="Stop Russia!"></picture></p><p>We encourage you to follow us. If you use CloudFront as your CDN, the following CloudFormation and Terraform snippets enable <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/georestrictions.html#georestrictions-cloudfront" target="_blank" rel="noopener">CloudFront Geo Restriction</a> to block traffic from Russia.</p><p>Block traffic from Russia with CloudFormation:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">CloudFrontDistribution:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFront::Distribution&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">DistributionConfig:</span></span><br><span class="line">      <span class="comment"># [...]</span></span><br><span class="line">      <span class="attr">Restrictions:</span></span><br><span class="line">        <span class="attr">GeoRestriction:</span></span><br><span class="line">          <span class="attr">Locations:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">RU</span></span><br><span class="line">          <span class="attr">RestrictionType:</span> <span class="string">blacklist</span></span><br></pre></td></tr></table></figure><p>Block traffic from Russia with Terraform:</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_cloudfront_distribution&quot; &quot;distribution&quot; &#123;</span><br><span class="line">  # [...]</span><br><span class="line"></span><br><span class="line">  restrictions &#123;</span><br><span class="line">    geo_restriction &#123;</span><br><span class="line">      restriction_type = &quot;blacklist&quot;</span><br><span class="line">      locations        = [&quot;RU&quot;]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>With the geo restriction in place, visitors from Russia will receive a <code>403 (Forbidden)</code> response from CloudFront.</p><p>“When the power of love overcomes the love of power the world will know peace.” Jimi Hendrix</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Enabling S3 Versioning is not a backup strategy</title>
      <link>https://cloudonaut.io/enabling-s3-versioning-is-not-a-backup-strategy/</link>
      <description>
        <![CDATA[<p>Here are three reasons why enabling <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/Versioning.html" target="_blank" rel="]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <guid isPermaLink="true">https://cloudonaut.io/enabling-s3-versioning-is-not-a-backup-strategy/</guid>
      <pubDate>Wed, 23 Feb 2022 07:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Here are three reasons why enabling <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/Versioning.html" target="_blank" rel="noopener">S3 Versioning</a> is not a backup strategy. Instead, you should consider <a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/s3-backups.html" target="_blank" rel="noopener">AWS Backup for S3</a>, which AWS <a href="https://aws.amazon.com/about-aws/whats-new/2022/02/general-availability-aws-backup-amazon-s3/" target="_blank" rel="noopener">released on February 18th, 2022</a>. AWS Backup enables you to control and automate managing backups centrally. To do so, AWS Backup supports EC2&#x2F;EBS, EFS, FSx, RDS, DynamoDB, Neptune, DocumentDB, and Storage Gateway.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/02/disk@730w.webp 730w, /images/2022/02/disk@730w2x.webp 1460w, /images/2022/02/disk@610w.webp 610w, /images/2022/02/disk@610w2x.webp 1220w, /images/2022/02/disk@450w.webp 450w, /images/2022/02/disk@450w2x.webp 900w, /images/2022/02/disk@330w.webp 330w, /images/2022/02/disk@330w2x.webp 660w, /images/2022/02/disk@545w.webp 545w, /images/2022/02/disk@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/02/disk@730w.jpg 730w, /images/2022/02/disk@730w2x.jpg 1460w, /images/2022/02/disk@610w.jpg 610w, /images/2022/02/disk@610w2x.jpg 1220w, /images/2022/02/disk@450w.jpg 450w, /images/2022/02/disk@450w2x.jpg 900w, /images/2022/02/disk@330w.jpg 330w, /images/2022/02/disk@330w2x.jpg 660w, /images/2022/02/disk@545w.jpg 545w, /images/2022/02/disk@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/02/disk.jpg" alt="Use AWS Backup for S3 to avoid data loss!" title="Use AWS Backup for S3 to avoid data loss!"></picture></p><p>Do you use S3 Versioning to protect against data loss? Here are three reasons why this is not enough.</p><h2 id="1-Accidental-deletion"><a href="#1-Accidental-deletion" class="headerlink" title="#1 Accidental deletion"></a>#1 Accidental deletion</h2><p>So you enabled S3 Versioning for all your buckets. S3 will not delete the data but create a deletion marker for the object when someone deletes an object. So in case you delete an object accidentally, you can recover the data quickly. But what, when you delete not only the object but all of its versions accidentally? In this case, something comparable to an offsite backup becomes essential.</p><p>Think of AWS Backup as an offsite backup for your data stored on S3.</p><h2 id="2-Malicious-deletion"><a href="#2-Malicious-deletion" class="headerlink" title="#2 Malicious deletion"></a>#2 Malicious deletion</h2><p>And it can get even worse. What if an attacker tries to delete all data from your AWS account? An attacker also could delete all objects and versions. S3 Versioning does not mitigate the risk of malicious data deletion.</p><p>Again, consider AWS Backup for S3 instead. AWS Backup supports vault locks an effective countermeasures against the malicious deletion of your backup.</p><h2 id="3-Point-In-Time-Recovery"><a href="#3-Point-In-Time-Recovery" class="headerlink" title="#3 Point-In-Time Recovery"></a>#3 Point-In-Time Recovery</h2><p>Imagine you rolled out a change to the batch job, which processes data stored on S3 every night. Due to a mistake in the code, the batch job corrupts a lot of objects. As you have S3 Versioning enabled, you want to roll back all the objects within a bucket to a specific point-in-time. However, doing so requires a lot of API calls to S3 - <code>ListObjectVersions</code>, <code>GetObject</code>, and <code>PutObject</code>. Recovering to a certain point in time is quite complicated and error-prone.</p><p>AWS Backup for S3 comes with point-in-time recovery for S3 out-of-the-box. Recovering a bucket becomes quite simple.</p><h2 id="Unboxing-AWS-Backup-for-Amazon-S3"><a href="#Unboxing-AWS-Backup-for-Amazon-S3" class="headerlink" title="Unboxing AWS Backup for Amazon S3"></a>Unboxing AWS Backup for Amazon S3</h2><p>I hope I could convince you that AWS Backup for S3 has some advantages over S3 Versioning for backing up data. Check out my unboxing video, including a demo, pricing, and limitations.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=NCkcZHlIh-8">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/NCkcZHlIh-8" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p><em>Update 2022&#x2F;03&#x2F;02: Correction related to AWS Backup for S3: copying backups cross-region and cross-account is currently not supported. The Management Console let’s you create a backup job with a copy configuration, but it will fail. The <a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/whatisbackup.html#features-by-resource" target="_blank" rel="noopener">official documentation</a> mentions this limitation.</em></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Security: Stephen Kuenzli and Andreas Wittig on IAM</title>
      <link>https://cloudonaut.io/aws-security-stephen-kuenzli-and-andreas-wittig-on-iam/</link>
      <description>
        <![CDATA[<p>Stephen Kuenzli and I lead several cloud migration projects. In this conversation, we shared our learnings focusing on AWS security and I]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-security-stephen-kuenzli-and-andreas-wittig-on-iam/</guid>
      <pubDate>Tue, 15 Feb 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Stephen Kuenzli and I lead several cloud migration projects. In this conversation, we shared our learnings focusing on AWS security and IAM (Identity and Access Management). The result is advice and inspiration that will help you in your daily work. Our conversation is available as a video or podcast episode. In the following, you will also find a summary of our discussion.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/02/security@730w.webp 730w, /images/2022/02/security@730w2x.webp 1460w, /images/2022/02/security@610w.webp 610w, /images/2022/02/security@610w2x.webp 1220w, /images/2022/02/security@450w.webp 450w, /images/2022/02/security@450w2x.webp 900w, /images/2022/02/security@330w.webp 330w, /images/2022/02/security@330w2x.webp 660w, /images/2022/02/security@545w.webp 545w, /images/2022/02/security@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/02/security@730w.jpg 730w, /images/2022/02/security@730w2x.jpg 1460w, /images/2022/02/security@610w.jpg 610w, /images/2022/02/security@610w2x.jpg 1220w, /images/2022/02/security@450w.jpg 450w, /images/2022/02/security@450w2x.jpg 900w, /images/2022/02/security@330w.jpg 330w, /images/2022/02/security@330w2x.jpg 660w, /images/2022/02/security@545w.jpg 545w, /images/2022/02/security@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/02/security.jpg" alt="AWS Security" title="AWS Security"></picture></p><p>As Michael is on parental leave, I invited Stephen to the <a href="https://podcast.cloudonaut.io/" target="_blank" rel="noopener">cloudonaut podcast</a>. Listen to our conversation on AWS security and IAM. Stephen has written a book for engineers who design, develop, and review AWS IAM security policies in their daily work: <a href="https://www.effectiveiam.com/" target="_blank" rel="noopener">Effective IAM for Amazon Web Services</a>. Besides that, Stephen is the founder of <a href="https://www.k9security.io/" target="_blank" rel="noopener">k9 Security</a> with the mission to help organizations use the Cloud to improve security and manage risks to the organization and its customers.</p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/44-aws-security-with-stephen-kuenzli/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>For the first time, we recorded audio and video as well. Therefore, the podcast is available on YouTube as well.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=sgYu7H8Jlxc">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/sgYu7H8Jlxc" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p>You will find the distilled advice from my conversation with Stephen in the following.</p><h2 id="Use-AWS-accounts-to-isolate-workloads"><a href="#Use-AWS-accounts-to-isolate-workloads" class="headerlink" title="Use AWS accounts to isolate workloads"></a>Use AWS accounts to isolate workloads</h2><p>When multiple workloads are running in the same AWS account, writing IAM policies to separate those workloads from each other gets tricky. In theory, IAM supports ABAC (attribute-based access control), but although AWS made some progress in that area, not all resources and actions are supported. Also, implementing ABAC adds a lot of complexity. Therefore, our advice is to use multiple AWS accounts to isolate workloads from each other. For example, to separate test and production workloads. Or to provide a sandbox environment for developers.</p><p>We also learned that it could be tricky to get too granular when using AWS accounts to isolate workloads. Managing hundreds or thousands of AWS accounts becomes problematic, especially when network connectivity between workloads is required. So, as is so often the case, it’s all about getting the balance right.</p><h2 id="Do-not-use-AWS-managed-policies"><a href="#Do-not-use-AWS-managed-policies" class="headerlink" title="Do not use AWS managed policies"></a>Do not use AWS managed policies</h2><p>AWS provides IAM policies that you can use out-of-the-box. However, the AWS-managed policies are imprecise. First, the policies do not include resource-based restrictions. Second, the policies typically grant access to more actions than needed. For example, the <code>ReadOnly</code> policy is often used by 3rd party tools to access information about your infrastructure. Did you know that the <code>ReadOnly</code> policy not only grants access to list all your EC2 instances? It also gives access to read all the data stored in your S3 buckets?</p><p>Another problem with AWS-managed policies: they are subject to change. And you might not notice when AWS adds another service or action.</p><p>To reduce the blast radius of security incidents, you should implement the least privilege principle for machines (IAM roles) and humans (IAM users, groups, and roles). To do so, create your own IAM policies or use AWS-managed policies very carefully.</p><h2 id="How-to-create-an-IAM-policy"><a href="#How-to-create-an-IAM-policy" class="headerlink" title="How to create an IAM policy?"></a>How to create an IAM policy?</h2><p>But how to come up with an IAM policy? Stephen and I want to share some tips and tricks for different scenarios.</p><ol><li>Writing an IAM policy for a 3rd party application running on EC2, Fargate, or Lambda.</li><li>Check documentation for IAM policies. When available, review whether the policy follows the least privilege principle (validate actions, resources, and conditions).</li><li>Use CloudTrail - or even better <a href="https://cloudonaut.io/record-aws-api-calls-to-improve-iam-policies/">Client Side Monitoring (CSM)</a> - to get information about the actions and resources accessed by the application.</li><li>When working with open-source, search for AWS SDK usage within the code.</li><li>Writing an IAM policy for an application developed in-house.</li><li>Ask a developer for help to search the code for AWS API calls.</li><li>Search for AWS SDK usage within the code yourself.</li><li>Writing an IAM policy for humans (e.g., to grant developers access to AWS).</li><li>Restrict as little as possible (e.g., deny deleting databases or backups). Use an allowlist of services (e.g., SOC compliant).</li><li>Make sure you are not slowing down engineers, educate about security, and use monitoring instead.</li></ol><h2 id="Consider-KMS-key-policies-to-control-access"><a href="#Consider-KMS-key-policies-to-control-access" class="headerlink" title="Consider KMS key policies to control access"></a>Consider KMS key policies to control access</h2><p>Consider using KMS keys with key policies to restrict access to sensitive data. Think about encryption as a second layer of authorization. But be warned using customer-managed keys for encryption adds complexity. For example, you need to ensure that no one deletes the keys. Otherwise, your data will be lost, including the backups which use the same key for encryption.</p><h2 id="Build-reusable-components-to-scale-security"><a href="#Build-reusable-components-to-scale-security" class="headerlink" title="Build reusable components to scale security"></a>Build reusable components to scale security</h2><p>It is not realistic to expect every engineer to become an IAM expert. Therefore, build reusable components to generate IAM policies. For example, application X needs read access on bucket Y and write access on table Z. Alternatively, built-in IAM policies into your Infrastructure as Code components (see CDK or <a href="https://github.com/cfn-modules" target="_blank" rel="noopener">cfn-modules</a>).</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Connect to your EC2 instance using SSH the modern way</title>
      <link>https://cloudonaut.io/connect-to-your-ec2-instance-using-ssh-the-modern-way/</link>
      <description>
        <![CDATA[<p>Did you know that establishing an SSH connection with an EC2 instance is possible without configuring a key pair and allowing inbound tra]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/ssm/">ssm</category>
      <guid isPermaLink="true">https://cloudonaut.io/connect-to-your-ec2-instance-using-ssh-the-modern-way/</guid>
      <pubDate>Tue, 01 Feb 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Did you know that establishing an SSH connection with an EC2 instance is possible without configuring a key pair and allowing inbound traffic on port 22? How is that possible? The secret is a combination of EC2 Instance Connect and Systems Manager (SSM). When following this article, connecting with an EC2 instance is as simple as typing <code>ssh i-059499e6abc8fbe6b</code> into your terminal.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/02/terminal@730w.webp 730w, /images/2022/02/terminal@730w2x.webp 1460w, /images/2022/02/terminal@610w.webp 610w, /images/2022/02/terminal@610w2x.webp 1220w, /images/2022/02/terminal@450w.webp 450w, /images/2022/02/terminal@450w2x.webp 900w, /images/2022/02/terminal@330w.webp 330w, /images/2022/02/terminal@330w2x.webp 660w, /images/2022/02/terminal@545w.webp 545w, /images/2022/02/terminal@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/02/terminal@730w.jpg 730w, /images/2022/02/terminal@730w2x.jpg 1460w, /images/2022/02/terminal@610w.jpg 610w, /images/2022/02/terminal@610w2x.jpg 1220w, /images/2022/02/terminal@450w.jpg 450w, /images/2022/02/terminal@450w2x.jpg 900w, /images/2022/02/terminal@330w.jpg 330w, /images/2022/02/terminal@330w2x.jpg 660w, /images/2022/02/terminal@545w.jpg 545w, /images/2022/02/terminal@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/02/terminal.jpg" alt="How to set up Jenkins on AWS?" title="How to set up Jenkins on AWS?"></picture></p><p>First of all, the following video demonstrates how to establish an SSH connection with an EC2 instance by using EC2 Instance Connect and Systems Manager.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=w-yVPzSbb0c">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/w-yVPzSbb0c" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p>The following diagram explains the approach in more detail.</p><ul><li>The user sends her public key to EC2 Instance Connect using the AWS CLI.</li><li>EC2 Instance connect pushes the key to the EC2 instance. The key remains for 60 seconds.</li><li>An SSM agent running on the EC2 instance establishes a bidirectional channel with the SSM backend.</li><li>The user establishes an SSH connection through a Websocket between Terminal and SSM.</li><li>Authentication and authorization for the user and the SSM agent is IAM’s job.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/02/ec2-ssh-ic-ssm@730w.webp 730w, /images/2022/02/ec2-ssh-ic-ssm@730w2x.webp 1460w, /images/2022/02/ec2-ssh-ic-ssm@610w.webp 610w, /images/2022/02/ec2-ssh-ic-ssm@610w2x.webp 1220w, /images/2022/02/ec2-ssh-ic-ssm@450w.webp 450w, /images/2022/02/ec2-ssh-ic-ssm@450w2x.webp 900w, /images/2022/02/ec2-ssh-ic-ssm@330w.webp 330w, /images/2022/02/ec2-ssh-ic-ssm@330w2x.webp 660w, /images/2022/02/ec2-ssh-ic-ssm@545w.webp 545w, /images/2022/02/ec2-ssh-ic-ssm@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/02/ec2-ssh-ic-ssm@730w.png 730w, /images/2022/02/ec2-ssh-ic-ssm@730w2x.png 1460w, /images/2022/02/ec2-ssh-ic-ssm@610w.png 610w, /images/2022/02/ec2-ssh-ic-ssm@610w2x.png 1220w, /images/2022/02/ec2-ssh-ic-ssm@450w.png 450w, /images/2022/02/ec2-ssh-ic-ssm@450w2x.png 900w, /images/2022/02/ec2-ssh-ic-ssm@330w.png 330w, /images/2022/02/ec2-ssh-ic-ssm@330w2x.png 660w, /images/2022/02/ec2-ssh-ic-ssm@545w.png 545w, /images/2022/02/ec2-ssh-ic-ssm@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/02/ec2-ssh-ic-ssm.png" alt="Establishing an SSH connection with an EC2 instance by using EC2 Instance Connect and Systems Manager" title="Establishing an SSH connection with an EC2 instance by using EC2 Instance Connect and Systems Manager"></picture></p><p>How do you make this approach happen on your local machine and EC2 instances?</p><ul><li>Make sure the SSM agent is running on your EC2 instances - which is already the case for Amazon Linux and Amazon Linux 2.</li><li>Double check that the SSM agent running on your machines can connect with the SSM API through an internet gateway, NAT gateway, or VPC endpoint.</li><li>Attach an IAM role - granting the SSM agent access to the SSM API - to each EC2 instance to</li><li>Install the AWS CLI and the session manager plugin on your local machine.</li><li>Configure your SSH client to use EC2 Instance Connect and Systems Manager to establish a tunnel for SSH connections.</li></ul><blockquote><p>Are you confused about the different options to connect by using EC2 Instance Connect and Systems Manager? Check out Petri’s comparision: <a href="https://carriagereturn.nl/aws/ec2/ssh/connect/ssm/2019/07/26/connect.html" target="_blank" rel="noopener">EC2 Instance Connect vs. SSM Session Manager</a></p></blockquote><h2 id="IAM-role-required-by-SSM-agent"><a href="#IAM-role-required-by-SSM-agent" class="headerlink" title="IAM role required by SSM agent"></a>IAM role required by SSM agent</h2><p>As mentioned before, the SSM agent is running on most EC2 instances already. That’s because the agent is bundled into Amazon Linux, Amazon Linux 2, SUSE Linux Enterprise Server (12 and 15), and Ubuntu Server (16.04, 18.04, and 20.04) by default. </p><blockquote><p>Learn how to <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-manual-agent-install.html" target="_blank" rel="noopener">Manually install SSM Agent on EC2 instances for Linux</a> from the AWS documentation.</p></blockquote><p>However, the SSM agent requires an IAM role attached to the EC2 instance granting access to the SSM API. That’s not a default, so you probably need to create a new IAM role or add a policy to existing roles.</p><p>You can either use the predefined policy <code>AmazonSSMManagedInstanceCore</code> (<code>arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore</code>) managed by AWS. Or create your own managed or in-line policy with the following contents.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line"> <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2012-10-17&quot;</span>,</span><br><span class="line"> <span class="string">&quot;Statement&quot;</span>: [</span><br><span class="line"> &#123;</span><br><span class="line"> <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line"> <span class="string">&quot;Action&quot;</span>: [</span><br><span class="line"> <span class="string">&quot;ssmmessages:*&quot;</span>,</span><br><span class="line"> <span class="string">&quot;ec2messages:*&quot;</span>,</span><br><span class="line"> <span class="string">&quot;ssm:UpdateInstanceInformation&quot;</span></span><br><span class="line"> ],</span><br><span class="line"> <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;*&quot;</span></span><br><span class="line"> &#125;</span><br><span class="line"> ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Install-the-AWS-CLI-and-session-manager-plugin"><a href="#Install-the-AWS-CLI-and-session-manager-plugin" class="headerlink" title="Install the AWS CLI and session manager plugin"></a>Install the AWS CLI and session manager plugin</h2><p>You will use the AWS Command Line Interface (CLI) to push your public key via EC2 Instance Connect and establish a tunnel for your SSH connection with the EC2 instance.</p><p>Therefore, make sure to install the AWS CLI on your local machine. Besides that, you need to install the session manager plugin.</p><ul><li><a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" target="_blank" rel="noopener">Installing or updating the latest version of the AWS CLI</a></li><li><a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html" target="_blank" rel="noopener">Install the Session Manager plugin for the AWS CLI</a></li></ul><p>I prefer <code>brew</code> to install the AWS CLI and the session manager plugin on macOS.</p><figure class="highlight mipsasm"><table><tr><td class="code"><pre><span class="line"><span class="keyword">brew </span><span class="keyword">install </span>awscli session-manager-plugin</span><br></pre></td></tr></table></figure><h2 id="Configuring-your-SSH-client"><a href="#Configuring-your-SSH-client" class="headerlink" title="Configuring your SSH client"></a>Configuring your SSH client</h2><p>Next, you need to configure your SSH client. To do so, edit the <code>~/.ssh/config</code> file on your local machine. Please add the following snippet configuring connections for all hosts, starting with <code>i-</code> at the end of the file. Do not forget to replace <code>$PRIVATE_KEY</code> and <code>$PUBLIC_KEY</code> with the path to your private and public key.</p><figure class="highlight brainfuck"><table><tr><td class="code"><pre><span class="line"><span class="comment"># SSH over Session Manager</span></span><br><span class="line"><span class="comment">host i</span><span class="literal">-</span><span class="comment">*</span></span><br><span class="line"> <span class="comment">IdentityFile $PRIVATE_KEY</span></span><br><span class="line"> <span class="comment">User ec2</span><span class="literal">-</span><span class="comment">user</span></span><br><span class="line"> <span class="comment">ProxyCommand sh</span> <span class="literal">-</span><span class="comment">c &quot;aws ec2</span><span class="literal">-</span><span class="comment">instance</span><span class="literal">-</span><span class="comment">connect send</span><span class="literal">-</span><span class="comment">ssh</span><span class="literal">-</span><span class="comment">public</span><span class="literal">-</span><span class="comment">key</span> <span class="literal">--</span><span class="comment">instance</span><span class="literal">-</span><span class="comment">id %h</span> <span class="literal">--</span><span class="comment">instance</span><span class="literal">-</span><span class="comment">os</span><span class="literal">-</span><span class="comment">user %r</span> <span class="literal">--</span><span class="comment">ssh</span><span class="literal">-</span><span class="comment">public</span><span class="literal">-</span><span class="comment">key &#x27;file://$PUBLIC_KEY&#x27;</span> <span class="literal">--</span><span class="comment">availability</span><span class="literal">-</span><span class="comment">zone &#x27;$(aws ec2 describe</span><span class="literal">-</span><span class="comment">instances</span> <span class="literal">--</span><span class="comment">instance</span><span class="literal">-</span><span class="comment">ids %h</span> <span class="literal">--</span><span class="comment">query &#x27;Reservations</span><span class="title">[</span><span class="comment">0</span><span class="title">]</span><span class="string">.</span><span class="comment">Instances</span><span class="title">[</span><span class="comment">0</span><span class="title">]</span><span class="string">.</span><span class="comment">Placement</span><span class="string">.</span><span class="comment">AvailabilityZone&#x27;</span> <span class="literal">--</span><span class="comment">output text)&#x27; &amp;&amp; aws ssm start</span><span class="literal">-</span><span class="comment">session</span> <span class="literal">--</span><span class="comment">target %h</span> <span class="literal">--</span><span class="comment">document</span><span class="literal">-</span><span class="comment">name AWS</span><span class="literal">-</span><span class="comment">StartSSHSession</span> <span class="literal">--</span><span class="comment">parameters &#x27;portNumber=%p&#x27;&quot;</span></span><br></pre></td></tr></table></figure><p>For me, the snippet looks as follows.</p><figure class="highlight brainfuck"><table><tr><td class="code"><pre><span class="line"><span class="comment"># SSH over Session Manager</span></span><br><span class="line"><span class="comment">host i</span><span class="literal">-</span><span class="comment">*</span></span><br><span class="line"> <span class="comment">IdentityFile ~/</span><span class="string">.</span><span class="comment">ssh/id_ed25519</span></span><br><span class="line"> <span class="comment">User ec2</span><span class="literal">-</span><span class="comment">user</span></span><br><span class="line"> <span class="comment">ProxyCommand sh</span> <span class="literal">-</span><span class="comment">c &quot;aws ec2</span><span class="literal">-</span><span class="comment">instance</span><span class="literal">-</span><span class="comment">connect send</span><span class="literal">-</span><span class="comment">ssh</span><span class="literal">-</span><span class="comment">public</span><span class="literal">-</span><span class="comment">key</span> <span class="literal">--</span><span class="comment">instance</span><span class="literal">-</span><span class="comment">id %h</span> <span class="literal">--</span><span class="comment">instance</span><span class="literal">-</span><span class="comment">os</span><span class="literal">-</span><span class="comment">user %r</span> <span class="literal">--</span><span class="comment">ssh</span><span class="literal">-</span><span class="comment">public</span><span class="literal">-</span><span class="comment">key &#x27;file://~/</span><span class="string">.</span><span class="comment">ssh/id_ed25519</span><span class="string">.</span><span class="comment">pub&#x27;</span> <span class="literal">--</span><span class="comment">availability</span><span class="literal">-</span><span class="comment">zone &#x27;$(aws ec2 describe</span><span class="literal">-</span><span class="comment">instances</span> <span class="literal">--</span><span class="comment">instance</span><span class="literal">-</span><span class="comment">ids %h</span> <span class="literal">--</span><span class="comment">query &#x27;Reservations</span><span class="title">[</span><span class="comment">0</span><span class="title">]</span><span class="string">.</span><span class="comment">Instances</span><span class="title">[</span><span class="comment">0</span><span class="title">]</span><span class="string">.</span><span class="comment">Placement</span><span class="string">.</span><span class="comment">AvailabilityZone&#x27;</span> <span class="literal">--</span><span class="comment">output text)&#x27; &amp;&amp; aws ssm start</span><span class="literal">-</span><span class="comment">session</span> <span class="literal">--</span><span class="comment">target %h</span> <span class="literal">--</span><span class="comment">document</span><span class="literal">-</span><span class="comment">name AWS</span><span class="literal">-</span><span class="comment">StartSSHSession</span> <span class="literal">--</span><span class="comment">parameters &#x27;portNumber=%p&#x27;&quot;</span></span><br></pre></td></tr></table></figure><p>The <code>ProxyCommand</code> contains a one-liner that I’d like to explain in more detail.</p><p>First, we need to find out the availability zone the instance is running in. The command <code>aws ec2 describe-instances</code> returns the necessary information. Please note that <code>%h</code> will be replaced with the host, for example, <code>i-059499e6abc8fbe6b</code>.</p><figure class="highlight jboss-cli"><table><tr><td class="code"><pre><span class="line">aws ec2 describe-instances <span class="params">--instance-ids</span> %h <span class="params">--query</span> &#x27;Reservations[0]<span class="string">.Instances</span>[0]<span class="string">.Placement.AvailabilityZone</span>&#x27; <span class="params">--output</span> text</span><br></pre></td></tr></table></figure><p>Next, you need to push your public key to the EC2 instance. That’s why you need to execute the <code>aws ssm start-session</code> command.</p><figure class="highlight mel"><table><tr><td class="code"><pre><span class="line">aws ec2-<span class="keyword">instance</span>-connect send-ssh-public-key --<span class="keyword">instance</span>-id %h --<span class="keyword">instance</span>-os-user %r --ssh-public-key <span class="string">&#x27;file://~/.ssh/id_ed25519.pub&#x27;</span> --availability-zone <span class="string">&#x27;$(...)&#x27;</span></span><br></pre></td></tr></table></figure><p>Last but not least, you need to establish the SSH session through a WebSocket connection. That’s the job of the <code>aws ssm start-session</code> command.</p><figure class="highlight dsconfig"><table><tr><td class="code"><pre><span class="line"><span class="string">aws</span> <span class="string">ssm</span> <span class="string">start-session</span> <span class="built_in">--target</span> %<span class="string">h</span> <span class="built_in">--document-name</span> <span class="string">AWS-StartSSHSession</span> <span class="built_in">--parameters</span> <span class="string">&#x27;portNumber=%p&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="Connect-to-your-EC2-instance-using-SSH"><a href="#Connect-to-your-EC2-instance-using-SSH" class="headerlink" title="Connect to your EC2 instance using SSH"></a>Connect to your EC2 instance using SSH</h2><p>You are ready to connect with your EC2 instance using SSH. All you need to do is type <code>ssh</code> followed by an EC2 instance ID into your terminal.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">ssh</span> i-<span class="number">059499</span>e6abc8fbe6b</span><br></pre></td></tr></table></figure><p>By the way, you can even copy files with <code>scp</code>.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">ssh</span> example.txt i-<span class="number">059499</span>e6abc8fbe6b:/tmp/</span><br></pre></td></tr></table></figure><p>That’s it. I hope you enjoy this approach to connect to your EC2 instances using SSH as much as I do.</p><p><em>Did you run into any issues while following my instructions? Do you love the approach to connect with your EC2 instances? Please let me know!</em></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to set up Jenkins on AWS?</title>
      <link>https://cloudonaut.io/how-to-set-up-jenkins-on-aws/</link>
      <description>
        <![CDATA[<p>What’s the best way to run Jenkins on AWS? As Jenkins is still a popular automation server used for continuous integration and deployment]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/efs/">efs</category>
      <category domain="https://cloudonaut.io/tag/alb/">alb</category>
      <category domain="https://cloudonaut.io/tag/cognito/">cognito</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-set-up-jenkins-on-aws/</guid>
      <pubDate>Fri, 21 Jan 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>What’s the best way to run Jenkins on AWS? As Jenkins is still a popular automation server used for continuous integration and deployment, consulting clients have engaged me to design and implement a cloud architecture for Jenkins several times in recent years. This blog post summarizes my learnings and presents a modern architecture for Jenkins on AWS.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/01/waitor@730w.webp 730w, /images/2022/01/waitor@730w2x.webp 1460w, /images/2022/01/waitor@610w.webp 610w, /images/2022/01/waitor@610w2x.webp 1220w, /images/2022/01/waitor@450w.webp 450w, /images/2022/01/waitor@450w2x.webp 900w, /images/2022/01/waitor@330w.webp 330w, /images/2022/01/waitor@330w2x.webp 660w, /images/2022/01/waitor@545w.webp 545w, /images/2022/01/waitor@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/01/waitor@730w.jpg 730w, /images/2022/01/waitor@730w2x.jpg 1460w, /images/2022/01/waitor@610w.jpg 610w, /images/2022/01/waitor@610w2x.jpg 1220w, /images/2022/01/waitor@450w.jpg 450w, /images/2022/01/waitor@450w2x.jpg 900w, /images/2022/01/waitor@330w.jpg 330w, /images/2022/01/waitor@330w2x.jpg 660w, /images/2022/01/waitor@545w.jpg 545w, /images/2022/01/waitor@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/01/waitor.jpg" alt="How to set up Jenkins on AWS?" title="How to set up Jenkins on AWS?"></picture></p><p>As shown in the following figure, our architecture consists of the following building blocks:</p><ul><li>An <strong>Auto Scaling Group (ASG)</strong> to ensure an EC2 instance running the Jenkins controller is up and running 24&#x2F;7.</li><li>The Jenkins plugin <a href="https://plugins.jenkins.io/ec2/" target="_blank" rel="noopener">ec2</a> launches and terminates <strong>EC2 instances</strong> acting as Jenkins agents automatically.</li><li>An <strong>Application Load Balancer (ALB)</strong> acts as the entry point for users and forwards the incoming request to the Jenkins controller. On top of that, the ALB terminates HTTPS and handles authentication.</li><li>The <strong>Cognito User Pool</strong> provides a secure way to authenticate with Jenkins. By the way, replacing the Cognito User Pool with any OpenID Connect compatible identity provider is possible.</li><li>An <strong>Elastic File System (EFS)</strong> is used to ensure no data is getting lost when replacing the Jenkins controller in case of a failure or rolling update.</li><li><strong>Backup</strong> takes snapshots of EFS to be able to restore data from a point-in-time snapshot. For example, to recover from a human error.</li><li>All EC2 instances send metrics and logs to <strong>CloudWatch</strong>. Alarms notify operators in case of problems. Also, operators have access to centralized logs to analyze issues.</li><li>The <strong>Simple E-Mail Service</strong> allows Jenkins to send notifications to engineers. For example, to notify about failed jobs.</li><li>Engineers access EC2 instances using the <strong>Sytems Manager Session Manager</strong> in a very secure manner. The Session Manager replaces SSH and, therefore, publicly accessible inbound ports.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/01/jenkins-on-aws@730w.webp 730w, /images/2022/01/jenkins-on-aws@730w2x.webp 1460w, /images/2022/01/jenkins-on-aws@610w.webp 610w, /images/2022/01/jenkins-on-aws@610w2x.webp 1220w, /images/2022/01/jenkins-on-aws@450w.webp 450w, /images/2022/01/jenkins-on-aws@450w2x.webp 900w, /images/2022/01/jenkins-on-aws@330w.webp 330w, /images/2022/01/jenkins-on-aws@330w2x.webp 660w, /images/2022/01/jenkins-on-aws@545w.webp 545w, /images/2022/01/jenkins-on-aws@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/01/jenkins-on-aws@730w.png 730w, /images/2022/01/jenkins-on-aws@730w2x.png 1460w, /images/2022/01/jenkins-on-aws@610w.png 610w, /images/2022/01/jenkins-on-aws@610w2x.png 1220w, /images/2022/01/jenkins-on-aws@450w.png 450w, /images/2022/01/jenkins-on-aws@450w2x.png 900w, /images/2022/01/jenkins-on-aws@330w.png 330w, /images/2022/01/jenkins-on-aws@330w2x.png 660w, /images/2022/01/jenkins-on-aws@545w.png 545w, /images/2022/01/jenkins-on-aws@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/01/jenkins-on-aws.png" alt="Jenkins on AWS" title="Jenkins on AWS"></picture></p><p>I do not recommend running Jenkins on a single EC2 instance. Next, you will learn how I set up Jenkins more professionally, taking high availability, scalability, security, and operations into account.</p><h2 id="High-Availability"><a href="#High-Availability" class="headerlink" title="High Availability"></a>High Availability</h2><p>The impact of a Jenkins downtime is huge for many organizations. Engineers get blocked, and deployments become impossible. Therefore, ensuring high availability is crucial. Unfortunately, Jenkins cannot be run as a cluster; only one Jenkins controller should run at a time.</p><p>I recommend using an Auto Scaling Group (ASG) to ensure a single Jenkins controller is running 24&#x2F;7. If the EC2 instance fails, the ASG will launch a new EC2 instance in another availability zone. As all data is stored on EFS, which distributes the data among multiple availability zones out-of-the-box, Jenkins will recover within minutes.</p><p>Also, the ASG allows rolling out new versions of the Jenkins controller with short downtime. In this case, the running EC2 instance is terminated and replaced by a new instance.</p><p>It is worth noting that the Application Load Balancer will make sure that the requests from the engineers get routed to the active Jenkins controller. No need to update DNS names or IP addresses.</p><h2 id="Scalability"><a href="#Scalability" class="headerlink" title="Scalability"></a>Scalability</h2><p>I remember a consulting gig where the client ran a CI&#x2F;CD infrastructure in-house. Every Friday afternoon, I wanted to deploy my changes before leaving the office to catch the train back home. But on Fridays, the CI&#x2F;CD infrastructure was heavily utilized. I was not the only one preparing for the weekend. Sometimes, I had to wait more than 30 minutes for my build job to start.</p><p>Your automation server should not slow down engineers because of limited resources. Instead, the number of Jenkins agents should scale automatically to balance peak loads.</p><p>Therefore, I recommend the Jenkins plugin <a href="https://plugins.jenkins.io/ec2/" target="_blank" rel="noopener">ec2</a>, which launches and terminates <strong>EC2 instances</strong> acting as Jenkins agents as needed. The plugin even allows you to utilize spot instances for cost efficiency.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/01/jenkins-cloud-configuration@730w.webp 730w, /images/2022/01/jenkins-cloud-configuration@730w2x.webp 1460w, /images/2022/01/jenkins-cloud-configuration@610w.webp 610w, /images/2022/01/jenkins-cloud-configuration@610w2x.webp 1220w, /images/2022/01/jenkins-cloud-configuration@450w.webp 450w, /images/2022/01/jenkins-cloud-configuration@450w2x.webp 900w, /images/2022/01/jenkins-cloud-configuration@330w.webp 330w, /images/2022/01/jenkins-cloud-configuration@330w2x.webp 660w, /images/2022/01/jenkins-cloud-configuration@545w.webp 545w, /images/2022/01/jenkins-cloud-configuration@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/01/jenkins-cloud-configuration@730w.png 730w, /images/2022/01/jenkins-cloud-configuration@730w2x.png 1460w, /images/2022/01/jenkins-cloud-configuration@610w.png 610w, /images/2022/01/jenkins-cloud-configuration@610w2x.png 1220w, /images/2022/01/jenkins-cloud-configuration@450w.png 450w, /images/2022/01/jenkins-cloud-configuration@450w2x.png 900w, /images/2022/01/jenkins-cloud-configuration@330w.png 330w, /images/2022/01/jenkins-cloud-configuration@330w2x.png 660w, /images/2022/01/jenkins-cloud-configuration@545w.png 545w, /images/2022/01/jenkins-cloud-configuration@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/01/jenkins-cloud-configuration.png" alt="Screenshot: Jenkins EC2 Plugin" title="Screenshot: Jenkins EC2 Plugin"></picture></p><h2 id="Security"><a href="#Security" class="headerlink" title="Security"></a>Security</h2><p>In my observation, many organizations underestimate the potential damage caused by a security breach in their Jenkins infrastructure. An attacker gaining access to Jenkins could most likely deploy changes to production or delete the whole production infrastructure, including all databases and backups.</p><p>First, a Jenkins server available via the Internet enables a smooth experience for remote workers. However, I am skeptical about relying on the authentication layer built into Jenkins. I prefer delegating authentication to the Application Load Balancer (ALB). Luckily, the ALB supports a widespread standard called OpenID Connect. If your organization does not provide a user directory with OpenID Connect support yet, the Coginit User Pool offered by AWS is a good fit.</p><p>Second, you should encrypt data in transit and at rest. Luckily, the ALB handles HTTPS out-of-the-box. Jenkins and EFS provide TLS encrypted connections as well. Besides that, encryption-at-rest is only a configuration option away for EBS and EFS.</p><p>Third, I cannot stress the importance of least privilege for IAM roles. You should grant minimal access to AWS services for EC2 instances running the Jenkins controller and agents.</p><h2 id="Operations"><a href="#Operations" class="headerlink" title="Operations"></a>Operations</h2><p>Although, operating Jenkins professionally creates immense value, there is no glory in managing Jenkins in most organizations. Therefore, it is even more critical to get the job done with as little effort as possible.</p><p>I recommend to following tools to operate Jenkins with little effort:</p><ol><li>Use CloudWatch to define alarms on metrics like <code>CPUUtilization</code> of the EC2 instanecs, <code>HTTPCode_ELB_5XX_Count</code> and <code>TargetConnectionErrorCount</code> of the ALB, and <code>PercentIOLimit</code>, <code>BurstCreditBalance</code> as well as <code>MeteredIOBytes</code> of EFS.</li><li>Configure all EC2 instances to send logs to CloudWatch. This allows you to search through the log messages of all instances - even those already terminated - to debug issues.</li><li>Use the Session Manager provided by the Systems Manager to connect to your EC2 instances securely. No need to establish an SSH connection over the Internet anymore.</li></ol><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>How to set up Jenkins on AWS? A few learnings.</p><ul><li>Launching a single EC2 instance is insufficient to build a highly available and scalable solution.</li><li>Leveraging AWS services like the Application Load Balancer, an Auto Scaling Group, an Elastic File System enables high availability for Jenkins and avoids costly downtimes.</li><li>The topic of security is critical when running Jenkins. For example, I recommend implementing a separate authentication layer via the ALB and OpenID Connect.</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>Parental Leave (Michael)</title>
      <link>https://cloudonaut.io/parental-leave-michael/</link>
      <description>
        <![CDATA[<p>Taking care of my family is the number one priority for me. Luckily, Andreas and I bootstrapped a profitable business that provides us tr]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/parental-leave-michael/</guid>
      <pubDate>Sat, 08 Jan 2022 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Taking care of my family is the number one priority for me. Luckily, Andreas and I bootstrapped a profitable business that provides us tremendous freedom. Starting today, I’m on parental leave for the next six months. I’m looking forward to dedicating my full attention to my seven-month-old son.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2022/01/toddler@730w.webp 730w, /images/2022/01/toddler@730w2x.webp 1460w, /images/2022/01/toddler@610w.webp 610w, /images/2022/01/toddler@610w2x.webp 1220w, /images/2022/01/toddler@450w.webp 450w, /images/2022/01/toddler@450w2x.webp 900w, /images/2022/01/toddler@330w.webp 330w, /images/2022/01/toddler@330w2x.webp 660w, /images/2022/01/toddler@545w.webp 545w, /images/2022/01/toddler@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2022/01/toddler@730w.jpg 730w, /images/2022/01/toddler@730w2x.jpg 1460w, /images/2022/01/toddler@610w.jpg 610w, /images/2022/01/toddler@610w2x.jpg 1220w, /images/2022/01/toddler@450w.jpg 450w, /images/2022/01/toddler@450w2x.jpg 900w, /images/2022/01/toddler@330w.jpg 330w, /images/2022/01/toddler@330w2x.jpg 660w, /images/2022/01/toddler@545w.jpg 545w, /images/2022/01/toddler@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2022/01/toddler.jpg" alt="Parental Leave" title="Parental Leave"></picture></p><p>Attentive readers will have noticed that <a href="/parental-leave-andreas/">Andreas returned from his parental leave</a> only a few days ago. Enough time to ensure a smooth transition to keep our projects running without interruption.</p><p>In the last nine months, I was reminded daily about what Andreas and I created over the years. It is not easy to run all of that as a one-person show anymore. Not only our little children are growing. Our business is growing as well. So before I leave, I will share my highlights of the last nine months with you.</p><ul><li>We receive cheering support and feedback from our cloudonaut readers, <a href="https://podcast.cloudonaut.io/" target="_blank" rel="noopener">podcast listeners</a>, and <a href="https://www.youtube.com/c/cloudonaut" target="_blank" rel="noopener">YouTube subscribers</a>. Thanks for all your emails, YouTube comments, and more!</li><li>Our customers love the simplicity of <a href="https://marbot.io/" target="_blank" rel="noopener">marbot, our ChatOps product to configure AWS monitoring, receive alerts, solve incidents</a>. I had dozens of onboarding calls where customers shared their monitoring challenges. I’m very proud of each new customer!</li><li>I’m thrilled to see our latest product growing. <a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV provides antivirus protection for Amazon S3</a>. I answered hundreds of support emails and shipped new features in ten releases. It’s great to see a product grow based on customer feedback.</li><li>I still enjoy working with our consulting clients. It’s mind-blowing how many different business needs can be implemented on AWS. Working with clients that Andreas usually serves was much smoother than expected.</li><li>Maintaining our open-source projects is a lot of work.</li><li>Writing a technical book is never done. We constantly update Rapid Docker on AWS, and we are working on the 3rd edition of <a href="https://cloudonaut.io/books-and-video-courses/">Amazon Web Services in Action</a>.</li></ul><p>I’ll be back in July. I wish you all a good time! 👋</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>2021 in Review</title>
      <link>https://cloudonaut.io/2021-in-review/</link>
      <description>
        <![CDATA[<p>The past 12 months have been an exciting time for us. We started the year with high expectations and a significant investment in video re]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/retrospective/">retrospective</category>
      <guid isPermaLink="true">https://cloudonaut.io/2021-in-review/</guid>
      <pubDate>Thu, 30 Dec 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The past 12 months have been an exciting time for us. We started the year with high expectations and a significant investment in video recording. Our goal was to attract 1,000 paying subscribers for exclusive video content called <em>cloudonaut plus</em>. Each week, we published another video. Unfortunately, our plan did not work out. We shut down <em>cloudonaut plus</em> with a heavy heart in May. But we didn’t let the failure demotivate us. We are still on a mission to publish high-quality content for AWS professionals. The positive side effect of the whole story: in addition to the blog and the podcast, there is another channel for our content with our <a href="https://www.youtube.com/channel/UCKRq7wc2BcHBR0SJIfJVwdw" target="_blank" rel="noopener">YouTube channel</a>.</p><p>The year was special on another level as well: I was on parental leave for nine months - that’s 75% off the year. The time with my children was wonderful. However, a big thank you to Michael, who represented me fantastically during this time. This was no easy task in a 2-man company. Thanks a ton, Michael!</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/12/2021@730w.webp 730w, /images/2021/12/2021@730w2x.webp 1460w, /images/2021/12/2021@610w.webp 610w, /images/2021/12/2021@610w2x.webp 1220w, /images/2021/12/2021@450w.webp 450w, /images/2021/12/2021@450w2x.webp 900w, /images/2021/12/2021@330w.webp 330w, /images/2021/12/2021@330w2x.webp 660w, /images/2021/12/2021@545w.webp 545w, /images/2021/12/2021@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/12/2021@730w.jpg 730w, /images/2021/12/2021@730w2x.jpg 1460w, /images/2021/12/2021@610w.jpg 610w, /images/2021/12/2021@610w2x.jpg 1220w, /images/2021/12/2021@450w.jpg 450w, /images/2021/12/2021@450w2x.jpg 900w, /images/2021/12/2021@330w.jpg 330w, /images/2021/12/2021@330w2x.jpg 660w, /images/2021/12/2021@545w.jpg 545w, /images/2021/12/2021@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/12/2021.jpg" alt="2021 in Review" title="2021 in Review"></picture></p><h2 id="Our-content-from-2021"><a href="#Our-content-from-2021" class="headerlink" title="Our content from 2021"></a>Our content from 2021</h2><p>During the past 52 weeks, we published <a href="https://cloudonaut.io/page/1/">44 articles</a>, <a href="https://www.youtube.com/channel/UCKRq7wc2BcHBR0SJIfJVwdw" target="_blank" rel="noopener">34 videos</a>, and <a href="https://podcast.cloudonaut.io/" target="_blank" rel="noopener">7 podcast epidsodes</a>. Every content piece is packed with our knowledge, experience, and opinion about all things AWS. We hope you have learned a lot from us!</p><p>In case you missed it, here are our favorites from 2021!</p><ul><li><a href="https://cloudonaut.io/does-your-vpc-endpoint-allow-access-to-half-of-the-internet/">Does your VPC endpoint allow access to half of the Internet?</a></li><li><a href="https://cloudonaut.io/defining-iam-policies-with-terraform/">Defining IAM Policies with Terraform safely</a></li><li><a href="https://cloudonaut.io/serverless-in-the-enterprise/">Serverless in the Enterprise</a></li><li><a href="https://cloudonaut.io/review-fault-injection-simulator-fis-chaos-as-a-service/">Review: AWS Fault Injection Simulator (FIS) – Chaos as a Service?</a></li><li><a href="https://cloudonaut.io/ec2-checklist-seven-things-to-do-after-launching-an-instance/">EC2 Checklist: 7 things to do after launching an instance</a></li><li><a href="https://cloudonaut.io/multi-region-aws-architectures/">Multi-Region AWS Architectures</a></li></ul><p>In total, you have read our articles 800,000 times, listened to our podcast 25,000 times, and watched our videos 20,000 times in 2021.</p><h2 id="Thanks"><a href="#Thanks" class="headerlink" title="Thanks!"></a>Thanks!</h2><p>We want to thank you for reading, listening, and watching! Also, thanks a lot for sharing our content with your peers. Thanks for giving us feedback, buying our books and video courses, and supporting us in any other way. Special thanks to <a href="https://cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft</a> for the content collaboration and <a href="https://superluminar.io/" target="_blank" rel="noopener">superluminar</a> for the recruiting experiment.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Video Hosting on AWS</title>
      <link>https://cloudonaut.io/video-hosting-on-aws/</link>
      <description>
        <![CDATA[<p>How to embed a video into your website? There is an alternative that looks and feels much more professional than embedding a YouTube vide]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/cloudfront/">cloudfront</category>
      <category domain="https://cloudonaut.io/tag/mediaconvert/">mediaconvert</category>
      <guid isPermaLink="true">https://cloudonaut.io/video-hosting-on-aws/</guid>
      <pubDate>Tue, 14 Dec 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>How to embed a video into your website? There is an alternative that looks and feels much more professional than embedding a YouTube video. Read on to learn how to host video-on-demand with the help of Amazon Web Services (AWS).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/12/video@730w.webp 730w, /images/2021/12/video@730w2x.webp 1460w, /images/2021/12/video@610w.webp 610w, /images/2021/12/video@610w2x.webp 1220w, /images/2021/12/video@450w.webp 450w, /images/2021/12/video@450w2x.webp 900w, /images/2021/12/video@330w.webp 330w, /images/2021/12/video@330w2x.webp 660w, /images/2021/12/video@545w.webp 545w, /images/2021/12/video@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/12/video@730w.jpg 730w, /images/2021/12/video@730w2x.jpg 1460w, /images/2021/12/video@610w.jpg 610w, /images/2021/12/video@610w2x.jpg 1220w, /images/2021/12/video@450w.jpg 450w, /images/2021/12/video@450w2x.jpg 900w, /images/2021/12/video@330w.jpg 330w, /images/2021/12/video@330w2x.jpg 660w, /images/2021/12/video@545w.jpg 545w, /images/2021/12/video@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/12/video.jpg" alt="Video Hosting in AWS" title="Video Hosting in AWS"></picture></p><p>First of all, delivering video content comes with three challenges.</p><ol><li><em>Distributing content around the globe to optimize bandwidth.</em> A user from Europe should not download video files from the Americas.</li><li><em>Adapting the bitrate depending on the device and connectivity.</em> A smartphone with low connectivity cannot stream HD video.</li><li><em>Being compatible with major platforms like iOS, Android, macOS, and Windows.</em> Most devices only support a subset of the available video codecs.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/12/video-hosting-on-aws-01@730w.webp 730w, /images/2021/12/video-hosting-on-aws-01@730w2x.webp 1460w, /images/2021/12/video-hosting-on-aws-01@610w.webp 610w, /images/2021/12/video-hosting-on-aws-01@610w2x.webp 1220w, /images/2021/12/video-hosting-on-aws-01@450w.webp 450w, /images/2021/12/video-hosting-on-aws-01@450w2x.webp 900w, /images/2021/12/video-hosting-on-aws-01@330w.webp 330w, /images/2021/12/video-hosting-on-aws-01@330w2x.webp 660w, /images/2021/12/video-hosting-on-aws-01@545w.webp 545w, /images/2021/12/video-hosting-on-aws-01@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/12/video-hosting-on-aws-01@730w.png 730w, /images/2021/12/video-hosting-on-aws-01@730w2x.png 1460w, /images/2021/12/video-hosting-on-aws-01@610w.png 610w, /images/2021/12/video-hosting-on-aws-01@610w2x.png 1220w, /images/2021/12/video-hosting-on-aws-01@450w.png 450w, /images/2021/12/video-hosting-on-aws-01@450w2x.png 900w, /images/2021/12/video-hosting-on-aws-01@330w.png 330w, /images/2021/12/video-hosting-on-aws-01@330w2x.png 660w, /images/2021/12/video-hosting-on-aws-01@545w.png 545w, /images/2021/12/video-hosting-on-aws-01@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/12/video-hosting-on-aws-01.png" alt="Delivering video-on-demand is challenging!" title="Delivering video-on-demand is challenging!"></picture></p><h2 id="Alternative-to-YouTube-for-video-on-demand"><a href="#Alternative-to-YouTube-for-video-on-demand" class="headerlink" title="Alternative to YouTube for video-on-demand"></a>Alternative to YouTube for video-on-demand</h2><p>It is not that complicated to embed videos into your website without using YouTube. Combining the following technologies allows you to do so without data privacy implications (YouTube, …) or baseline costs (Vimeo, …).</p><ul><li><strong>Dash</strong> and <strong>HLS</strong> for adaptive streaming</li><li><strong>AWS Elemental MediaConvert</strong> to transcode videos into Dash and HLS</li><li><strong>Amazon S3</strong> to store the video files</li><li><strong>Amazon CloudFront</strong> to deliver the content to users all around the world</li><li><strong>Video.js</strong> to embed videos in Dash and HLS to any website</li></ul><p>Next, let’s dive into the details.</p><h2 id="What-is-adaptive-streaming"><a href="#What-is-adaptive-streaming" class="headerlink" title="What is adaptive streaming?"></a>What is adaptive streaming?</h2><p>Nowadays, we consume video content in many different situations: while commuting on the train, at home with a big screen, or with the laptop on our lap. The capabilities of the devices we use for this vary greatly. The same applies to the Internet connection and its maximum bandwidth. Therefore, the quality - and thus the bitrate - needs to adapt when streaming a video.</p><p>How does adaptive streaming work? A simplified explanation.</p><ol><li>Transcode the original video file into multiple video files with different bitrates.</li><li>Split the video files into small chunks.</li><li>Create a manifest file containing information about the available bitrates and files.</li></ol><p>The client will download the manifest file first. Next, the client will decide which bitrate fits best and start downloading the necessary video files on the fly. If necessary, the client can even switch to another bitrate.</p><p>There are a handful of standards for adaptive streaming. In our scenario, we rely on two standards.</p><ul><li>Dynamic Adaptive Streaming over HTTP (Dash)</li><li>HTTP Live Streaming (HLS)</li></ul><p>IOS and macOS support Apple’s HLS out-of-the-box. Most browsers do not support Dash, but Video.js comes with a fallback implementation.</p><h2 id="What-is-AWS-Elemental-MediaConvert"><a href="#What-is-AWS-Elemental-MediaConvert" class="headerlink" title="What is AWS Elemental MediaConvert?"></a>What is AWS Elemental MediaConvert?</h2><p>But how to generate and transcode all those video files for Dash and HLS? In theory, you can do so on your local machine. But doing so will take some time, as this is a compute intense operation. Luckily, AWS provides a service for that: AWS Elemental MediaConvert, which I will call MediaConvert in the following. MediaConvert outsources the task of transcoding video files to the cloud. To do so, MediaConvert reads and writes media files from and to S3.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/12/video-hosting-on-aws-02@730w.webp 730w, /images/2021/12/video-hosting-on-aws-02@730w2x.webp 1460w, /images/2021/12/video-hosting-on-aws-02@610w.webp 610w, /images/2021/12/video-hosting-on-aws-02@610w2x.webp 1220w, /images/2021/12/video-hosting-on-aws-02@450w.webp 450w, /images/2021/12/video-hosting-on-aws-02@450w2x.webp 900w, /images/2021/12/video-hosting-on-aws-02@330w.webp 330w, /images/2021/12/video-hosting-on-aws-02@330w2x.webp 660w, /images/2021/12/video-hosting-on-aws-02@545w.webp 545w, /images/2021/12/video-hosting-on-aws-02@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/12/video-hosting-on-aws-02@730w.png 730w, /images/2021/12/video-hosting-on-aws-02@730w2x.png 1460w, /images/2021/12/video-hosting-on-aws-02@610w.png 610w, /images/2021/12/video-hosting-on-aws-02@610w2x.png 1220w, /images/2021/12/video-hosting-on-aws-02@450w.png 450w, /images/2021/12/video-hosting-on-aws-02@450w2x.png 900w, /images/2021/12/video-hosting-on-aws-02@330w.png 330w, /images/2021/12/video-hosting-on-aws-02@330w2x.png 660w, /images/2021/12/video-hosting-on-aws-02@545w.png 545w, /images/2021/12/video-hosting-on-aws-02@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/12/video-hosting-on-aws-02.png" alt="MediaConvert transcodes the original video into files for adaptive streaming" title="MediaConvert transcodes the original video into files for adaptive streaming"></picture></p><p>The following steps guide you through generating the HLS and Dash files for a video.</p><ol><li>Make sure you have uploaded the original video to S3. For example, <code>s3://cloudonaut-video/input/001</code>.</li><li>Open <a href="https://console.aws.amazon.com/mediaconvert/home#/templates/list" target="_blank" rel="noopener">MediaConvert</a>.</li><li>Create a new job (HLS).<ol><li>Open the list of job templates*.</li><li>Select <code>System Templates</code>.</li><li>Select category <code>OTT-HLS</code>.</li><li>Create a new job based on the <code>System-Ott_Hls_Ts_Avc_Aac</code> template.</li><li>Choose output destination <code>s3://cloudonaut-video/output/001/</code></li><li>Add input and select the original video file from <code>s3://cloudonaut-video/input/001/video.mov</code></li><li>Go to <code>AWS Integration</code> in the <code>Job settings</code> and select IAM role <code>MediaConvertDefaultRole</code>.</li><li>Press the <code>Create</code> button to start the job.</li></ol></li><li>Create a new job (Dash).<ol><li>Open the list of job templates*.</li><li>Select <code>System Templates</code>.</li><li>Select category <code>OTT-DASH</code>.</li><li>Create a new job based on the <code>System-Ott_Dash_Mp4_Avc_Aac</code> template.</li><li>Choose output destination <code>s3://cloudonaut-video/output/001/</code></li><li>Add input and select the original video file from <code>s3://cloudonaut-video/input/001/video.mov</code></li><li>Go to <code>AWS Integration</code> in the <code>Job settings</code> and select IAM role <code>MediaConvertDefaultRole</code>.</li><li>Press the <code>Create</code> button to start the job.</li></ol></li><li>Wait until both jobs succeed.</li></ol><p>Next, we need to talk about delivering the video content to your users.</p><h2 id="What-is-CloudFront"><a href="#What-is-CloudFront" class="headerlink" title="What is CloudFront?"></a>What is CloudFront?</h2><p>So far, both the original video files and the transcoded video files for adaptive streaming are stored on Simple Storage Service (S3). In theory, users can download the video files directly from S3. However, the files are stored in a single region only - for example, Europe (Ireland). When a user far away from Ireland wants to stream the video, the bandwidth or latency might not be optimal. Therefore, it is a best practice to use a Content Delivery Network (CDN) to distribute the video files all around the globe.</p><p>Amazon CloudFront is the CDN provided by AWS. It works with S3 out-of-the-box. When a user streams a video, the browser will send an HTTPS request to a CloudFront edge location nearby. CloudFront will try to answer the request from the local cache. Only when necessary will CloudFront fetch the data from S3.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/12/video-hosting-on-aws-03@730w.webp 730w, /images/2021/12/video-hosting-on-aws-03@730w2x.webp 1460w, /images/2021/12/video-hosting-on-aws-03@610w.webp 610w, /images/2021/12/video-hosting-on-aws-03@610w2x.webp 1220w, /images/2021/12/video-hosting-on-aws-03@450w.webp 450w, /images/2021/12/video-hosting-on-aws-03@450w2x.webp 900w, /images/2021/12/video-hosting-on-aws-03@330w.webp 330w, /images/2021/12/video-hosting-on-aws-03@330w2x.webp 660w, /images/2021/12/video-hosting-on-aws-03@545w.webp 545w, /images/2021/12/video-hosting-on-aws-03@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/12/video-hosting-on-aws-03@730w.png 730w, /images/2021/12/video-hosting-on-aws-03@730w2x.png 1460w, /images/2021/12/video-hosting-on-aws-03@610w.png 610w, /images/2021/12/video-hosting-on-aws-03@610w2x.png 1220w, /images/2021/12/video-hosting-on-aws-03@450w.png 450w, /images/2021/12/video-hosting-on-aws-03@450w2x.png 900w, /images/2021/12/video-hosting-on-aws-03@330w.png 330w, /images/2021/12/video-hosting-on-aws-03@330w2x.png 660w, /images/2021/12/video-hosting-on-aws-03@545w.png 545w, /images/2021/12/video-hosting-on-aws-03@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/12/video-hosting-on-aws-03.png" alt="CloudFront is a Content Delivery Network (CDN) to distribute your video files globally" title="CloudFront is a Content Delivery Network (CDN) to distribute your video files globally"></picture></p><h2 id="What-is-Video-js"><a href="#What-is-Video-js" class="headerlink" title="What is Video.js?"></a>What is Video.js?</h2><p>There is only one more building block missing. In theory, HTML5 comes with a built-in video player. But browsers implement all kinds of different standards. Therefore, it is helpful to add a layer of abstraction. <a href="https://videojs.com/" target="_blank" rel="noopener">Video.js</a> is an open-source HTML5 player framework I have had a perfect experience with. It is crucial to notice that Video.js also includes a fallback implementation if the browser does not support Dash or HLS natively.</p><p>So how to embed a video into your website. The following snippet shows a <code>video</code> element referencing an HLS (<code>.m3u8</code>) and Dash (<code>.mpd</code>) manifest file.</p><figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">&lt;body&gt;</span><br><span class="line">  &lt;video controls preload<span class="operator">=</span><span class="string">&quot;auto&quot;</span> class<span class="operator">=</span><span class="string">&quot;video-js&quot;</span>&gt;</span><br><span class="line">    &lt;source src<span class="operator">=</span><span class="string">&quot;/001/video.m3u8&quot;</span> type<span class="operator">=</span><span class="string">&quot;application/x-mpegURL&quot;</span>&gt;</span><br><span class="line">    &lt;source src<span class="operator">=</span><span class="string">&quot;/001/video.mpd&quot;</span> type<span class="operator">=</span><span class="string">&quot;application/dash+xml&quot;</span>&gt;</span><br><span class="line">  &lt;/video&gt;</span><br><span class="line">&lt;/body&gt;</span><br></pre></td></tr></table></figure><p>Also, the following snippet shows the JavaScript to configure the video player. Most important are the configuration parameters for <code>hls</code>. For example, <code>smoothQualityChange</code> tells the player to switch to a lower or higher bitrate when necessary.</p><figure class="highlight php"><table><tr><td class="code"><pre><span class="line">document.<span class="title function_ invoke__">querySelectorAll</span>(<span class="string">&#x27;.video-js&#x27;</span>).<span class="title function_ invoke__">forEach</span>(function(element) &#123;</span><br><span class="line">    <span class="keyword">var</span> player = <span class="title function_ invoke__">videojs</span>(element, &#123;</span><br><span class="line">      <span class="attr">controls</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">autoplay</span>: <span class="literal">false</span>,</span><br><span class="line">      <span class="attr">preload</span>: <span class="string">&#x27;auto&#x27;</span>,</span><br><span class="line">      <span class="attr">html5</span>: &#123;</span><br><span class="line">        <span class="attr">hls</span>: &#123;</span><br><span class="line">          <span class="attr">limitRenditionByPlayerDimensions</span>: <span class="literal">false</span>,</span><br><span class="line">          <span class="attr">smoothQualityChange</span>: <span class="literal">true</span>,</span><br><span class="line">          <span class="attr">overrideNative</span>: <span class="literal">false</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br></pre></td></tr></table></figure><h2 id="How-to-embed-a-video-into-your-website"><a href="#How-to-embed-a-video-into-your-website" class="headerlink" title="How to embed a video into your website?"></a>How to embed a video into your website?</h2><p>That’s it. All you need for hosting videos is HLS&#x2F;Dash, Video.js, and three cloud services provided by AWS:</p><ul><li><strong>AWS Elemental MediaConvert</strong> to transcode the original video file into HLS and Dash video files.</li><li><strong>S3</strong> to store the original video and the video files ready for adaptive streaming.</li><li><strong>CloudFront</strong> to deliver the content globally.</li></ul><p>How does that look and feel in the real world? Check out the following example built with all the technologies discussed above.</p><video controls preload="auto" class="video-js vjs-theme-forest vjs-16-9" poster="/media/cloudonaut/2021/000-behind-the-scenes/000-behind-the-scenes@730w.jpg">  <source src="/media/cloudonaut/2021/000-behind-the-scenes/000-behind-the-scenes.m3u8" type="application/x-mpegURL">  <source src="/media/cloudonaut/2021/000-behind-the-scenes/000-behind-the-scenes.mpd" type="application/dash+xml"></video><p>Remember, it is not that hard to host videos yourself. YouTube is the obvious choice but comes with privacy implications.</p><p>One more thing: there are no baseline costs for the presented solution. MediaConvert, S3, and CloudFront are billed per usage. So you only pay for what you use. And let’s be honest, your company’s image video will most likely not go viral. </p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Architect Mindset</title>
      <link>https://cloudonaut.io/aws-architect-mindset/</link>
      <description>
        <![CDATA[<p>Architecting applications on AWS is challenging. On the one hand, you need a broad understanding of AWS services. On the other hand, you]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/career/">career</category>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-architect-mindset/</guid>
      <pubDate>Thu, 18 Nov 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Architecting applications on AWS is challenging. On the one hand, you need a broad understanding of AWS services. On the other hand, you have to know the details as well. In this blog post, I outline the mindset you need to build on AWS successfully.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/11/aws-architect-mindset@730w.webp 730w, /images/2021/11/aws-architect-mindset@730w2x.webp 1460w, /images/2021/11/aws-architect-mindset@610w.webp 610w, /images/2021/11/aws-architect-mindset@610w2x.webp 1220w, /images/2021/11/aws-architect-mindset@450w.webp 450w, /images/2021/11/aws-architect-mindset@450w2x.webp 900w, /images/2021/11/aws-architect-mindset@330w.webp 330w, /images/2021/11/aws-architect-mindset@330w2x.webp 660w, /images/2021/11/aws-architect-mindset@545w.webp 545w, /images/2021/11/aws-architect-mindset@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/11/aws-architect-mindset@730w.jpg 730w, /images/2021/11/aws-architect-mindset@730w2x.jpg 1460w, /images/2021/11/aws-architect-mindset@610w.jpg 610w, /images/2021/11/aws-architect-mindset@610w2x.jpg 1220w, /images/2021/11/aws-architect-mindset@450w.jpg 450w, /images/2021/11/aws-architect-mindset@450w2x.jpg 900w, /images/2021/11/aws-architect-mindset@330w.jpg 330w, /images/2021/11/aws-architect-mindset@330w2x.jpg 660w, /images/2021/11/aws-architect-mindset@545w.jpg 545w, /images/2021/11/aws-architect-mindset@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/11/aws-architect-mindset.jpg" alt="Containers on AWS: ECS, EKS, and Fargate" title="Containers on AWS: ECS, EKS, and Fargate"></picture></p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><p>Without the details, you might miss that one of the services is unavailable in your region or not in scope in the required compliance framework. Ignoring the details can also lead to high costs or reduced availability.</p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/43-aws-architect-mindset/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="Regional-availability-of-services"><a href="#Regional-availability-of-services" class="headerlink" title="Regional availability of services"></a>Regional availability of services</h2><p>Not all AWS services are available in all regions. Newer services, as well as smaller regions, tend to have lower service coverage. Regions like us-east-1 and eu-west-1 usually have the highest coverage.</p><p>AWS publishes a <a href="https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/" target="_blank" rel="noopener">list of supported services per region</a>. Unfortunately, the list does not allow comparing multiple regions. A <a href="https://awsservices.info/" target="_blank" rel="noopener">community project</a> solves that need.</p><p>There is still a slight chance that a single feature is not available in your region. Unfortunately, there is no table at that granularity. You have to read the docs carefully or test it out!</p><h2 id="Availability"><a href="#Availability" class="headerlink" title="Availability"></a>Availability</h2><p>Some AWS services are distributed worldwide by default. Examples are Route 53 or <a href="/how-to-replicate-your-data-with-dynamodb-global-tables/">DynamoDB Global Tables</a>. Other services are regional. A regional service is distributed across multiple availability zones within one region. Examples are S3 buckets, SQS queue, SNS topics. The last category of services is zonal. A zonal service runs in a single availability zone. Examples are EC2 instances and EBS volumes.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/11/aws-architect-mindset-scope@730w.webp 730w, /images/2021/11/aws-architect-mindset-scope@730w2x.webp 1460w, /images/2021/11/aws-architect-mindset-scope@610w.webp 610w, /images/2021/11/aws-architect-mindset-scope@610w2x.webp 1220w, /images/2021/11/aws-architect-mindset-scope@450w.webp 450w, /images/2021/11/aws-architect-mindset-scope@450w2x.webp 900w, /images/2021/11/aws-architect-mindset-scope@330w.webp 330w, /images/2021/11/aws-architect-mindset-scope@330w2x.webp 660w, /images/2021/11/aws-architect-mindset-scope@545w.webp 545w, /images/2021/11/aws-architect-mindset-scope@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/11/aws-architect-mindset-scope@730w.png 730w, /images/2021/11/aws-architect-mindset-scope@730w2x.png 1460w, /images/2021/11/aws-architect-mindset-scope@610w.png 610w, /images/2021/11/aws-architect-mindset-scope@610w2x.png 1220w, /images/2021/11/aws-architect-mindset-scope@450w.png 450w, /images/2021/11/aws-architect-mindset-scope@450w2x.png 900w, /images/2021/11/aws-architect-mindset-scope@330w.png 330w, /images/2021/11/aws-architect-mindset-scope@330w2x.png 660w, /images/2021/11/aws-architect-mindset-scope@545w.png 545w, /images/2021/11/aws-architect-mindset-scope@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/11/aws-architect-mindset-scope.png" alt="AWS services are global, regional, or zonal." title="AWS services are global, regional, or zonal."></picture></p><p>Depending on your availability requirements, you have to select the services with care or implement additional measures to achieve higher availability. For example, run multiple EC2 instances in an Auto Scaling Group spread across availability zones.</p><h2 id="Costs"><a href="#Costs" class="headerlink" title="Costs"></a>Costs</h2><p>AWS services are, in almost all cases, priced based on usage. To create a cost-efficient architecture, you have to familiarize yourself with the cost model of each service.</p><p>Don’t assume that services are priced in the same way. They all have their own pricing model using different dimensions. Also, keep in mind that prices vary in each region.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/11/aws-architect-mindset-traffic@730w.webp 730w, /images/2021/11/aws-architect-mindset-traffic@730w2x.webp 1460w, /images/2021/11/aws-architect-mindset-traffic@610w.webp 610w, /images/2021/11/aws-architect-mindset-traffic@610w2x.webp 1220w, /images/2021/11/aws-architect-mindset-traffic@450w.webp 450w, /images/2021/11/aws-architect-mindset-traffic@450w2x.webp 900w, /images/2021/11/aws-architect-mindset-traffic@330w.webp 330w, /images/2021/11/aws-architect-mindset-traffic@330w2x.webp 660w, /images/2021/11/aws-architect-mindset-traffic@545w.webp 545w, /images/2021/11/aws-architect-mindset-traffic@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/11/aws-architect-mindset-traffic@730w.png 730w, /images/2021/11/aws-architect-mindset-traffic@730w2x.png 1460w, /images/2021/11/aws-architect-mindset-traffic@610w.png 610w, /images/2021/11/aws-architect-mindset-traffic@610w2x.png 1220w, /images/2021/11/aws-architect-mindset-traffic@450w.png 450w, /images/2021/11/aws-architect-mindset-traffic@450w2x.png 900w, /images/2021/11/aws-architect-mindset-traffic@330w.png 330w, /images/2021/11/aws-architect-mindset-traffic@330w2x.png 660w, /images/2021/11/aws-architect-mindset-traffic@545w.png 545w, /images/2021/11/aws-architect-mindset-traffic@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/11/aws-architect-mindset-traffic.png" alt="AWS charges for network traffic in almost all cases." title="AWS charges for network traffic in almost all cases."></picture></p><p>One big factor that is often omitted is that you pay for traffic. <a href="/what-architects-need-to-know-about-networking-on-aws/">The network design has a considerable impact on your bill</a>. A rule of thumb: As long as the pricing page does not explicitly state traffic is free, you pay for it.</p><h2 id="Compliance"><a href="#Compliance" class="headerlink" title="Compliance"></a>Compliance</h2><p>Are you tasked to create an architecture in compliance with a framework (e.g., SOC)? You might read some marketing material that tells you: AWS is SOC compliant!? Only the services and regions in scope are. For example, <a href="https://aws.amazon.com/elasticache/memcached/" target="_blank" rel="noopener">ElastiCache for Memcached</a> is not in scope as well as the regions in China.</p><p>The latest SOC update includes 133 services (out of over 200) and 23 regions (out of 25). This leads us to the question: What is in scope? Rule of thumb: New services and regions aren’t. <a href="https://aws.amazon.com/compliance/services-in-scope/" target="_blank" rel="noopener">You can find a list here</a>.</p><p>To see if your region is in scope, you have to look at AWS’s detailed PDF report. The details also include a list of edge locations that are in scope.</p><h2 id="Quotas"><a href="#Quotas" class="headerlink" title="Quotas"></a>Quotas</h2><p>AWS protects your bill and its infrastructure with <a href="https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html" target="_blank" rel="noopener">quotas</a>. By default, you are not allowed to launch 100 EC2 instances or create 100 VPCs.</p><p>Some quotas can be increased while others can’t. For example, if your architecture relies on creating a VPC per customer, you should know the upper limit for that quota. If your AWS bill is significant enough for AWS, they might make exceptions for you. But you should know that upfront.</p><h2 id="Limitations"><a href="#Limitations" class="headerlink" title="Limitations"></a>Limitations</h2><p>Every AWS service has limitations. One of my favorite limitations is the network throughput of EC2 instances. If your EC2 instance has guaranteed 10 Gbit&#x2F;s connectivity, <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-network-bandwidth.html" target="_blank" rel="noopener">you only get 5Gbit&#x2F;s to and from the Internet</a>. Surprising, right? If you miss that detail, a traffic-bound architecture will become twice expensive as you thought.</p><p>I share another limitation with you: <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-resource-limits.html#port-25-throttle" target="_blank" rel="noopener">EC2 blocks outbound traffic on port 25 by default</a>. No matter what your security group says. You will have a hard time debugging this.</p><p>For every AWS service that you use, make sure to read the FAQs and docs carefully. Unfortunately, AWS makes it harder to find the limitations every year.</p><h2 id="SLAs"><a href="#SLAs" class="headerlink" title="SLAs"></a>SLAs</h2><p>EC2 instances have an SLA of 99.99%?! If you <a href="https://aws.amazon.com/compute/sla/" target="_blank" rel="noopener">look closer</a>, 99.99% does not apply to a single instance. The SLO 99.99% assumes that EC2 instances are deployed concurrently across two or more AZs in the same region.</p><p>The SLO for a single instance is 99.5%. The question now is what happens if AWS misses the SLO. And that’s where the SLA gets interesting. If AWS misses an SLO, you earn credits worth 10% of your EC2 costs in that month. That’s it. If AWS misses the SLO badly, AWS provides up to 100% in credits.</p><p>Other services provide different or no SLAs at all. Keep that in mind.</p><h2 id="IAM-Capabilities"><a href="#IAM-Capabilities" class="headerlink" title="IAM Capabilities"></a>IAM Capabilities</h2><p>Access to AWS services is granted by the IAM service. You control access by defining policies. Your architecture might rely on IAM to achieve tenant isolation.</p><p>Unfortunately, not all AWS services support the same set of features. Some services allow you to limit access to specific resources. Others allow you to restrict access based on tags in policies. But don’t take this for granted. <a href="https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html" target="_blank" rel="noopener">Double-check the AWS IAM docs</a>.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>To select the best services for your architecture, you need a broad understanding of all AWS services. Wide enough to know what services are good for. But you also need a deep understanding of each service you include in your architecture.</p><p>I listed many reasons why the details matter. I hope my tips help you to avoid common pitfalls when architecting on AWS.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>EBS Snapshot Pitfalls: Does your backup withstand reality?</title>
      <link>https://cloudonaut.io/ebs-snapshot-pitfalls/</link>
      <description>
        <![CDATA[<p>Does your disaster recovery plan deliver what it promises? Here are three reasons why your plan won’t stand up to reality. Learn about co]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/ebs/">ebs</category>
      <guid isPermaLink="true">https://cloudonaut.io/ebs-snapshot-pitfalls/</guid>
      <pubDate>Thu, 11 Nov 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Does your disaster recovery plan deliver what it promises? Here are three reasons why your plan won’t stand up to reality. Learn about common pitfalls when backing up EC2 instances with the help of EBS snapshots.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/10/backup@730w.webp 730w, /images/2021/10/backup@730w2x.webp 1460w, /images/2021/10/backup@610w.webp 610w, /images/2021/10/backup@610w2x.webp 1220w, /images/2021/10/backup@450w.webp 450w, /images/2021/10/backup@450w2x.webp 900w, /images/2021/10/backup@330w.webp 330w, /images/2021/10/backup@330w2x.webp 660w, /images/2021/10/backup@545w.webp 545w, /images/2021/10/backup@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/10/backup@730w.jpg 730w, /images/2021/10/backup@730w2x.jpg 1460w, /images/2021/10/backup@610w.jpg 610w, /images/2021/10/backup@610w2x.jpg 1220w, /images/2021/10/backup@450w.jpg 450w, /images/2021/10/backup@450w2x.jpg 900w, /images/2021/10/backup@330w.jpg 330w, /images/2021/10/backup@330w2x.jpg 660w, /images/2021/10/backup@545w.jpg 545w, /images/2021/10/backup@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/10/backup.jpg" alt="EBS Snapshot Pitfalls" title="EBS Snapshot Pitfalls"></picture></p><h2 id="A-crash-consistent-snapshot-leads-to-data-corruption"><a href="#A-crash-consistent-snapshot-leads-to-data-corruption" class="headerlink" title="A crash-consistent snapshot leads to data corruption"></a>A crash-consistent snapshot leads to data corruption</h2><p>AWS describes EBS snapshots as crash-consistent. What does that mean? Imagine that a machine suddenly breaks. In what condition is the data on the hard disk? We don’t know whether the application and operating system wrote a consistent state to disk before the interruption. In other words: a crash-consistent snapshot is worthless for disaster recovery. You might restore corrupt or inconsistent data.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/10/ebs-snapshot-pitfalls-1@730w.webp 730w, /images/2021/10/ebs-snapshot-pitfalls-1@730w2x.webp 1460w, /images/2021/10/ebs-snapshot-pitfalls-1@610w.webp 610w, /images/2021/10/ebs-snapshot-pitfalls-1@610w2x.webp 1220w, /images/2021/10/ebs-snapshot-pitfalls-1@450w.webp 450w, /images/2021/10/ebs-snapshot-pitfalls-1@450w2x.webp 900w, /images/2021/10/ebs-snapshot-pitfalls-1@330w.webp 330w, /images/2021/10/ebs-snapshot-pitfalls-1@330w2x.webp 660w, /images/2021/10/ebs-snapshot-pitfalls-1@545w.webp 545w, /images/2021/10/ebs-snapshot-pitfalls-1@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/10/ebs-snapshot-pitfalls-1@730w.png 730w, /images/2021/10/ebs-snapshot-pitfalls-1@730w2x.png 1460w, /images/2021/10/ebs-snapshot-pitfalls-1@610w.png 610w, /images/2021/10/ebs-snapshot-pitfalls-1@610w2x.png 1220w, /images/2021/10/ebs-snapshot-pitfalls-1@450w.png 450w, /images/2021/10/ebs-snapshot-pitfalls-1@450w2x.png 900w, /images/2021/10/ebs-snapshot-pitfalls-1@330w.png 330w, /images/2021/10/ebs-snapshot-pitfalls-1@330w2x.png 660w, /images/2021/10/ebs-snapshot-pitfalls-1@545w.png 545w, /images/2021/10/ebs-snapshot-pitfalls-1@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/10/ebs-snapshot-pitfalls-1.png" alt="EBS Snapshot Pitfalls #1" title="EBS Snapshot Pitfalls #1"></picture></p><p>Why is that? All EBS cares about are data blocks; it reads and writes ones and zeros. The operating system running on EC2 is responsible for persisting data on behalf of the application.</p><p>Be aware that even official tools like <a href="https://aws.amazon.com/backup/" target="_blank" rel="noopener">AWS Backup</a> do not offer a solution to this problem - not to mention the countless third-party providers.</p><blockquote><p>Solution: Before creating a snapshot, tell the application and operating system to write a consistent state to disk. For example on Linux, use <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-automation.html" target="_blank" rel="noopener">AWS Systems Manager Automation</a> to halt the application and ask the operating system to flush caches before creating an EBS snapshot. Running on Windows? Check out <a href="https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/application-consistent-snapshots.html" target="_blank" rel="noopener">Create a VSS application-consistent snapshot</a>.</p></blockquote><h2 id="A-restored-EBS-volume-requires-initialization"><a href="#A-restored-EBS-volume-requires-initialization" class="headerlink" title="A restored EBS volume requires initialization"></a>A restored EBS volume requires initialization</h2><p>Restoring a volume based on an EBS snapshot typically takes a few seconds only. However, the data is not available from the beginning. Instead, EBS restores the data asynchronously. You will notice high latencies during that period.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/10/ebs-snapshot-pitfalls-2@730w.webp 730w, /images/2021/10/ebs-snapshot-pitfalls-2@730w2x.webp 1460w, /images/2021/10/ebs-snapshot-pitfalls-2@610w.webp 610w, /images/2021/10/ebs-snapshot-pitfalls-2@610w2x.webp 1220w, /images/2021/10/ebs-snapshot-pitfalls-2@450w.webp 450w, /images/2021/10/ebs-snapshot-pitfalls-2@450w2x.webp 900w, /images/2021/10/ebs-snapshot-pitfalls-2@330w.webp 330w, /images/2021/10/ebs-snapshot-pitfalls-2@330w2x.webp 660w, /images/2021/10/ebs-snapshot-pitfalls-2@545w.webp 545w, /images/2021/10/ebs-snapshot-pitfalls-2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/10/ebs-snapshot-pitfalls-2@730w.png 730w, /images/2021/10/ebs-snapshot-pitfalls-2@730w2x.png 1460w, /images/2021/10/ebs-snapshot-pitfalls-2@610w.png 610w, /images/2021/10/ebs-snapshot-pitfalls-2@610w2x.png 1220w, /images/2021/10/ebs-snapshot-pitfalls-2@450w.png 450w, /images/2021/10/ebs-snapshot-pitfalls-2@450w2x.png 900w, /images/2021/10/ebs-snapshot-pitfalls-2@330w.png 330w, /images/2021/10/ebs-snapshot-pitfalls-2@330w2x.png 660w, /images/2021/10/ebs-snapshot-pitfalls-2@545w.png 545w, /images/2021/10/ebs-snapshot-pitfalls-2@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/10/ebs-snapshot-pitfalls-2.png" alt="EBS Snapshot Pitfalls #2" title="EBS Snapshot Pitfalls #2"></picture></p><p>If latency matters to your system, you should initialize a restored volume before ramping up traffic. To do so, make sure to read all blocks from the volume once. The following command does the trick on Linux. See <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-initialize.html" target="_blank" rel="noopener">Initialize Amazon EBS volumes</a> for more detailed information.</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">fio <span class="attribute">--filename</span>=/dev/xvda <span class="attribute">--rw</span>=read <span class="attribute">--bs</span>=128k <span class="attribute">--iodepth</span>=32 <span class="attribute">--ioengine</span>=libaio <span class="attribute">--direct</span>=1 <span class="attribute">--name</span>=ebs-restore</span><br></pre></td></tr></table></figure><p>Depending on the volume size, the volume type, and the EC2 instance type, initializing the volume might take a while. For example, it will take about 23 minutes to initialize an EBS volume of type <code>gp3</code> with 500 GB connected to an EC2 instance of type <code>m5a.large</code>. So make sure to add the initialization phase when planning the Recovery Time Objective (RTO). The bottleneck is the maximum throughput from EC2 instance to EBS volume of 360 MB&#x2F;s. See <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-optimized.html" target="_blank" rel="noopener">Amazon EBS–optimized instances</a> for more details.</p><p>It should also be mentioned that AWS offers a feature called <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-fast-snapshot-restore.html" target="_blank" rel="noopener">Amazon EBS fast snapshot restore</a>. By enabling fast restore, it is no longer necessary to initialize a restored volume as described above. The volume is ready for latency-critical workloads from the start. However, the pricing for fast snapshot restore make clear that this feature is not intended for this use case: $500 per snapshot and availability zone.</p><blockquote><p>Solution: Consider the time it takes to initialize a recovered volume when planning for disaster recovery. While creating an EBS volume based on a snapshot usually takes a few seconds only, it is necessary to read all blocks of a restored volume to ensure low latency throughput.</p></blockquote><h2 id="A-SLA-is-missing-for-restoring-EBS-volumes"><a href="#A-SLA-is-missing-for-restoring-EBS-volumes" class="headerlink" title="A SLA is missing for restoring EBS volumes"></a>A SLA is missing for restoring EBS volumes</h2><p>When someone else is operating our infrastructure, we need to rely on SLAs to ensure the provider complies with our requirements. Therefore, most AWS services come with a well-defined SLA.</p><p>Unfortunately, the <a href="https://aws.amazon.com/compute/sla/" target="_blank" rel="noopener">SLA for EC2 and EBS</a> is very unspecific. AWS does not specify any objectives when it comes to restoring EBS snapshots. Therefore, it isn’t easy to evaluate to what extent the system can be relied upon. For example, what will happen during a significant outage when many customers decide to restore machines from snapshots to recover machines in another availability zone or region?</p><blockquote><p>Solution: There is no technical solution to this problem. Talk to your AWS representative about this and ask them to be more specific about their SLA.</p></blockquote><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>To ensure that you can recover all data in an emergency, there are a few stumbling blocks to avoid.</p><ol><li>By default creating an EBS snapshot results in a crash-consistent backup. Restoring the snapshot might lead to corrupt or inconsistent data. Make sure to halt the application and flush caches before creating a snapshot to avoid that.</li><li>EBS restores data from snapshots asynchronously. Therefore, you should initialize the volume by reading all blocks before ramping up your workload.</li><li>Unfortunately, AWS does not publish any information about the extent to which we can rely on the recovery process of EBS snapshots. Especially if you want to plan for significant outages on AWS, this is very unsatisfactory. AWS needs to improve here.</li></ol><p><em>By the way, are you interested in an example on how to use AWS Systems Manager Automation to create application-consistent snapshots on Linux? Please let me know! I’m considering to write a blog post about that.</em></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Authentic and hands-on AWS consulting. Join superluminar!</title>
      <link>https://cloudonaut.io/join-superluminar/</link>
      <description>
        <![CDATA[<p>In 2015 - after building on Amazon Web Services for two years - I made a big decision. I wanted to become a consultant focusing on AWS. O]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/career/">career</category>
      <guid isPermaLink="true">https://cloudonaut.io/join-superluminar/</guid>
      <pubDate>Mon, 01 Nov 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>In 2015 - after building on Amazon Web Services for two years - I made a big decision. I wanted to become a consultant focusing on AWS. One of the best career decisions I have ever made. As a consultant, I worked with many different people in different industries and countries (Europe, US, India), from early-stage start-ups to mature enterprises. Over time, I was able to identify common pain points that many of my clients experienced. Back in the day, many clients struggled with EC2 patching, compliance, and of course, IAM policies. Over time, topics like containers, Serverless, and Infrastructure as Code gained traction. There is no other job where you learn so much.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/10/office@730w.webp 730w, /images/2021/10/office@730w2x.webp 1460w, /images/2021/10/office@610w.webp 610w, /images/2021/10/office@610w2x.webp 1220w, /images/2021/10/office@450w.webp 450w, /images/2021/10/office@450w2x.webp 900w, /images/2021/10/office@330w.webp 330w, /images/2021/10/office@330w2x.webp 660w, /images/2021/10/office@545w.webp 545w, /images/2021/10/office@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/10/office@730w.jpg 730w, /images/2021/10/office@730w2x.jpg 1460w, /images/2021/10/office@610w.jpg 610w, /images/2021/10/office@610w2x.jpg 1220w, /images/2021/10/office@450w.jpg 450w, /images/2021/10/office@450w2x.jpg 900w, /images/2021/10/office@330w.jpg 330w, /images/2021/10/office@330w2x.jpg 660w, /images/2021/10/office@545w.jpg 545w, /images/2021/10/office@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/10/office.jpg" alt="Office" title="Office"></picture></p><p>Are you interested in a job like this? I have good news: <a href="https://superluminar.io/?utm_source=cloudonaut&utm_medium=post&utm_campaign=q4-2021" target="_blank" rel="noopener">superluminar</a>, a cloud consulting boutique from Hamburg, wants to hire you! Before I go into details, let me share how superluminar identified a common pain point and how they solved it in the open. </p><h2 id="Common-pain-point-solved-by-superwerker"><a href="#Common-pain-point-solved-by-superwerker" class="headerlink" title="Common pain point solved by superwerker"></a>Common pain point solved by superwerker</h2><p>The consultants at superluminar noticed a pattern: Before deploying business-critical AWS applications, you need to provision AWS accounts. Sounds easy? It turns out that this is more complicated than expected. A few examples:</p><ul><li>You need a unique email address for every account (not so easy in an enterprise).</li><li>A baseline of security features is required in each account.</li><li>A cross-account incident dashboard is required for 24&#x2F;7 first-level support.</li><li>Contact details and tax details need to be configured.</li><li>User access needs to be managed.</li><li>And of course, AWS releases new features that require reconfiguring everything from time to time (e.g., VPCs before&#x2F;after Transit Gateway, or before&#x2F;after AWS Organizations).</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/10/superwerker_logo@730w.webp 730w, /images/2021/10/superwerker_logo@730w2x.webp 1460w, /images/2021/10/superwerker_logo@610w.webp 610w, /images/2021/10/superwerker_logo@610w2x.webp 1220w, /images/2021/10/superwerker_logo@450w.webp 450w, /images/2021/10/superwerker_logo@450w2x.webp 900w, /images/2021/10/superwerker_logo@330w.webp 330w, /images/2021/10/superwerker_logo@330w2x.webp 660w, /images/2021/10/superwerker_logo@545w.webp 545w, /images/2021/10/superwerker_logo@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/10/superwerker_logo@730w.png 730w, /images/2021/10/superwerker_logo@730w2x.png 1460w, /images/2021/10/superwerker_logo@610w.png 610w, /images/2021/10/superwerker_logo@610w2x.png 1220w, /images/2021/10/superwerker_logo@450w.png 450w, /images/2021/10/superwerker_logo@450w2x.png 900w, /images/2021/10/superwerker_logo@330w.png 330w, /images/2021/10/superwerker_logo@330w2x.png 660w, /images/2021/10/superwerker_logo@545w.png 545w, /images/2021/10/superwerker_logo@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/10/superwerker_logo.png" alt="superwerker logo" title="superwerker logo"></picture></p><p>Instead of coming up with yet another commercial solution, superluminar decided to solve the problem for all of their customers and the rest of the world. They teamed up with kreuzwerker to start superwerker - an open-source project to help you get started with AWS quickly without investing in consultants or devoting time to extensive research. superwerker is a free and open solution that lets you quickly set up an AWS Cloud environment following best practices for security and efficiency so you can focus on your core business.</p><p>Are you interested in solving real-world problems for clients while contributing to an open-source project at the same time? If so, please read on.</p><h2 id="About-superluminar"><a href="#About-superluminar" class="headerlink" title="About superluminar"></a>About superluminar</h2><p><a href="https://superluminar.io/?utm_source=cloudonaut&utm_medium=post&utm_campaign=q4-2021" target="_blank" rel="noopener">superluminar</a>, founded in Hamburg in 2017, is a cloud consulting boutique specialized in AWS and known for authentic hands-on consulting. superluminar advises start-ups, media houses, and MDAX companies. As an AWS Advanced Partner, superluminar works closely with AWS and other partners from the AWS ecosystem.</p><p>superluminar is a great place to work with highly skilled people. But we all know that modern technology and skills do not solve all problems. superluminar coaches companies and their employees sustainably and in partnership on their journey to the cloud, aka digital transformation.</p><p>Sounds interesting? If you are based in Germany, check out the following positions at superluminar:</p><ul><li><a href="https://superluminar.io/jobs/senior-consultant-cloud-data-engineering/?utm_source=cloudonaut&utm_medium=post&utm_campaign=q4-2021" target="_blank" rel="noopener">Senior Consultant Cloud Data Engineering (m&#x2F;w&#x2F;d)</a></li><li><a href="https://superluminar.io/jobs/senior-consultant-cloud-engineering/?utm_source=cloudonaut&utm_medium=post&utm_campaign=q4-2021" target="_blank" rel="noopener">Senior Consultant Cloud Engineering (m&#x2F;w&#x2F;d)</a></li><li><a href="https://superluminar.io/jobs/junior-consultant-cloud-engineering/?utm_source=cloudonaut&utm_medium=post&utm_campaign=q4-2021" target="_blank" rel="noopener">Junior Consultant Cloud Engineering (m&#x2F;w&#x2F;d)</a></li></ul><p>None of the positions is for you? Reach out to superluminar and tell them why they should hire you: <a href="mailto:&#x6a;&#111;&#x62;&#x73;&#64;&#x73;&#117;&#112;&#x65;&#114;&#x6c;&#x75;&#109;&#105;&#110;&#x61;&#x72;&#46;&#105;&#111;">jobs@superluminar.io</a></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Celebrating five years of marbot</title>
      <link>https://cloudonaut.io/celebrating-5-years-of-marbot/</link>
      <description>
        <![CDATA[<p>Five years ago, we released <a href="https://marbot.io/" target="_blank" rel="noopener">marbot - ChatOps for AWS
</a>. Back in the day, m]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/celebrating-5-years-of-marbot/</guid>
      <pubDate>Mon, 25 Oct 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Five years ago, we released <a href="https://marbot.io/" target="_blank" rel="noopener">marbot - ChatOps for AWS</a>. Back in the day, marbot connected Amazon CloudWatch and Slack to spread alerts among your team members. Today, marbot configures AWS monitoring rules, receives alerts from any AWS service, and provides context to solve incidents quickly in Slack and Microsoft Teams. A big thank you to all our customers who trust marbot. Some of them have been using marbot since day one. More than 1,000 teams close 7,500+ alerts every week. Thousands of AWS accounts are monitored by marbot.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/10/5-years@730w.webp 730w, /images/2021/10/5-years@730w2x.webp 1460w, /images/2021/10/5-years@610w.webp 610w, /images/2021/10/5-years@610w2x.webp 1220w, /images/2021/10/5-years@450w.webp 450w, /images/2021/10/5-years@450w2x.webp 900w, /images/2021/10/5-years@330w.webp 330w, /images/2021/10/5-years@330w2x.webp 660w, /images/2021/10/5-years@545w.webp 545w, /images/2021/10/5-years@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/10/5-years@730w.jpg 730w, /images/2021/10/5-years@730w2x.jpg 1460w, /images/2021/10/5-years@610w.jpg 610w, /images/2021/10/5-years@610w2x.jpg 1220w, /images/2021/10/5-years@450w.jpg 450w, /images/2021/10/5-years@450w2x.jpg 900w, /images/2021/10/5-years@330w.jpg 330w, /images/2021/10/5-years@330w2x.jpg 660w, /images/2021/10/5-years@545w.jpg 545w, /images/2021/10/5-years@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/10/5-years.jpg" alt="Celebrating five years of marbot" title="Celebrating five years of marbot"></picture></p><p>In this post, I summarize the history of marbot and provide an outlook of what is coming.</p><h2 id="Past"><a href="#Past" class="headerlink" title="Past"></a>Past</h2><p>2016</p><ul><li>marbot wins the <a href="https://devpost.com/software/marbot" target="_blank" rel="noopener">Serverless Chatbot Competition 2016</a></li><li>marbot is generally available</li></ul><p>2017</p><ul><li>Launch of marbot plus with a 14-day free trial</li><li><a href="/lessons-learned-serverless-chatbot-architecture-for-marbot/">Technical insight 1: Serverless Chatbot architecture for marbot</a></li><li>Launch of alert <a href="https://marbot.io/help/aggregation.html" target="_blank" rel="noopener">aggregation</a></li><li>marbot @ AWS re:invent</li></ul><p>2018</p><ul><li><a href="https://marbot.io/blog/aws-summit-berlin-2018.html" target="_blank" rel="noopener">marbot @ AWS Summit Berlin</a></li><li><a href="/evolutionary-serverless-architecture/">Technical insight 2: JeffConfg Hamburg 2018 talk</a></li></ul><p>2019</p><ul><li><a href="https://marbot.io/blog/solve-aws-incidents-with-runbooks.html" target="_blank" rel="noopener">Launch of runbooks</a></li><li>Redesign<br><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/10/marbot-redesign@730w.webp 730w, /images/2021/10/marbot-redesign@730w2x.webp 1460w, /images/2021/10/marbot-redesign@610w.webp 610w, /images/2021/10/marbot-redesign@610w2x.webp 1220w, /images/2021/10/marbot-redesign@450w.webp 450w, /images/2021/10/marbot-redesign@450w2x.webp 900w, /images/2021/10/marbot-redesign@330w.webp 330w, /images/2021/10/marbot-redesign@330w2x.webp 660w, /images/2021/10/marbot-redesign@545w.webp 545w, /images/2021/10/marbot-redesign@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/10/marbot-redesign@730w.png 730w, /images/2021/10/marbot-redesign@730w2x.png 1460w, /images/2021/10/marbot-redesign@610w.png 610w, /images/2021/10/marbot-redesign@610w2x.png 1220w, /images/2021/10/marbot-redesign@450w.png 450w, /images/2021/10/marbot-redesign@450w2x.png 900w, /images/2021/10/marbot-redesign@330w.png 330w, /images/2021/10/marbot-redesign@330w2x.png 660w, /images/2021/10/marbot-redesign@545w.png 545w, /images/2021/10/marbot-redesign@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/10/marbot-redesign.png" alt="Redesign" title="Redesign"></picture></li><li><a href="https://marbot.io/blog/aws-marketplace.html" target="_blank" rel="noopener">Launch of AWS Marketplace integration</a> to replace marbot plus</li><li><a href="https://marbot.io/blog/aws-reinvent-2019.html" target="_blank" rel="noopener">marbot @ AWS re:invent</a></li></ul><p>2020</p><ul><li><a href="https://marbot.io/blog/launch-of-notifications.html" target="_blank" rel="noopener">Launch of notifications</a></li><li>AWS released a competing product: <a href="https://marbot.io/blog/aws-chatbot-vs-marbot.html" target="_blank" rel="noopener">AWS Chatbot</a></li><li><a href="/resilient-event-driven-serverless-architectures-isolate-your-dependencies/">Technical insight 3: Isolate your dependencies</a></li><li><a href="https://marbot.io/blog/marbot-for-microsoft-teams-launch.html" target="_blank" rel="noopener">Launch of marbot for Microsoft Teams</a></li></ul><p>2021</p><ul><li>Over 100 AWS services are enriched with <a href="https://marbot.io/help/quick-link.html" target="_blank" rel="noopener">Quick Links</a>.</li><li>marbot has been running for more than five years</li></ul><h2 id="Future"><a href="#Future" class="headerlink" title="Future"></a>Future</h2><p>The future of marbot has always been steered by customer demand and data.</p><p>Every one or two years, we reach out to all our customers and ask for feedback. What are areas that need improvement? Are new topics popping up that we should cover? Within the following days, we will start a new survey to gather up-to-date customer feedback. We will reach out to you in Slack and Microsoft Teams.</p><p>To celebrate 5 years of marbot, we created a brand new marbot t-shirt (sold out). </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/10/marbot-tshirt@730w.webp 730w, /images/2021/10/marbot-tshirt@730w2x.webp 1460w, /images/2021/10/marbot-tshirt@610w.webp 610w, /images/2021/10/marbot-tshirt@610w2x.webp 1220w, /images/2021/10/marbot-tshirt@450w.webp 450w, /images/2021/10/marbot-tshirt@450w2x.webp 900w, /images/2021/10/marbot-tshirt@330w.webp 330w, /images/2021/10/marbot-tshirt@330w2x.webp 660w, /images/2021/10/marbot-tshirt@545w.webp 545w, /images/2021/10/marbot-tshirt@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/10/marbot-tshirt@730w.png 730w, /images/2021/10/marbot-tshirt@730w2x.png 1460w, /images/2021/10/marbot-tshirt@610w.png 610w, /images/2021/10/marbot-tshirt@610w2x.png 1220w, /images/2021/10/marbot-tshirt@450w.png 450w, /images/2021/10/marbot-tshirt@450w2x.png 900w, /images/2021/10/marbot-tshirt@330w.png 330w, /images/2021/10/marbot-tshirt@330w2x.png 660w, /images/2021/10/marbot-tshirt@545w.png 545w, /images/2021/10/marbot-tshirt@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/10/marbot-tshirt.png" alt="T-Shirt" title="T-Shirt"></picture></p><p>Take your AWS monitoring to a new level! <a href="https://marbot.io/" target="_blank" rel="noopener">Add marbot to Slack or Microsoft Teams</a> and start your 14-day free trial. No credit card required.</p><p>Thanks for your support,<br>Andreas &amp; Michael</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Containers on AWS: ECS, EKS, and Fargate</title>
      <link>https://cloudonaut.io/containers-on-aws-ecs-eks-and-fargate/</link>
      <description>
        <![CDATA[<p>The container landscape in general and on AWS in particular is changing quickly. AWS releases new services and features to deploy contain]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <category domain="https://cloudonaut.io/tag/eks/">eks</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <guid isPermaLink="true">https://cloudonaut.io/containers-on-aws-ecs-eks-and-fargate/</guid>
      <pubDate>Thu, 02 Sep 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The container landscape in general and on AWS in particular is changing quickly. AWS releases new services and features to deploy containers constantly. Currently, the most interesting options are: <a href="https://aws.amazon.com/ecs/" target="_blank" rel="noopener">Elastic Container Service (ECS)</a> and <a href="https://aws.amazon.com/eks/" target="_blank" rel="noopener">Elastic Kubernetes Service (EKS)</a>. On top of that, there are two options to provide the compute resources for your containers: virtual machines (EC2) or Fargate. When thinking about how to deploy your container workload on AWS, you should evaluate these four options. Read on to learn about these options and their differences.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/09/container-ship@730w.webp 730w, /images/2021/09/container-ship@730w2x.webp 1460w, /images/2021/09/container-ship@610w.webp 610w, /images/2021/09/container-ship@610w2x.webp 1220w, /images/2021/09/container-ship@450w.webp 450w, /images/2021/09/container-ship@450w2x.webp 900w, /images/2021/09/container-ship@330w.webp 330w, /images/2021/09/container-ship@330w2x.webp 660w, /images/2021/09/container-ship@545w.webp 545w, /images/2021/09/container-ship@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/09/container-ship@730w.jpg 730w, /images/2021/09/container-ship@730w2x.jpg 1460w, /images/2021/09/container-ship@610w.jpg 610w, /images/2021/09/container-ship@610w2x.jpg 1220w, /images/2021/09/container-ship@450w.jpg 450w, /images/2021/09/container-ship@450w2x.jpg 900w, /images/2021/09/container-ship@330w.jpg 330w, /images/2021/09/container-ship@330w2x.jpg 660w, /images/2021/09/container-ship@545w.jpg 545w, /images/2021/09/container-ship@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/09/container-ship.jpg" alt="Containers on AWS: ECS, EKS, and Fargate" title="Containers on AWS: ECS, EKS, and Fargate"></picture></p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><h2 id="What-is-ECS"><a href="#What-is-ECS" class="headerlink" title="What is ECS?"></a>What is ECS?</h2><p>ECS (Elastic Container Service) is a fault-tolerant and scalable container management service. An ECS cluster manages compute resources and orchestrates the container lifecycle: allocate CPU and memory, fetch container image, start container, monitor container, stop container, clean up.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/09/containers-on-aws-overview@730w.webp 730w, /images/2021/09/containers-on-aws-overview@730w2x.webp 1460w, /images/2021/09/containers-on-aws-overview@610w.webp 610w, /images/2021/09/containers-on-aws-overview@610w2x.webp 1220w, /images/2021/09/containers-on-aws-overview@450w.webp 450w, /images/2021/09/containers-on-aws-overview@450w2x.webp 900w, /images/2021/09/containers-on-aws-overview@330w.webp 330w, /images/2021/09/containers-on-aws-overview@330w2x.webp 660w, /images/2021/09/containers-on-aws-overview@545w.webp 545w, /images/2021/09/containers-on-aws-overview@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/09/containers-on-aws-overview@730w.png 730w, /images/2021/09/containers-on-aws-overview@730w2x.png 1460w, /images/2021/09/containers-on-aws-overview@610w.png 610w, /images/2021/09/containers-on-aws-overview@610w2x.png 1220w, /images/2021/09/containers-on-aws-overview@450w.png 450w, /images/2021/09/containers-on-aws-overview@450w2x.png 900w, /images/2021/09/containers-on-aws-overview@330w.png 330w, /images/2021/09/containers-on-aws-overview@330w2x.png 660w, /images/2021/09/containers-on-aws-overview@545w.png 545w, /images/2021/09/containers-on-aws-overview@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/09/containers-on-aws-overview.png" alt="Containers on AWS Overview" title="Containers on AWS Overview"></picture></p><p>Another important aspect of ECS: automating deployments. ECS supports rolling updates and blue-green deployments. Besides that, ECS monitors the health of your applications and replaces failed containers automatically.</p><p>A big benefit of ECS is the tight integration into the AWS ecosystem. For example, the ECS API works very similarly to any other AWS APIs which provide frictionless authentication and authorization through AWS Identity and Access Management (IAM).</p><p>ECS is free of charge, however, it is a proprietary solution. That’s the most important difference to the other container management service: EKS.</p><h2 id="What-is-EKS"><a href="#What-is-EKS" class="headerlink" title="What is EKS?"></a>What is EKS?</h2><p>Kubernetes (K8s) is an open-source system for automating deployment, scaling, and management of containerized applications. On a high level, ECS and Kubernetes are solving the same problems. My first contact with K8s was in 2017. Back then, I learned from a vivid example that it is no child’s play to make K8s ready for production.</p><p>Luckily, AWS came along with Elastic Kubernetes Service (EKS) in 2018. EKS offers fully-managed Kubernetes as a service. That’s a game changer, as you can focus on deploying your containers instead of investing a lot of energy in operating a distributed system.</p><p>Unlike ECS, Kubernetes cannot be used only on AWS. Every major cloud provider comes with a managed Kubernetes service as well. Furthermore, you could deploy K8s on-premises and even on your local machine.</p><p>There’s a catch, though: an EKS cluster costs $72 per month. Quite a lot, compared to $0 per month for an ECS cluster. It is also disappointing that EKS does not support the latest K8s versions.</p><h2 id="What-is-Cluster-Auto-Scaling-Managed-Node-Group"><a href="#What-is-Cluster-Auto-Scaling-Managed-Node-Group" class="headerlink" title="What is Cluster Auto Scaling&#x2F;Managed Node Group?"></a>What is Cluster Auto Scaling&#x2F;Managed Node Group?</h2><p>The hard truth about containers: you are adding an additional layer to your infrastructure. Instead of managing virtual machines only, you have to manage virtual machines and containers. That adds extra complexity. For example, you need to scale both layers: the containers as well as the underlying virtual machines.</p><p>Fortunately, two features reduce the complexity of managing virtual machines for your container clusters:</p><ul><li>ECS Cluster Auto Scaling manages a pool of EC2 instances forming an ECS cluster. A capacity provider interacts with an Auto Scaling Group to increase or decrease the number of EC2 instances on-demand.</li><li>The EKS Managed Node Group launches and terminates EC2 instances on-demand based on an Amazon Machine Image (AMI) provided by AWS.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/09/containers-on-aws-ecs-asg@730w.webp 730w, /images/2021/09/containers-on-aws-ecs-asg@730w2x.webp 1460w, /images/2021/09/containers-on-aws-ecs-asg@610w.webp 610w, /images/2021/09/containers-on-aws-ecs-asg@610w2x.webp 1220w, /images/2021/09/containers-on-aws-ecs-asg@450w.webp 450w, /images/2021/09/containers-on-aws-ecs-asg@450w2x.webp 900w, /images/2021/09/containers-on-aws-ecs-asg@330w.webp 330w, /images/2021/09/containers-on-aws-ecs-asg@330w2x.webp 660w, /images/2021/09/containers-on-aws-ecs-asg@545w.webp 545w, /images/2021/09/containers-on-aws-ecs-asg@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/09/containers-on-aws-ecs-asg@730w.png 730w, /images/2021/09/containers-on-aws-ecs-asg@730w2x.png 1460w, /images/2021/09/containers-on-aws-ecs-asg@610w.png 610w, /images/2021/09/containers-on-aws-ecs-asg@610w2x.png 1220w, /images/2021/09/containers-on-aws-ecs-asg@450w.png 450w, /images/2021/09/containers-on-aws-ecs-asg@450w2x.png 900w, /images/2021/09/containers-on-aws-ecs-asg@330w.png 330w, /images/2021/09/containers-on-aws-ecs-asg@330w2x.png 660w, /images/2021/09/containers-on-aws-ecs-asg@545w.png 545w, /images/2021/09/containers-on-aws-ecs-asg@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/09/containers-on-aws-ecs-asg.png" alt="ECS Auto Scaling Group" title="ECS Auto Scaling Group"></picture></p><p>Keep in mind that the EC2 instances are still running in your AWS account. Which means you are responsible for those virtual machines. AWS is just offering a tool to make that job a little bit easier.</p><h2 id="What-is-Fargate"><a href="#What-is-Fargate" class="headerlink" title="What is Fargate?"></a>What is Fargate?</h2><p>AWS Fargate is a game changer for container workloads on AWS. Fargate provides a fully-managed compute engine for ECS and EKS. No need to manage virtual machines - as described above - any more. Instead AWS provides compute capacity for your containers on-demand. That’s decreasing complexity dramatically. I might even argue that operating both layers containers and virtual machines yourself is not economically viable in most cases.</p><p>Fargate allows you to allocate 0.25 to 4 vCPUs and 0.5 GB to 30.0 GB memory.</p><p>Keep in mind that Fargate comes with some limitations. A few examples:</p><ul><li>Running containers in <code>privileged</code> mode is not supported.</li><li>It is not possible to configure custom DNS servers when launching a container.</li><li>Making use of an IPC namespace to allow containers to communicate directly through shared-memory is not supported.</li></ul><p>Most likely, those limitations will not hinder you from launching your containers on Fargate. But you might want to check the AWS documentation whether one of the limitations affects your scenario.</p><h2 id="Cluster-and-Compute-Costs"><a href="#Cluster-and-Compute-Costs" class="headerlink" title="Cluster and Compute Costs"></a>Cluster and Compute Costs</h2><p>As mentioned above there is a huge difference between the costs for cluster management. On the one hand, ECS is free of charge. On the other hand, each EKS cluster costs $72 per month. That’s a huge difference. Especially, as AWS recommends to use separate clusters to isolate workloads from each other.</p><p>“However, because Kubernetes is a single-tenant orchestrator, Fargate cannot guarantee pod-level security isolation. You should run sensitive workloads or untrusted workloads that need complete security isolation using separate Amazon EKS clusters.” <sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup></p><p>So you will end up with more than one cluster. For example, to isolate your test environment from your production environment.</p><p>Let’s have a look at compute costs next. First of all, there is no big difference between ECS and EKS when it comes to compute costs. The underlying EC2 instances are priced the same no matter if you are using them with ECS or EKS.</p><p>However, there is a significant difference when comparing EC2 with Fargate. Be aware that the surcharge differs significantly depending on the allocated CPU and memory resources.</p><table class="table table-striped table-responsive"><thead><tr><th>Category</th><th>Type</th><th>vCPU</th><th>Memory</th><th>Monthly Costs</th></tr></thead><tbody><tr><td>XS</td><td>EC2 Instance  t3.nano</td><td>2</td><td>0.5 GiB</td><td>$3.80</td></tr><tr><td></td><td>Fargate</td><td>0.25</td><td>0.5 GiB</td><td>$8.88</td></tr><tr><td>L</td><td>EC2 Instance  m5.large</td><td>2</td><td>8.0 GiB</td><td>$70.08</td></tr><tr><td></td><td>Fargate</td><td>2</td><td>8.0 GB</td><td>$83.89</td></tr><tr><td>XXL</td><td>EC2 Instance  r5.xlarge</td><td>4</td><td>32.0 GiB</td><td>$183.96</td></tr><tr><td></td><td>Fargate</td><td>4</td><td>30.0 GB</td><td>$212.59</td></tr></tbody></table><p>I’d like to mention that comparing EC2 costs with Fargate costs is not really fair. This is simply because a container cluster based on EC2 instances will never be utilized to capacity. For example, placing containers of different sizes will cause fragmentation. Also, it is hard to calculate the cost savings by reducing the infrastructure complexity when switching from virtual machines to Fargate.</p><p>There is another aspect that you should take into consideration. Fargate for ECS comes with three different pricing models:</p><ul><li>On-demand, that’s the default pricing model that we have discussed above. Pricing is per second with a 1-minute minimum. </li><li>Savings Plans offer a simple deal. You commit to a certain usage, AWS provides you a discount of up to 50% compared to the on-demand price.</li><li>Spot, allows you to benefit from spare capacity within AWS’s infrastructure. You get a discount of 68.87% compared to the on-demand price. In return your containers might be terminated by AWS 2 minutes after sending an interruption notification.</li></ul><p>Unfortunately, Fargate for EKS does not support Spot.</p><h2 id="Infrastructure-Scaling"><a href="#Infrastructure-Scaling" class="headerlink" title="Infrastructure Scaling"></a>Infrastructure Scaling</h2><p>As discussed above ECS Cluster Auto Scaling and EKS Managed Node Group simplify the task of managing a fleet of EC2 instances. In general, both tools solve the same problem. However, there are two important differences:</p><ol><li>If necessary the EKS Managed Node Group will move containers to another virtual machine to be able to decrease the overcapacity within the cluster. ECS Cluster Auto Scaling does not consider moving a container to another virtual machine to reduce the cluster size at all.</li><li>AWS maintains pre-configured AMIs for ECS and EKS. However, the ECS Cluster Auto Scaling does not support a rolling-update to update to the latest AMI. On the other hand, the EKS Managed Node Group comes with rolling updates for the virtual machines by default.</li></ol><p>Therefore, EKS is the better choice when running your container workload on top of EC2. However, I highly recommend avoiding spinning up EC2 instances for your container cluster at all.</p><p>Scaling the underlying infrastructure is no longer your job when using Fargate. Instead, AWS is responsible for managing the underlying machines.</p><h2 id="Load-Balancing-and-Service-Discovery"><a href="#Load-Balancing-and-Service-Discovery" class="headerlink" title="Load Balancing and Service Discovery"></a>Load Balancing and Service Discovery</h2><p>Whenever feasible, I go with a managed service instead of operating a service on my own. That’s true for load balancing as well. </p><p>AWS offers two kinds of managed load balancers:</p><ul><li>Application Load Balancer (ALB) operates on layer 7 and is capable of routing  incoming HTTP&#x2F;HTTPS requests to a target.</li><li>Network Load Balancer (NLB) operates on layer 4 and routes TCP and UDP connections to a target.</li></ul><p>An ECS service will launch containers and register them at an ALB&#x2F;NLB out-of-the box. With EKS you can achieve a very similar behaviour by using the <a href="https://github.com/kubernetes-sigs/aws-alb-ingress-controller" target="_blank" rel="noopener">ALB Ingress Controller for Kubernetes</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/09/containers-on-aws-ecs-elb@730w.webp 730w, /images/2021/09/containers-on-aws-ecs-elb@730w2x.webp 1460w, /images/2021/09/containers-on-aws-ecs-elb@610w.webp 610w, /images/2021/09/containers-on-aws-ecs-elb@610w2x.webp 1220w, /images/2021/09/containers-on-aws-ecs-elb@450w.webp 450w, /images/2021/09/containers-on-aws-ecs-elb@450w2x.webp 900w, /images/2021/09/containers-on-aws-ecs-elb@330w.webp 330w, /images/2021/09/containers-on-aws-ecs-elb@330w2x.webp 660w, /images/2021/09/containers-on-aws-ecs-elb@545w.webp 545w, /images/2021/09/containers-on-aws-ecs-elb@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/09/containers-on-aws-ecs-elb@730w.png 730w, /images/2021/09/containers-on-aws-ecs-elb@730w2x.png 1460w, /images/2021/09/containers-on-aws-ecs-elb@610w.png 610w, /images/2021/09/containers-on-aws-ecs-elb@610w2x.png 1220w, /images/2021/09/containers-on-aws-ecs-elb@450w.png 450w, /images/2021/09/containers-on-aws-ecs-elb@450w2x.png 900w, /images/2021/09/containers-on-aws-ecs-elb@330w.png 330w, /images/2021/09/containers-on-aws-ecs-elb@330w2x.png 660w, /images/2021/09/containers-on-aws-ecs-elb@545w.png 545w, /images/2021/09/containers-on-aws-ecs-elb@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/09/containers-on-aws-ecs-elb.png" alt="ECS ELB" title="ECS ELB"></picture></p><p>Kubernetes is known for its built-in service discovery. By default, <code>kube-proxy</code> will route inter-service requests within the cluster. For example, a container of the frontend service can easily send a request to the backend service, without needing to know which containers serve requests for the backend service.</p><p>When discussing the differences between ECS and EKS, I’ve often heard that ECS does not provide service discovery at all. However, that is not correct.</p><p>By default, ECS integrates with a service called AWS Cloud Map, which offers service discovery over an API or DNS. An ECS service will register new containers at Cloud Map. Other services are then able to resolve the service’s IP addresses via DNS.</p><p>Both ECS and EKS integrate with AWS App Mesh. Doing so allows you to build a service mesh based on the open source Envoy proxy.</p><h2 id="Summary-and-Comparison"><a href="#Summary-and-Comparison" class="headerlink" title="Summary and Comparison"></a>Summary and Comparison</h2><p>The following table summarizes the similarities and differences discussed above. Personally, I still prefer ECS for most scenarios.</p><div class="table-responsive"><table class="table table-striped"><thead><tr><th></th><th>ECS + Fargate</th><th>EKS + Fargate</th><th>ECS + Cluster Auto Scaling</th><th>EKS + Managed Node Group</th></tr></thead><tbody><tr><td>Deployment Options</td><td><p>⚠️ AWS only</p></td><td><p>⚠️ AWS only</p></td><td><p>⚠️ AWS only (except ECS Anywhere)</p></td><td><p>✅ Major cloud providers, On-premises, …</p></td></tr><tr><td>Availability</td><td><p>✅ All AWS regions</p></td><td><p>⚠️ Available in 18 regions</p></td><td><p>✅ All AWS regions</p></td><td><p>✅ Available in most regions</p></td></tr><tr><td>Cluster Costs (per month)</td><td><p>$ 0.00</p></td><td><p>$ 72.00</p></td><td><p>$ 0.00</p></td><td><p>$ 72.00</p></td></tr><tr><td>Compute Costs</td><td><p>💰💰</p></td><td><p>💰💰</p></td><td><p>💰</p></td><td><p>💰</p></td></tr><tr><td>Compute Pricing Options</td><td><p>✅ On-demand<br>✅ Spot<br>✅ Savings Plan</p></td><td><p>✅ On-demand<br>❌ Spot<br>✅ Savings Plan</p></td><td><p>✅ On-demand<br>✅ Spot<br>✅ Savings Plan</p></td><td><p>✅ On-demand<br>✅ Spot<br>✅ Savings Plan</p></td></tr><tr><td>Virtual Machines</td><td><p>✅ Does not involve managing EC2 instances.</p></td><td><p>✅ Does not involve managing EC2 instances.</p></td><td><p>⚠️ EC2 instances running in your AWS account. ECS Cluster Auto Scaling does not cover all aspects of operating virtual machines (e.g. rolling update to update AMI).</p></td><td><p>⚠️ EC2 instances running in your AWS account, but Managed Node Group + Cluster Autoscaler automate most operations tasks.</p></td></tr><tr><td>Infrastructure Scaling</td><td><p>✅ AWS takes care of the scaling of the infrastructure.</p></td><td><p>✅ AWS takes care of the scaling of the infrastructure.</p></td><td><p>⚠️ ECS Cluster Auto Scaling works fine for scaling up. But will not scale down automatically in most scenarios.</p></td><td><p>✅ Managed Node Groups and Cluster Autoscaler scale the cluster capacity up and down automatically.</p></td></tr><tr><td>Networking</td><td><p>✅ ENI and Security Group per task&#x2F;container.</p></td><td><p>✅ ENI and Security Group per pod&#x2F;container.</p></td><td><p>✅ ENI and Security Group per task&#x2F;container.</p></td><td><p>⚠️ Multiple containers&#x2F;pods share the same ENI and Security Group.</p></td></tr><tr><td>Load Balancing</td><td><p>✅ ALB + NLB</p></td><td><p>✅ ALB</p></td><td><p>✅ ALB + NLB + CLB</p></td><td><p>✅ ALB + NLB + CLB</p></td></tr><tr><td>Service Discovery</td><td><p>✅ Cloud Map</p></td><td><p>✅ K8s</p></td><td><p>✅ Cloud Map</p></td><td><p>✅ K8s</p></td></tr></tbody></table></div><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. <a href="https://docs.aws.amazon.com/eks/latest/userguide/fargate.html#fargate-considerations">AWS Fargate considerations</a> <a href="#fnref:1" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>EC2 Checklist: 7 things to do after launching an instance</title>
      <link>https://cloudonaut.io/ec2-checklist-seven-things-to-do-after-launching-an-instance/</link>
      <description>
        <![CDATA[<p>Launching an EC2 instance takes minutes. Keeping your virtual machines secure and maintaining your VMs is more work. In this blog post, I]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <category domain="https://cloudonaut.io/tag/ssm/">ssm</category>
      <category domain="https://cloudonaut.io/tag/backup/">backup</category>
      <category domain="https://cloudonaut.io/tag/inspector/">inspector</category>
      <guid isPermaLink="true">https://cloudonaut.io/ec2-checklist-seven-things-to-do-after-launching-an-instance/</guid>
      <pubDate>Wed, 25 Aug 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Launching an EC2 instance takes minutes. Keeping your virtual machines secure and maintaining your VMs is more work. In this blog post, I share seven things to do <em>after</em> launching a Linux, Windows, or macOS instance:</p><ol><li>Configure remote access with SSM Session Manager.</li><li>Back up your data with AWS Backup.</li><li>Install patches on a schedule with SSM Patch Manager.</li><li>Configure the CloudWatch agent to ship logs, disk, and memory metrics to CloudWatch.</li><li>Add CloudWatch alarms for alerting and instance recovery.</li><li>Disable IMDSv1 to prevent a <a href="https://aws.amazon.com/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service/" target="_blank" rel="noopener">Capital One-like data breach</a>.</li><li>Schedule regular software scans to detect vulnerabilities with Inspector.</li></ol><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/42-ec2-checklist-seven-things-to-do-after-launching-an-instance/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/08/plan@730w.webp 730w, /images/2021/08/plan@730w2x.webp 1460w, /images/2021/08/plan@610w.webp 610w, /images/2021/08/plan@610w2x.webp 1220w, /images/2021/08/plan@450w.webp 450w, /images/2021/08/plan@450w2x.webp 900w, /images/2021/08/plan@330w.webp 330w, /images/2021/08/plan@330w2x.webp 660w, /images/2021/08/plan@545w.webp 545w, /images/2021/08/plan@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/08/plan@730w.jpg 730w, /images/2021/08/plan@730w2x.jpg 1460w, /images/2021/08/plan@610w.jpg 610w, /images/2021/08/plan@610w2x.jpg 1220w, /images/2021/08/plan@450w.jpg 450w, /images/2021/08/plan@450w2x.jpg 900w, /images/2021/08/plan@330w.jpg 330w, /images/2021/08/plan@330w2x.jpg 660w, /images/2021/08/plan@545w.jpg 545w, /images/2021/08/plan@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/08/plan.jpg" alt="7 tips for operating and securing EC2 instances " title="7 tips for operating and securing EC2 instances "></picture></p><p>Read on and learn to implement each best practice in your AWS account.</p><h2 id="Configure-remote-access-with-SSM-Session-Manager"><a href="#Configure-remote-access-with-SSM-Session-Manager" class="headerlink" title="Configure remote access with SSM Session Manager"></a>Configure remote access with SSM Session Manager</h2><p>Gone are the days where we connected to virtual machines using SSH or RDP directly. <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html" target="_blank" rel="noopener">AWS Systems Manager Session Manager</a> allows you to connect to any EC2 instance without inbound network connectivity. All you need is the SSM agent<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup> (preinstalled on Amazon Linux, Amazon Linux 2, Ubuntu, macOS, and Windows Server) and access to the AWS API<sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup>.</p><p>You can use Session Manager in two ways:</p><ol><li>Connect to your EC2 instance <a href="/goodbye-ssh-use-aws-session-manager-instead/">directly from the AWS Console</a> and get bash or PowerShell access.</li><li><a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-sessions-start.html#sessions-start-port-forwarding" target="_blank" rel="noopener">Forward any port</a> (e.g., 3389 for RDP) from the EC2 instance to your local machine and connect to the local port. To connect to your Windows instance, forward the remote port <code>3389</code> to a local port such as <code>13389</code> and point your local RDP client to <code>localhost:13389</code>. No security group changes are required to make it work! The EC2 instance needs only outbound access to the AWS API.</li></ol><h2 id="Back-up-your-data-with-AWS-Backup"><a href="#Back-up-your-data-with-AWS-Backup" class="headerlink" title="Back up your data with AWS Backup"></a>Back up your data with AWS Backup</h2><p>There are many reasons why you want to back up the disk of your virtual machine. Typical reasons are human error (configuration mistakes, accidental deletion of files) or hardware failure. <a href="https://aws.amazon.com/backup/" target="_blank" rel="noopener">AWS Backup</a> takes care of creating backups on a schedule, removing them after a configurable period, and restoring them.</p><blockquote><p><strong>Warning</strong>: AWS Backup supports EBS volumes, not instance store volumes attached to EC2 instances. AWS Backup creates “crash consistent” backups for Linux and macOS (don’t back up a database this way!) Windows backups are “application consistent” by using <a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/windows-backups.html" target="_blank" rel="noopener">VSS</a>.</p></blockquote><p><a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/create-a-scheduled-backup.html" target="_blank" rel="noopener">Configure a Backup Plan</a> or learn more by reading our <a href="/review-aws-backup/">detailed AWS Backup Review</a>!</p><h2 id="Install-patches-on-a-schedule-with-SSM-Patch-Manager"><a href="#Install-patches-on-a-schedule-with-SSM-Patch-Manager" class="headerlink" title="Install patches on a schedule with SSM Patch Manager"></a>Install patches on a schedule with SSM Patch Manager</h2><p><a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-patch.html" target="_blank" rel="noopener">AWS Systems Manager Patch Manager</a> applies patches during a configurable maintenance window (e.g., Sunday morning) and tracks and displays the installed patches for you. <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/create-patching-configuration.html" target="_blank" rel="noopener">Configure a maintenance window and create a patching configuration</a>.</p><p>SSM Patch Manager works with the following operating systems: Amazon Linux, Amazon Linux 2, CentOS, Debian Server, macOS, Oracle Linux, RHEL, SLES, Ubuntu Server, and Windows Server.</p><h2 id="Configure-the-CloudWatch-agent-to-ship-logs-disk-and-memory-metrics-to-CloudWatch"><a href="#Configure-the-CloudWatch-agent-to-ship-logs-disk-and-memory-metrics-to-CloudWatch" class="headerlink" title="Configure the CloudWatch agent to ship logs, disk, and memory metrics to CloudWatch"></a>Configure the CloudWatch agent to ship logs, disk, and memory metrics to CloudWatch</h2><p>By default, the CPU utilization of your EC2 instance is reported to CloudWatch. Unfortunately, memory and disk utilization metrics are missing. I recommend streaming logs to a central system to make them searchable and securely archive them. AWS provides the <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html" target="_blank" rel="noopener">CloudWatch agent</a> to solve both tasks: metric and log collection. The following figure shows how metrics are reported.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/08/cloudwatch-agent-disk-usage@730w.webp 730w, /images/2021/08/cloudwatch-agent-disk-usage@730w2x.webp 1460w, /images/2021/08/cloudwatch-agent-disk-usage@610w.webp 610w, /images/2021/08/cloudwatch-agent-disk-usage@610w2x.webp 1220w, /images/2021/08/cloudwatch-agent-disk-usage@450w.webp 450w, /images/2021/08/cloudwatch-agent-disk-usage@450w2x.webp 900w, /images/2021/08/cloudwatch-agent-disk-usage@330w.webp 330w, /images/2021/08/cloudwatch-agent-disk-usage@330w2x.webp 660w, /images/2021/08/cloudwatch-agent-disk-usage@545w.webp 545w, /images/2021/08/cloudwatch-agent-disk-usage@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/08/cloudwatch-agent-disk-usage@730w.png 730w, /images/2021/08/cloudwatch-agent-disk-usage@730w2x.png 1460w, /images/2021/08/cloudwatch-agent-disk-usage@610w.png 610w, /images/2021/08/cloudwatch-agent-disk-usage@610w2x.png 1220w, /images/2021/08/cloudwatch-agent-disk-usage@450w.png 450w, /images/2021/08/cloudwatch-agent-disk-usage@450w2x.png 900w, /images/2021/08/cloudwatch-agent-disk-usage@330w.png 330w, /images/2021/08/cloudwatch-agent-disk-usage@330w2x.png 660w, /images/2021/08/cloudwatch-agent-disk-usage@545w.png 545w, /images/2021/08/cloudwatch-agent-disk-usage@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/08/cloudwatch-agent-disk-usage.png" alt="CloudWatch agent reports custom metrics to CloudWatch" title="CloudWatch agent reports custom metrics to CloudWatch"></picture></p><p><a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/installing-cloudwatch-agent-commandline.html" target="_blank" rel="noopener">Install and configure the CloudWatch agent now</a>!</p><p>CloudWatch agent supports Amazon Linux, Amazon Linux 2, Ubuntu Server, CentOS, RHEL, Debian, SLES, Oracle Linux, macOS, Windows Server 2019, 2016, and 2012.</p><h2 id="Add-CloudWatch-alarms-for-alerting-and-instance-recovery"><a href="#Add-CloudWatch-alarms-for-alerting-and-instance-recovery" class="headerlink" title="Add CloudWatch alarms for alerting and instance recovery"></a>Add CloudWatch alarms for alerting and instance recovery</h2><p>After sending memory and disk usage metrics to CloudWatch, it’s time to monitor all the metrics to alert you when the virtual machine is running at capacity. Monitor the following constrained resources with CloudWatch alarms:</p><ul><li>CPU utilization</li><li>Memory usage</li><li><a href="https://marbot.io/blog/monitoring-ec2-disk-usage.html" target="_blank" rel="noopener">Disk usage</a></li><li><a href="https://marbot.io/blog/monitoring-ec2-network-utilization.html" target="_blank" rel="noopener">Network utilization</a></li></ul><p>One open question is what happens if the utilization reaches the predefined threshold? Out of the box, you can send an email. With <a href="https://aws.amazon.com/chatbot/" target="_blank" rel="noopener">AWS Chatbot</a>, you can send a message in Slack. Or you use our <a href="https://marbot.io/" target="_blank" rel="noopener">chatbot marbot to configure monitoring, receive alerts, and solve incidents</a>.</p><p>On top of that, a CloudWatch alarm monitors the <code>StatusCheckFailed_System</code> metric to trigger a <a href="/high-availability-is-a-no-brainer-ec2-auto-recovery/">recovery action</a>. Instance recovery moves the virtual machine to another hypervisor in case of hardware issues without changing the metadata (id, IP address, tags, …) of the EC2 instance.</p><h2 id="Disable-IMDSv1"><a href="#Disable-IMDSv1" class="headerlink" title="Disable IMDSv1"></a>Disable IMDSv1</h2><p>IMDSv2 can improve EC2 security by making it harder to accidentally making the instance metadata service (IMDS) accessible from the public. IMDS can be used to obtain IAM credentials for the attached IAM role. AWS Foundational Security Best Practices recommends that EC2 instances use IMDSv2 (see <a href="https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-ec2-8" target="_blank" rel="noopener">control EC2.8</a>). If you are interested in the details, read my <a href="https://cloudonaut.io/transition-to-imdsv2-on-ec2/">Transition to IMDSv2 on EC2 - Introduction, Preparation, Pitfalls</a> post. Otherwise, follow these steps to disable IMDSv1 and transition to IMDSv2.</p><p>If you forget to <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-options.html#configuring-IMDS-new-instances" target="_blank" rel="noopener">disable IMDSv1 during launch</a>, you can still <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-options.html#configuring-IMDS-existing-instances" target="_blank" rel="noopener">disable IMDSv1 using the AWS CLI</a>. The fastest way to launch the AWS CLI today is by using <a href="https://docs.aws.amazon.com/cloudshell/latest/userguide/getting-started.html#launch-region-shell" target="_blank" rel="noopener">AWS CloudShell in the AWS Console</a>.</p><h2 id="Schedule-regular-software-scans-to-detect-vulnerabilities-with-Inspector"><a href="#Schedule-regular-software-scans-to-detect-vulnerabilities-with-Inspector" class="headerlink" title="Schedule regular software scans to detect vulnerabilities with Inspector"></a>Schedule regular software scans to detect vulnerabilities with Inspector</h2><p><a href="https://aws.amazon.com/inspector/" target="_blank" rel="noopener">Amazon Inspector</a> is the service of choice to automatically check your EC2 instances for:</p><ul><li>Unintended network accessibility</li><li>Vulnerabilities</li><li>Deviations from best practices (such as CIS Amazon Linux 2 Benchmark).</li></ul><p>Inspector relies on an agent to collect the needed information on your EC2 instances. Findings are presented in a table and include remediation hints, as the following screenshot shows.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/08/amazon-inspector-findings@730w.webp 730w, /images/2021/08/amazon-inspector-findings@730w2x.webp 1460w, /images/2021/08/amazon-inspector-findings@610w.webp 610w, /images/2021/08/amazon-inspector-findings@610w2x.webp 1220w, /images/2021/08/amazon-inspector-findings@450w.webp 450w, /images/2021/08/amazon-inspector-findings@450w2x.webp 900w, /images/2021/08/amazon-inspector-findings@330w.webp 330w, /images/2021/08/amazon-inspector-findings@330w2x.webp 660w, /images/2021/08/amazon-inspector-findings@545w.webp 545w, /images/2021/08/amazon-inspector-findings@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/08/amazon-inspector-findings@730w.png 730w, /images/2021/08/amazon-inspector-findings@730w2x.png 1460w, /images/2021/08/amazon-inspector-findings@610w.png 610w, /images/2021/08/amazon-inspector-findings@610w2x.png 1220w, /images/2021/08/amazon-inspector-findings@450w.png 450w, /images/2021/08/amazon-inspector-findings@450w2x.png 900w, /images/2021/08/amazon-inspector-findings@330w.png 330w, /images/2021/08/amazon-inspector-findings@330w2x.png 660w, /images/2021/08/amazon-inspector-findings@545w.png 545w, /images/2021/08/amazon-inspector-findings@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/08/amazon-inspector-findings.png" alt="Amazon Inspector findings" title="Amazon Inspector findings"></picture></p><p><a href="https://marbot.io/blog/ec2-vulnerability-and-compliance-violation-alerting.html" target="_blank" rel="noopener">Learn more about Inspector and how to install the agent</a>.</p><h2 id="Automation-is-king"><a href="#Automation-is-king" class="headerlink" title="Automation is king"></a>Automation is king</h2><p>Implementing the best practices after launching an EC2 instance is time-consuming and error-prone. If you are interested in automating the work, check out our <a href="https://templates.cloudonaut.io/en/stable/ec2/" target="_blank" rel="noopener">CloudFormation templates</a>. They include all best practices except AWS Inspector. You can use a CloudFormation template to spin up hundreds of EC2 instances without any manual work. You can find the source code on <a href="https://github.com/widdix/aws-cf-templates/blob/master/ec2/al2-mutable-private.yaml" target="_blank" rel="noopener">GitHub</a>.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Launching an EC2 instance takes minutes. With the correct settings, you can securely run your virtual machines without much ongoing effort. Keep in mind that some settings can not be changed after instance launch and are therefore not in the scope of this blog post. E.g., EBS volume encryption or the VPC subnet. I have also not included the obvious candidates, such as security group rules and the IAM policies of the attached IAM role.</p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. Installation instructions can be found here https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. In private VPCs, you need to add VPC endpoints for <code>ssm</code>, <code>ec2messages</code>, and <code>ssmmessages</code>: https://aws.amazon.com/premiumsupport/knowledge-center/ec2-systems-manager-vpc-endpoints/ <a href="#fnref:2" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>The AWS Security Journey (2021)</title>
      <link>https://cloudonaut.io/the-aws-security-journey-2021/</link>
      <description>
        <![CDATA[<p>A lot has happened in the area of security at AWS over the years. By now, AWS has released an exhaustive range of security services and t]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <guid isPermaLink="true">https://cloudonaut.io/the-aws-security-journey-2021/</guid>
      <pubDate>Tue, 20 Jul 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>A lot has happened in the area of security at AWS over the years. By now, AWS has released an exhaustive range of security services and the role of the security officer has changed significantly. This article looks back and forecasts where the journey will go next.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/07/road@730w.webp 730w, /images/2021/07/road@730w2x.webp 1460w, /images/2021/07/road@610w.webp 610w, /images/2021/07/road@610w2x.webp 1220w, /images/2021/07/road@450w.webp 450w, /images/2021/07/road@450w2x.webp 900w, /images/2021/07/road@330w.webp 330w, /images/2021/07/road@330w2x.webp 660w, /images/2021/07/road@545w.webp 545w, /images/2021/07/road@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/07/road@730w.jpg 730w, /images/2021/07/road@730w2x.jpg 1460w, /images/2021/07/road@610w.jpg 610w, /images/2021/07/road@610w2x.jpg 1220w, /images/2021/07/road@450w.jpg 450w, /images/2021/07/road@450w2x.jpg 900w, /images/2021/07/road@330w.jpg 330w, /images/2021/07/road@330w2x.jpg 660w, /images/2021/07/road@545w.jpg 545w, /images/2021/07/road@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/07/road.jpg" alt="Auto Scaling" title="Auto Scaling"></picture></p><h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL;DR"></a>TL;DR</h2><ul><li>Cloud providers have shaped the philosophy of security and done their homework.</li><li>New services to secure and new security services and their features pose a know-how challenge.</li><li>Security Engineers as specialists will outnumber Security Architect generalists.</li><li>Cloud customers organizations will be increasingly challenged to keep risk at an acceptable level.</li></ul><h2 id="The-beginning-–-Security-Philosophers"><a href="#The-beginning-–-Security-Philosophers" class="headerlink" title="The beginning – Security Philosophers"></a>The beginning – Security Philosophers</h2><p>What is your story? How did you come to AWS? For me, as a security professional, the cloud was an obvious field of work. However, initially, it was not clear that AWS would be the market leader and for such a long time.</p><p>Cloud computing was proposed, but its adoption was not an immediate success. There were security concerns about confidentiality, legal issues about data location, and data ownership worries.<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup></p><p>AWS had an easy solution for the last one: Customers own their data, can always transfer their data, and must do so before they terminate the contract. AWS will not keep the data afterward.</p><p>The legal discussion – at least in Europe – dragged on for several years: USA PATRIOT Act, EU Privacy Directive, Safe Harbor, CLOUD ACT, GDPR, … In the meantime, the practitioners were implementing, with major or minor concessions to the perceived legal situation, and they were rarely called to order. </p><p>AWS did their homework on security and passed audits, and achieved certifications. With the Security White Paper, AWS published the <a href="https://aws.amazon.com/compliance/shared-responsibility-model/" target="_blank" rel="noopener">Shared Responsibility Model</a> and informed their customers that they had work to do, too. </p><table class="table table-striped table-responsive"><thead><tr><th>Date</th><th>Innovation</th><th>Importance</th></tr></thead><tbody><tr><td>2006</td><td>First service S3 in region us-east-1</td><td>First AWS service, followed by SQS, EC2</td></tr><tr><td>2007</td><td>First EU-region <code>eu-west-1</code></td><td>Improved latency and compliance with privacy regulation in Europe</td></tr><tr><td>2009</td><td>VPC (Virtual Private Cloud)</td><td>Network isolation with subnets, routing, access control lists</td></tr><tr><td>2009</td><td>SAS70 Type II Audit<sup><a href="#fn:3" id="fnref:3" class="footnote-ref">3</a></sup></td><td>3rd party audit (today: ISAE 3402, SOC 1)</td></tr><tr><td>2010</td><td>ISO 27001 certification<sup><a href="#fn:4" id="fnref:4" class="footnote-ref">4</a></sup></td><td>3rd party audit for information security</td></tr><tr><td>2011</td><td>AWS Security White Paper</td><td>Shared Responsibility Model</td></tr></tbody></table><p>Security work was like this:</p><ol><li>Customer voices a security concern, what could go wrong.</li><li>Reword it as a question or demand to the cloud provider.</li><li>Locate information about the provider’s security in an official white paper, an answered questionnaire, or from a 3rd party audit.</li><li>Evaluate whether the answer covers the initial concern or whether parts of the issue remain in the customers’ responsibility.</li></ol><h2 id="The-build-up-–-Security-Architects"><a href="#The-build-up-–-Security-Architects" class="headerlink" title="The build-up – Security Architects"></a>The build-up – Security Architects</h2><p>AWS released CloudTrail and KMS and was directing a large part of their information campaign to security: AWS Summits, Blogs, Meetups always mentioned: “security is our #1 priority”. With the availability of KMS, encryption-at-rest was more in the hands of customers, and later, Werner Vogels recommended, “just encrypt everything”.</p><p>AWS was leading the security initiative. The regional rollouts in many countries acknowledged their customer’s legal worries about data location and allowed them to save their data in their own country. Now all concerns could be handled, and implementation of “Cloud First” initiatives could start. For me, with <code>eu-central-1</code>, I realized that AWS would become the most important player, not only in Germany but worldwide.</p><table class="table table-striped table-responsive"><thead><tr><th>Date</th><th>Innovation</th><th>Importance</th></tr></thead><tbody><tr><td>2013</td><td>AWS Certified Solutions Architect</td><td>Defined expectations for professional work</td></tr><tr><td>2013</td><td>CloudTrail</td><td>Universal Logging</td></tr><tr><td>2014</td><td>Key Management Service</td><td>Customer control over encryption</td></tr><tr><td>2015</td><td>Policies in IAM</td><td>Fine-grained access control</td></tr></tbody></table><p>By then, the services were complete with security features, and one could safely assume that there would be some solution for every security problem. The security design would proceed as follows:</p><ol><li>Identify the AWS service, which addressed the problem.<br>A nod to Michael, who helped to structure the topics with his <a href="https://cloudonaut.io/aws-security-primer/">AWS Security Primer mindmap</a>.</li><li>Use the service name to look up security information in the AWS reference.</li><li>Design a solution and draw a diagram.</li><li>Code and find out about the limitations of the service.</li></ol><p>AWS provided some structure, how this work should be done, and ultimately it was codified in the professional education program for the “AWS Certified Solutions Architect”, which also includes many security topics. Note that there was no prep book or online course initially, and taking the exam was a step into the unknown.</p><p>In 2018 AWS created the <a href="https://aws.amazon.com/de/blogs/security/announcing-the-new-aws-certified-security-specialty-exam/" target="_blank" rel="noopener">Security Specialty</a>, which was going deeper into implementation details, but in scope initially stayed close to the Solution Architects scope. The exam is updated over time, and as I recertified in 2021, it occurred to me how much the field has changed: More services to secure and more security services, more bits to know.  </p><h2 id="The-overload-–-Security-Engineers"><a href="#The-overload-–-Security-Engineers" class="headerlink" title="The overload – Security Engineers"></a>The overload – Security Engineers</h2><p>AWS announced that <a href="https://aws.amazon.com/de/blogs/security/over-150-aws-services-now-have-security-chapter/" target="_blank" rel="noopener">“Over 150 AWS services now have a security chapter”</a>, which is quite an impressive number. Depending on how you count (the <a href="https://medium.com/cloudpegboard/how-many-aws-services-are-there-51dda44fa946" target="_blank" rel="noopener">CloudPegBoard lists 323 AWS services</a>), most common services seem to be covered, and everyone should use this ready-made security resource. If a security chapter does not cover a service, a security analysis will have to be performed, as was previously the norm.</p><h3 id="AWS-native-security-services"><a href="#AWS-native-security-services" class="headerlink" title="AWS-native security services"></a>AWS-native security services</h3><p>AWS rolls out ever more security services, and new features are continuously added to the existing services, as summarized in the table below. First and foremost the table shows that the required know-how rises in two dimensions: Year after year, the table gets longer, and the numbers of the existing lines get higher. </p><p>Is it realistic that any AWS security expert will have sufficiently deep knowledge about every service’s internal operations and configuration details? In security, the margin of error is relatively small.</p><table class="table table-striped table-responsive"><thead><tr><th>Release</th><th>AWS Security Service</th><th>Number of announcements<sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup></th></tr></thead><tbody><tr><td>09/2010</td><td>AWS IAM</td><td>82+</td></tr><tr><td>07/2014</td><td>Amazon Cognito</td><td>49</td></tr><tr><td>10/2014</td><td>AWS Directory Service</td><td>60+</td></tr><tr><td>11/2014</td><td>AWS Key Management Service</td><td>30</td></tr><tr><td>10/2015</td><td>AWS WAF (Web Application Firewall)</td><td>52+</td></tr><tr><td>10/2015</td><td>Amazon Inspector</td><td>36</td></tr><tr><td>01/2016</td><td>AWS Certificate Manager</td><td>39</td></tr><tr><td>11/2016</td><td>AWS Organizations</td><td>31</td></tr><tr><td>12/2016</td><td>AWS Shield</td><td>22</td></tr><tr><td>01/2017</td><td>Amazon Cloud Directory</td><td>13</td></tr><tr><td>08/2017</td><td>AWS CloudHSM (new)</td><td>15</td></tr><tr><td>08/2017</td><td>Amazon Macie</td><td>10</td></tr><tr><td>11/2017</td><td>Amazon GuardDuty</td><td>29</td></tr><tr><td>12/2017</td><td>AWS Single Sign-On</td><td>28</td></tr><tr><td>04/2018</td><td>AWS Firewall Manager</td><td>22</td></tr><tr><td>04/2018</td><td>AWS Secrets Manager</td><td>40</td></tr><tr><td>11/2018</td><td>AWS Security Hub</td><td>44</td></tr><tr><td>12/2018</td><td>AWS Resource Access Manager</td><td>10+</td></tr><tr><td>11/2019</td><td>AWS Artifact</td><td>4</td></tr><tr><td>12/2019</td><td>Amazon Detective</td><td>9+</td></tr><tr><td>11/2020</td><td>AWS Network Firewall</td><td>5</td></tr><tr><td>12/2020</td><td>AWS Audit Manager</td><td>4+</td></tr><tr><td></td><td><strong>Total announcements in &quot;Security&quot;</strong></td><td><strong>634</strong></td></tr></tbody></table><h3 id="Specialist-vs-generalist"><a href="#Specialist-vs-generalist" class="headerlink" title="Specialist vs. generalist"></a>Specialist vs. generalist</h3><p>The larger amount of knowledge causes a split between the Security Architect as a generalist and the Security Engineer as a specialist. The number of engineers will continue to grow with the number of applications that must be maintained in AWS and the increasing need-to-know-how in AWS services. Whereas Security Architects will continue to work at the generalist level, similar to Solution Architects, usually in implementation projects, their number will not grow as much.</p><p>Both can work together to generate results faster by using the increasing amount of material available from AWS and other sources, as follows: </p><ol><li>Architect finds an implementation blueprint or CloudFormation template and lists all AWS services in the diagram and hands it to the Engineer.</li><li>Engineer collects all recommendations from the security chapters of each service and checks the implementation manually or in the CloudFormation template.</li><li>Both lookup checks or metrics in Security Hub or Guard Duty or design their own to configure alerts from the checks and metrics.</li></ol><p>This approach is traceable, measurable, efficient, and improves security. It also acknowledges that nothing is perfect and includes a response to incidents. However, the remaining risk is not transparent, primarily if some measures have not been implemented.</p><h2 id="The-future"><a href="#The-future" class="headerlink" title="The future"></a>The future</h2><p>As more cloud applications go into operation, they encounter well-known management problems of security in organizations: Immediate costs get more attention than future damages, and prevention is not valued if nothing has happened in the past. In effect, a business will do less than was recommended and accept more risk than would be optimal. </p><p>It is foreseeable that security incidents will happen more often, and damages will be more significant. Unfortunately, currently, there is no mechanism for the community to learn from substantial breaches like Capital One.<sup><a href="#fn:5" id="fnref:5" class="footnote-ref">5</a></sup> We would need 3rd party investigations of essential incidents, which make analysis and improvements public so that everyone can learn and improve.</p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. 2012 Cloud Computing Market Maturity Study Results, CSA and ISACA <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. Based on &quot;AWS What's new&quot; on 22 June 2021, ordered by date of the first announcement; +marks services, for which the first announcement is listed not in the category, so the total number of announcements will probably be larger. <a href="#fnref:2" class="footnote-backref">↩</a></p></li><li id="fn:3"><p>3. <a href="https://aws.amazon.com/de/about-aws/whats-new/2009/11/11/aws-completes-sas70-type-ii-audit/">AWS Completes SAS70 Type II Audit</a> <a href="#fnref:3" class="footnote-backref">↩</a></p></li><li id="fn:4"><p>4. <a href="https://aws.amazon.com/de/blogs/aws/aws-receives-iso-27001-certification/">AWS Receives ISO 27001 Certification</a> <a href="#fnref:4" class="footnote-backref">↩</a></p></li><li id="fn:5"><p>5. <a href="https://edition.cnn.com/2019/07/29/business/capital-one-data-breach/index.html">Capital One Data Breach</a> <a href="#fnref:5" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to Replicate Your Data with DynamoDB Global Tables</title>
      <link>https://cloudonaut.io/how-to-replicate-your-data-with-dynamodb-global-tables/</link>
      <description>
        <![CDATA[<p>In my last post about <a href="/multi-region-aws-architectures/">Multi-Region AWS Architectures</a>, I discussed how you could reduce end]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/dynamodb/">dynamodb</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-replicate-your-data-with-dynamodb-global-tables/</guid>
      <pubDate>Wed, 14 Jul 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>In my last post about <a href="/multi-region-aws-architectures/">Multi-Region AWS Architectures</a>, I discussed how you could reduce end-user latency and increase availability by running your application in multiple regions. I compared AWS services that help you to run your application in various regions at the same time. In this post, we’ll focus on one data store that shines in multi-region architectures: Amazon DynamoDB.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/07/multi-region2@730w.webp 730w, /images/2021/07/multi-region2@730w2x.webp 1460w, /images/2021/07/multi-region2@610w.webp 610w, /images/2021/07/multi-region2@610w2x.webp 1220w, /images/2021/07/multi-region2@450w.webp 450w, /images/2021/07/multi-region2@450w2x.webp 900w, /images/2021/07/multi-region2@330w.webp 330w, /images/2021/07/multi-region2@330w2x.webp 660w, /images/2021/07/multi-region2@545w.webp 545w, /images/2021/07/multi-region2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/07/multi-region2@730w.jpg 730w, /images/2021/07/multi-region2@730w2x.jpg 1460w, /images/2021/07/multi-region2@610w.jpg 610w, /images/2021/07/multi-region2@610w2x.jpg 1220w, /images/2021/07/multi-region2@450w.jpg 450w, /images/2021/07/multi-region2@450w2x.jpg 900w, /images/2021/07/multi-region2@330w.jpg 330w, /images/2021/07/multi-region2@330w2x.jpg 660w, /images/2021/07/multi-region2@545w.jpg 545w, /images/2021/07/multi-region2@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/07/multi-region2.jpg" alt="Multi-Region" title="Multi-Region"></picture></p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><h2 id="Amazon-DynamoDB"><a href="#Amazon-DynamoDB" class="headerlink" title="Amazon DynamoDB"></a>Amazon DynamoDB</h2><p><a href="https://aws.amazon.com/dynamodb/" target="_blank" rel="noopener">Amazon DynamoDB</a> is a NoSQL database service that supports key-value and document data structures. DynamoDB is a fully managed service that you can use only on the AWS cloud. You can run it in a <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html#HowItWorks.OnDemand" target="_blank" rel="noopener">Serverless fashion</a> where you only pay for what you use while DynamoDB adapts to the load automatically.</p><h3 id="Using-DynamoDB"><a href="#Using-DynamoDB" class="headerlink" title="Using DynamoDB"></a>Using DynamoDB</h3><p>DynamoDB is a key-value store that organizes your data in tables. Each table contains items (values) that are identified by keys. A table can also maintain secondary indexes for data lookup besides the primary key. You will now have a look at these basic building blocks of DynamoDB.</p><h4 id="Table-item-attribute"><a href="#Table-item-attribute" class="headerlink" title="Table, item, attribute"></a>Table, item, attribute</h4><p>A DynamoDB table has a name and organizes a collection of items. An item is a collection of attributes. An attribute is a name-value pair. The attribute value can be scalar (number, string, binary, boolean), multi-valued (number set, string set, binary set), or JSON document (object, array). Items in a table are not required to have the same attributes; there is no enforced schema.</p><p>You can create a table with the Management Console, CloudFormation, SDKs, or the CLI. The following example shows how you create a table with the CLI:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws dynamodb create-table --table-name entity \</span><br><span class="line">  --attribute-definitions AttributeName=<span class="built_in">id</span>,AttributeType=S \</span><br><span class="line">  --key-schema AttributeName=<span class="built_in">id</span>,KeyType=HASH \</span><br><span class="line">  --billing-mode PAY_PER_REQUEST</span><br></pre></td></tr></table></figure><p>In line 1, you define the name of the table. Line 2 defines the attributes that are used in the key schema in line 3. Line 4 defines that you want to use DynamoDB in the serverless flavor.</p><h3 id="Primary-keys"><a href="#Primary-keys" class="headerlink" title="Primary keys"></a>Primary keys</h3><p>A primary key is unique within a table and identifies an item. You need the primary key to lookup an item. The primary key is either a partition key or a partition and a sort key.</p><h4 id="Partition-keys"><a href="#Partition-keys" class="headerlink" title="Partition keys"></a>Partition keys</h4><p>A partition key (formerly known as hash key) uses a single attribute of an item to create a hash index. If you want to look up an item based on its partition key, you need to know the exact partition key. A user table could use the user’s email as a primary key. A user then can be retrieved if you know the partition key (email, in this case).</p><h4 id="Partition-and-sort-keys"><a href="#Partition-and-sort-keys" class="headerlink" title="Partition and sort keys"></a>Partition and sort keys</h4><p>A partition and sort key (formerly known as hash and range key) use two attributes of an item to create a more powerful primary key. The first attribute is the partition part of the key, and the second part is the sort key. To look up an item, you need to know the exact partition part of the key, but you don’t need to know the sort part. Items are sorted by sort key within a partition key. This allows you to query the sort part of the key from a particular starting point. A message table can use a partition and sort as its primary key; the partition is the email of the user, and the sort is a timestamp. You can now look up all messages of a user that are newer than a specific timestamp.</p><h2 id="Multi-Region"><a href="#Multi-Region" class="headerlink" title="Multi-Region"></a>Multi-Region</h2><p>DynamoDB is one of the few data stores on AWS that can run in multiple regions and allows writes and reads to all the regions in parallel. For example, you can replicate a table created in us-east-1 to eu-west-1. If you write to us-east-1, the data item will show up in eu-west-1 as well. You can also write to eu-west-1, and the data item appears in us-east-1. Data is usually replicated across regions in under a second, which is way faster than any human can travel from one continent to another.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/07/dynamodb-replication@730w.webp 730w, /images/2021/07/dynamodb-replication@730w2x.webp 1460w, /images/2021/07/dynamodb-replication@610w.webp 610w, /images/2021/07/dynamodb-replication@610w2x.webp 1220w, /images/2021/07/dynamodb-replication@450w.webp 450w, /images/2021/07/dynamodb-replication@450w2x.webp 900w, /images/2021/07/dynamodb-replication@330w.webp 330w, /images/2021/07/dynamodb-replication@330w2x.webp 660w, /images/2021/07/dynamodb-replication@545w.webp 545w, /images/2021/07/dynamodb-replication@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/07/dynamodb-replication@730w.png 730w, /images/2021/07/dynamodb-replication@730w2x.png 1460w, /images/2021/07/dynamodb-replication@610w.png 610w, /images/2021/07/dynamodb-replication@610w2x.png 1220w, /images/2021/07/dynamodb-replication@450w.png 450w, /images/2021/07/dynamodb-replication@450w2x.png 900w, /images/2021/07/dynamodb-replication@330w.png 330w, /images/2021/07/dynamodb-replication@330w2x.png 660w, /images/2021/07/dynamodb-replication@545w.png 545w, /images/2021/07/dynamodb-replication@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/07/dynamodb-replication.png" alt="DynamoDB Replication" title="DynamoDB Replication"></picture></p><p>There is one thing to keep in mind when you modify the same data item in two regions simultaneously. If the same data item is modified in two or more regions at the same time, the last write wins. The previous write is discarded.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/07/dynamodb-last-write-wins@730w.webp 730w, /images/2021/07/dynamodb-last-write-wins@730w2x.webp 1460w, /images/2021/07/dynamodb-last-write-wins@610w.webp 610w, /images/2021/07/dynamodb-last-write-wins@610w2x.webp 1220w, /images/2021/07/dynamodb-last-write-wins@450w.webp 450w, /images/2021/07/dynamodb-last-write-wins@450w2x.webp 900w, /images/2021/07/dynamodb-last-write-wins@330w.webp 330w, /images/2021/07/dynamodb-last-write-wins@330w2x.webp 660w, /images/2021/07/dynamodb-last-write-wins@545w.webp 545w, /images/2021/07/dynamodb-last-write-wins@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/07/dynamodb-last-write-wins@730w.png 730w, /images/2021/07/dynamodb-last-write-wins@730w2x.png 1460w, /images/2021/07/dynamodb-last-write-wins@610w.png 610w, /images/2021/07/dynamodb-last-write-wins@610w2x.png 1220w, /images/2021/07/dynamodb-last-write-wins@450w.png 450w, /images/2021/07/dynamodb-last-write-wins@450w2x.png 900w, /images/2021/07/dynamodb-last-write-wins@330w.png 330w, /images/2021/07/dynamodb-last-write-wins@330w2x.png 660w, /images/2021/07/dynamodb-last-write-wins@545w.png 545w, /images/2021/07/dynamodb-last-write-wins@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/07/dynamodb-last-write-wins.png" alt="Last write wins" title="Last write wins"></picture></p><p>Unfortunately, DynamoDB transactions do not span multiple regions. A transaction only runs in a single region. You can modify multiple items in a single transaction. Therefore, your life gets much easier if you can ensure that writes to a data item happen in a single region only at a time.</p><p>Imagine a user editing data. This user is always routed to the same region. Therefore, the user will edit data in a single region only. Other users from other continents still see the data. And if the user travels from one continent to another, she can continue to edit data without issues.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>DynamoDB is a NoSQL database that supports key-value and document data structures. Data items are grouped in tables. Each item consists of attributes. One or two attributes form the primary key of the item.</p><p>DynamoDB allows you to replicate tables across multiple regions. You can read and write to any replica across the globe at any time. If the same item is modified in two regions, the last write wins.</p><p>I recommend using DynamoDB in multi-region architectures whenever possible. You might need to learn a new technology, but your learning efforts will pay off quickly. With DynamoDB, you use the only multi-region-write data store on AWS that is fully managed and pay per use.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Multi-Region AWS Architectures</title>
      <link>https://cloudonaut.io/multi-region-aws-architectures/</link>
      <description>
        <![CDATA[<p>Running an application in multiple availability zones (data centers) in a single region is a best practice when architecting on AWS. Inte]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <guid isPermaLink="true">https://cloudonaut.io/multi-region-aws-architectures/</guid>
      <pubDate>Mon, 05 Jul 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Running an application in multiple availability zones (data centers) in a single region is a best practice when architecting on AWS. Interested in learning more about multi-AZ? Take a look at our previous post <a href="/aws-architecture-checklist/">here</a>.</p><p>Depending on your availability or latency requirements, you might need to deploy your application among multiple regions. For example, use US East (Ohio), US West (Oregon), and EU West (Ireland) to operate your application close to your users. In this blog post, I will demonstrate how Multi-Region architectures look. We will also discuss the main challenges: replicating state and ingress routing.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/07/multi-region@730w.webp 730w, /images/2021/07/multi-region@730w2x.webp 1460w, /images/2021/07/multi-region@610w.webp 610w, /images/2021/07/multi-region@610w2x.webp 1220w, /images/2021/07/multi-region@450w.webp 450w, /images/2021/07/multi-region@450w2x.webp 900w, /images/2021/07/multi-region@330w.webp 330w, /images/2021/07/multi-region@330w2x.webp 660w, /images/2021/07/multi-region@545w.webp 545w, /images/2021/07/multi-region@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/07/multi-region@730w.jpg 730w, /images/2021/07/multi-region@730w2x.jpg 1460w, /images/2021/07/multi-region@610w.jpg 610w, /images/2021/07/multi-region@610w2x.jpg 1220w, /images/2021/07/multi-region@450w.jpg 450w, /images/2021/07/multi-region@450w2x.jpg 900w, /images/2021/07/multi-region@330w.jpg 330w, /images/2021/07/multi-region@330w2x.jpg 660w, /images/2021/07/multi-region@545w.jpg 545w, /images/2021/07/multi-region@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/07/multi-region.jpg" alt="Multi-Region" title="Multi-Region"></picture></p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><h2 id="Architecture-Overview"><a href="#Architecture-Overview" class="headerlink" title="Architecture Overview"></a>Architecture Overview</h2><p>Running an application in multiple regions requires us to set up the infrastructure for our application in each region. For a typical web application, the infrastructure includes:</p><ul><li>load balancer</li><li>compute layer (EC2, ECS, EKS, Lambda)</li><li>datastore</li></ul><p>You create the infrastructure as before in each region.</p><blockquote><p><strong>Pro Tip</strong>: If you invested in Infrastructure as Code, you can easily recreate your environment in another region with CloudFormation or Terraform.  </p></blockquote><p>The following figure shows the overall architecture.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/07/multi-region-architecture@730w.webp 730w, /images/2021/07/multi-region-architecture@730w2x.webp 1460w, /images/2021/07/multi-region-architecture@610w.webp 610w, /images/2021/07/multi-region-architecture@610w2x.webp 1220w, /images/2021/07/multi-region-architecture@450w.webp 450w, /images/2021/07/multi-region-architecture@450w2x.webp 900w, /images/2021/07/multi-region-architecture@330w.webp 330w, /images/2021/07/multi-region-architecture@330w2x.webp 660w, /images/2021/07/multi-region-architecture@545w.webp 545w, /images/2021/07/multi-region-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/07/multi-region-architecture@730w.png 730w, /images/2021/07/multi-region-architecture@730w2x.png 1460w, /images/2021/07/multi-region-architecture@610w.png 610w, /images/2021/07/multi-region-architecture@610w2x.png 1220w, /images/2021/07/multi-region-architecture@450w.png 450w, /images/2021/07/multi-region-architecture@450w2x.png 900w, /images/2021/07/multi-region-architecture@330w.png 330w, /images/2021/07/multi-region-architecture@330w2x.png 660w, /images/2021/07/multi-region-architecture@545w.png 545w, /images/2021/07/multi-region-architecture@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/07/multi-region-architecture.png" alt="Architecture Overview" title="Architecture Overview"></picture></p><p>Two challenges need further investigation:</p><ul><li>How can you route your users to the closest available region?</li><li>How can you replicate the data between the data stores in the different regions?</li></ul><p>Let’s have a look.</p><h2 id="Ingress-Routing"><a href="#Ingress-Routing" class="headerlink" title="Ingress Routing"></a>Ingress Routing</h2><p>What is closest, and what is available? To calculate how close a user is to a region, either the latency or the geographical distance is used. The availability of a region is determined by health checks that constantly send requests to your application in each region.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/07/multi-region-ingress-routing@730w.webp 730w, /images/2021/07/multi-region-ingress-routing@730w2x.webp 1460w, /images/2021/07/multi-region-ingress-routing@610w.webp 610w, /images/2021/07/multi-region-ingress-routing@610w2x.webp 1220w, /images/2021/07/multi-region-ingress-routing@450w.webp 450w, /images/2021/07/multi-region-ingress-routing@450w2x.webp 900w, /images/2021/07/multi-region-ingress-routing@330w.webp 330w, /images/2021/07/multi-region-ingress-routing@330w2x.webp 660w, /images/2021/07/multi-region-ingress-routing@545w.webp 545w, /images/2021/07/multi-region-ingress-routing@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/07/multi-region-ingress-routing@730w.png 730w, /images/2021/07/multi-region-ingress-routing@730w2x.png 1460w, /images/2021/07/multi-region-ingress-routing@610w.png 610w, /images/2021/07/multi-region-ingress-routing@610w2x.png 1220w, /images/2021/07/multi-region-ingress-routing@450w.png 450w, /images/2021/07/multi-region-ingress-routing@450w2x.png 900w, /images/2021/07/multi-region-ingress-routing@330w.png 330w, /images/2021/07/multi-region-ingress-routing@330w2x.png 660w, /images/2021/07/multi-region-ingress-routing@545w.png 545w, /images/2021/07/multi-region-ingress-routing@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/07/multi-region-ingress-routing.png" alt="Ingress Routing" title="Ingress Routing"></picture></p><p>The following options are available on AWS:</p><ul><li>Route53</li><li>Global Accelerator</li></ul><p>The capabilities are similar, but the technology is different. Route53 offers DNS technology. Your users might not resolve names fast enough for a quick failover. Global Accelerator exposes two static Anycast IP addresses for you. <a href="/review-aws-global-accelerator-latency-multi-region-disaster-recovery/">Anycast means that multiple servers use the same IP address</a>. Routers decide where to go by selecting the shortest path. Additionally, Global Accelerator routes traffic into the AWS backbone as early as possible to reduce latency. Keep in mind that <a href="https://aws.amazon.com/route53/pricing/" target="_blank" rel="noopener">Route53 is cheaper</a> compared to <a href="https://aws.amazon.com/global-accelerator/pricing/" target="_blank" rel="noopener">Global Accelerator</a>.</p><h2 id="Replicating-State"><a href="#Replicating-State" class="headerlink" title="Replicating State"></a>Replicating State</h2><p>So far, a user is always routed to the closest available region. But what happens if a user boards a plane to travel between continents. Even worse, what happens if one region goes down. How can we ensure that user data is available in the right region? The straightforward solution is to replicate all your data to all your regions. Sounds easy? One thing to understand is that cross-region replication is asynchronous. Synchronous replication would be too slow because of the latency caused by the long distances. That’s why almost all data is replicated asynchronously. That works great until the same data item is updated in multiple regions. That’s why some solutions allow write-only in a single region while reads can happen across the globe. </p><p>The following AWS datastores come with support for cross-region replication:</p><ul><li>DynamoDB</li><li>RDS Aurora</li><li>RDS</li><li>S3</li><li>ElastiCache</li><li>DocumentDB</li></ul><p>The comparison table at the end of this section compares the different options. Before we look into the comparison, you have a closer look at S3 cross-region replication.</p><h3 id="S3-Bucket-Cross-Region-Replication-configuration"><a href="#S3-Bucket-Cross-Region-Replication-configuration" class="headerlink" title="S3 Bucket Cross-Region Replication configuration"></a>S3 Bucket Cross-Region Replication configuration</h3><p>Replicating S3 buckets is a little harder than it should be. You have to create a replication configuration between each bucket in both directions.<br>If you have two buckets in us-east-1 and us-west-2, you need to replicate:</p><ul><li><code>us-east-1</code> to <code>us-west-2</code></li><li><code>us-west-2</code> to <code>us-east-1</code></li></ul><p>This way, you can write to either us-east-1 or us-west-2 and see the object in the other bucket. If you want to have buckets in 3 regions, things get more complex. You need to replicate:</p><ul><li><code>us-east-1</code> to <code>us-west-2</code></li><li><code>us-east-1</code> to <code>eu-west-1</code></li><li><code>us-west-2</code> to <code>us-east-1</code></li><li><code>us-west-2</code> to <code>eu-west-1</code></li><li><code>eu-west-1</code> to <code>us-east-1</code></li><li><code>eu-west-1</code> to <code>us-west-2</code></li></ul><p>If you need four regions, you will end up with 12 connections and so on. The following figure shows the configuration for three regions.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/07/s3-bucket-cross-region-replication@730w.webp 730w, /images/2021/07/s3-bucket-cross-region-replication@730w2x.webp 1460w, /images/2021/07/s3-bucket-cross-region-replication@610w.webp 610w, /images/2021/07/s3-bucket-cross-region-replication@610w2x.webp 1220w, /images/2021/07/s3-bucket-cross-region-replication@450w.webp 450w, /images/2021/07/s3-bucket-cross-region-replication@450w2x.webp 900w, /images/2021/07/s3-bucket-cross-region-replication@330w.webp 330w, /images/2021/07/s3-bucket-cross-region-replication@330w2x.webp 660w, /images/2021/07/s3-bucket-cross-region-replication@545w.webp 545w, /images/2021/07/s3-bucket-cross-region-replication@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/07/s3-bucket-cross-region-replication@730w.png 730w, /images/2021/07/s3-bucket-cross-region-replication@730w2x.png 1460w, /images/2021/07/s3-bucket-cross-region-replication@610w.png 610w, /images/2021/07/s3-bucket-cross-region-replication@610w2x.png 1220w, /images/2021/07/s3-bucket-cross-region-replication@450w.png 450w, /images/2021/07/s3-bucket-cross-region-replication@450w2x.png 900w, /images/2021/07/s3-bucket-cross-region-replication@330w.png 330w, /images/2021/07/s3-bucket-cross-region-replication@330w2x.png 660w, /images/2021/07/s3-bucket-cross-region-replication@545w.png 545w, /images/2021/07/s3-bucket-cross-region-replication@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/07/s3-bucket-cross-region-replication.png" alt="S3 Bucket Cross-Region Replication" title="S3 Bucket Cross-Region Replication"></picture></p><p>As promised, here is a comparison table with the available datastore options that support cross-region replication.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th>DynamoDB global tables</th><th>RDS Aurora global databases</th><th>S3 Cross-Region Two-Way Replication</th><th>ElastiCache for Redis Global Datastore</th><th>RDS Read Replica</th><th>Document DB Global Clusters</th></tr></thead><tbody><tr><td>Multi-Region write support</td><td>✅</td><td>❌</td><td>✅</td><td>❌</td><td>❌</td><td>❌</td></tr><tr><td>Writer-Region Failover</td><td>n/a (not needed)</td><td>automated</td><td>n/a (not needed)</td><td>manual</td><td>manual</td><td>manual</td></tr><tr><td>Propagation delay</td><td>~ &lt;1 second</td><td>~ &lt;1 second</td><td>~ &lt;15 minutes</td><td>&lt;~1 second</td><td>~ &lt;1 second</td><td>~ &lt;1 second</td></tr><tr><td>Conflict resolution</td><td>last writer wins</td><td>n/a (writes can only happen in one region)</td><td>all versions are replicated, order might differ</td><td>n/a (writes can only happen in one region)</td><td>n/a (writes can only happen in one region)</td><td>n/a (writes can only happen in one region)</td></tr><tr><td>Multi-Region transactions support</td><td>❌</td><td>n/a (writes can only happen in one region)</td><td>❌</td><td>n/a (writes can only happen in one region)</td><td>n/a (writes can only happen in one region)</td><td>n/a (writes can only happen in one region)</td></tr><tr><td>Multi-Region read support</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>Maximum regions</td><td>all</td><td>1+5</td><td>all</td><td>1+5</td><td>1+5</td><td>1+5</td></tr><tr><td>Serverless provisioning</td><td>✅ (configurable)</td><td>❌</td><td>✅</td><td>❌</td><td>❌</td><td>❌</td></tr></tbody></table><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Until today, Multi-Region deployments are exceptions, not the norm. Luckily, routing user traffic into different regions is a solved problem on AWS. You can rely either on DNS with Route53 or Anycast with Global Accelerator.<br>Remember, not all datastores offer the same capabilities. When selecting a datastore, it is crucial to understand the differences as highlighted in the comparison table. </p><p>With all that in mind you can architect multi region systems on AWS today. Multi-Region AWS architectures are more complex and expensive compared to a single region deployment. Make sure that there is a real business case for the availability requirements. </p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Choosing the best way to scale EC2 instances on demand</title>
      <link>https://cloudonaut.io/choosing-the-best-way-to-scale-ec2-instances-on-demand/</link>
      <description>
        <![CDATA[<p>Migrating workloads into the cloud — and specifically to AWS — comes with many advantages. You can operate workloads in new ways. When yo]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <category domain="https://cloudonaut.io/tag/auto-scaling/">auto-scaling</category>
      <guid isPermaLink="true">https://cloudonaut.io/choosing-the-best-way-to-scale-ec2-instances-on-demand/</guid>
      <pubDate>Wed, 26 May 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Migrating workloads into the cloud — and specifically to AWS — comes with many advantages. You can operate workloads in new ways. When you only pay for what you use and add capacity within minutes, the world of auto-scaling opens up. When your workload is idling, you remove capacity and lower your expenses. When the workload is busy, you add capacity to keep the users happy. An excellent idea that works perfectly in theory. In practice, many pitfalls are waiting for you.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/05/auto-scaling@730w.webp 730w, /images/2021/05/auto-scaling@730w2x.webp 1460w, /images/2021/05/auto-scaling@610w.webp 610w, /images/2021/05/auto-scaling@610w2x.webp 1220w, /images/2021/05/auto-scaling@450w.webp 450w, /images/2021/05/auto-scaling@450w2x.webp 900w, /images/2021/05/auto-scaling@330w.webp 330w, /images/2021/05/auto-scaling@330w2x.webp 660w, /images/2021/05/auto-scaling@545w.webp 545w, /images/2021/05/auto-scaling@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/05/auto-scaling@730w.jpg 730w, /images/2021/05/auto-scaling@730w2x.jpg 1460w, /images/2021/05/auto-scaling@610w.jpg 610w, /images/2021/05/auto-scaling@610w2x.jpg 1220w, /images/2021/05/auto-scaling@450w.jpg 450w, /images/2021/05/auto-scaling@450w2x.jpg 900w, /images/2021/05/auto-scaling@330w.jpg 330w, /images/2021/05/auto-scaling@330w2x.jpg 660w, /images/2021/05/auto-scaling@545w.jpg 545w, /images/2021/05/auto-scaling@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/05/auto-scaling.jpg" alt="Auto Scaling" title="Auto Scaling"></picture></p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><p>Read on to understand the options that AWS provides to help you scale EC2 instances in and out.</p><h2 id="Vertical-versus-horizontal-scaling"><a href="#Vertical-versus-horizontal-scaling" class="headerlink" title="Vertical versus horizontal scaling"></a>Vertical versus horizontal scaling</h2><p>Vertical scaling refers to the practice of upgrading the hardware of a machine that runs your workloads. Instead of running on 16 cores, you can run on 32 cores. The problem here is that you can not add as many cores as you wish. Infinite core CPUs are not yet invented. Besides that, CPUs with a high core density are usually more expensive than many CPUs with a lower core count.</p><p>On AWS, it is not possible to change the hardware specs of a running EC2 instance. Vertical scaling always comes with a short downtime. You have to stop the EC2 instance, change the instance type, and start the EC2 instance again.</p><p>Horizontal scaling is about adding more machines, usually by using cheap commodity hardware.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/05/horizontal-scaling@730w.webp 730w, /images/2021/05/horizontal-scaling@730w2x.webp 1460w, /images/2021/05/horizontal-scaling@610w.webp 610w, /images/2021/05/horizontal-scaling@610w2x.webp 1220w, /images/2021/05/horizontal-scaling@450w.webp 450w, /images/2021/05/horizontal-scaling@450w2x.webp 900w, /images/2021/05/horizontal-scaling@330w.webp 330w, /images/2021/05/horizontal-scaling@330w2x.webp 660w, /images/2021/05/horizontal-scaling@545w.webp 545w, /images/2021/05/horizontal-scaling@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/05/horizontal-scaling@730w.png 730w, /images/2021/05/horizontal-scaling@730w2x.png 1460w, /images/2021/05/horizontal-scaling@610w.png 610w, /images/2021/05/horizontal-scaling@610w2x.png 1220w, /images/2021/05/horizontal-scaling@450w.png 450w, /images/2021/05/horizontal-scaling@450w2x.png 900w, /images/2021/05/horizontal-scaling@330w.png 330w, /images/2021/05/horizontal-scaling@330w2x.png 660w, /images/2021/05/horizontal-scaling@545w.png 545w, /images/2021/05/horizontal-scaling@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/05/horizontal-scaling.png" alt="Horizontal Scaling" title="Horizontal Scaling"></picture></p><p>Horizontal auto-scaling comes with important limitations:</p><ol><li>It only works when your workloads can be scaled horizontally by adding more EC2 instances. This usually requires that the EC2 instances run behind a load balancer or that <a href="/messaging-on-aws/">requests are processed asynchronously from a message queue</a>.</li><li>You have to leverage Auto Scaling Groups to manage the EC2 instances.</li></ol><p>Let’s have a closer look at Auto Scaling Groups.</p><h2 id="Auto-Scaling-Groups"><a href="#Auto-Scaling-Groups" class="headerlink" title="Auto Scaling Groups"></a>Auto Scaling Groups</h2><p>The naming Auto Scaling Group (ASG) is misleading. The surprising fact is this: ASGs don’t provide auto-scaling capabilities. An ASG has one job: Keep the number of running EC2 instances in sync with a desired number of instances. The ASG itself does not modify the desired number of instances. An external trigger is responsible for adjusting the desired number of instances. By increasing the number, the ASG will add EC2 instances (scale-out). By decreasing the number, the ASL will terminate EC2 instances (scale-in).</p><p>I downplayed the capabilities of an ASG. ASGs are great and can do many things, but they will not auto-scale your workload. To do so, we need an external trigger to adjust the desired number of instances. AWS provides four options:</p><ul><li>Simple Scaling</li><li>Step Scaling</li><li>Target Tracking</li><li>AWS Auto Scaling</li></ul><p>Let’s have a look at each of them.</p><h2 id="Simple-Scaling"><a href="#Simple-Scaling" class="headerlink" title="Simple Scaling"></a>Simple Scaling</h2><p>This option relies on a CloudWatch Alarm. The alarm watches a metric like <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html#ec2-cloudwatch-metrics" target="_blank" rel="noopener">network or CPU utilization</a> or the <a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-cloudwatch-metrics.html#load-balancer-metrics-alb" target="_blank" rel="noopener">requests per target behind a load balancer</a>. If the metric reaches a predefined threshold, a <a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-scaling-simple-step.html" target="_blank" rel="noopener">scaling policy</a> is triggered. The scaling policy defines:</p><ul><li>How many instances are added or terminated? You can specify an absolute or relative number.</li><li>The pause between two scaling activities, aka <a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/Cooldown.html" target="_blank" rel="noopener">cooldown</a>. Remember that it takes minutes until newly launched EC2 instances impact the CloudWatch metric that you use to trigger the scaling policy.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/05/simple-scaling@730w.webp 730w, /images/2021/05/simple-scaling@730w2x.webp 1460w, /images/2021/05/simple-scaling@610w.webp 610w, /images/2021/05/simple-scaling@610w2x.webp 1220w, /images/2021/05/simple-scaling@450w.webp 450w, /images/2021/05/simple-scaling@450w2x.webp 900w, /images/2021/05/simple-scaling@330w.webp 330w, /images/2021/05/simple-scaling@330w2x.webp 660w, /images/2021/05/simple-scaling@545w.webp 545w, /images/2021/05/simple-scaling@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/05/simple-scaling@730w.png 730w, /images/2021/05/simple-scaling@730w2x.png 1460w, /images/2021/05/simple-scaling@610w.png 610w, /images/2021/05/simple-scaling@610w2x.png 1220w, /images/2021/05/simple-scaling@450w.png 450w, /images/2021/05/simple-scaling@450w2x.png 900w, /images/2021/05/simple-scaling@330w.png 330w, /images/2021/05/simple-scaling@330w2x.png 660w, /images/2021/05/simple-scaling@545w.png 545w, /images/2021/05/simple-scaling@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/05/simple-scaling.png" alt="Simple Scaling" title="Simple Scaling"></picture></p><p>A couple of pitfalls here:</p><ul><li>If you receive a sharp rise in traffic, the scale-out likely is too slow. Keep in mind that the cooldown limits how often a scale-out can happen. Usually, the cooldown is somewhere around 5-10 minutes.</li><li>You have to choose a metric for scaling that represents the bottleneck of the architecture under load. If you scale based on CPU, but the bottleneck is the database connection pool size, you will run into issues. Load testing is required!</li><li>Scaling takes time. Metric data is only reported every minute. CloudWatch alarms take time to fire as well. Last but not least, launching new EC2 instances and starting up your application also takes time. It is highly recommended to use AMIs that contain your application to eliminate installation times.</li></ul><p>The next approach deals with a sharp rise in traffic.</p><h2 id="Step-Scaling"><a href="#Step-Scaling" class="headerlink" title="Step Scaling"></a>Step Scaling</h2><p>Step scaling also required a CloudWatch Alarm. But you can react differently based on how bad things are. You could add one instance if the CPU is utilized more than 60%. But if the CPU utilization grows to 70%, you want to add two instances. If it rises to 80%, you add four instances. If it grows to 90%, you add eight.</p><p>There is no cooldown as well. Instead, you can define an estimated instance warmup timespan. During this timespan, step scaling will not count the EC2 instance into the number of running instances.</p><p>A couple of pitfalls here:</p><ul><li>If your estimated instance warmup is too low, you will scale out more often than required. Keep in mind that it takes time until a newly launched instance affects your scaling policy (usually this around 5-10 minutes).</li><li>The scaling metric pitfall of Simple Scaling still applies.</li></ul><p>If the previous approaches look too cumbersome, Target Tracking might be for you.</p><h2 id="Target-Tracking"><a href="#Target-Tracking" class="headerlink" title="Target Tracking"></a>Target Tracking</h2><p>With target tracking, you ask AWS to keep a metric within a specific range. For example, you can tell AWS to keep your CPU utilization inside an ASG at 50%. AWS will adjust the number of instances in a way to reach the target. No CloudWatch Alarm is required here. AWS will create alarms on your behalf.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/05/target-tracking@730w.webp 730w, /images/2021/05/target-tracking@730w2x.webp 1460w, /images/2021/05/target-tracking@610w.webp 610w, /images/2021/05/target-tracking@610w2x.webp 1220w, /images/2021/05/target-tracking@450w.webp 450w, /images/2021/05/target-tracking@450w2x.webp 900w, /images/2021/05/target-tracking@330w.webp 330w, /images/2021/05/target-tracking@330w2x.webp 660w, /images/2021/05/target-tracking@545w.webp 545w, /images/2021/05/target-tracking@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/05/target-tracking@730w.png 730w, /images/2021/05/target-tracking@730w2x.png 1460w, /images/2021/05/target-tracking@610w.png 610w, /images/2021/05/target-tracking@610w2x.png 1220w, /images/2021/05/target-tracking@450w.png 450w, /images/2021/05/target-tracking@450w2x.png 900w, /images/2021/05/target-tracking@330w.png 330w, /images/2021/05/target-tracking@330w2x.png 660w, /images/2021/05/target-tracking@545w.png 545w, /images/2021/05/target-tracking@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/05/target-tracking.png" alt="Target Tracking" title="Target Tracking"></picture></p><p>The following metrics are supported by target racking out-of-the-box:</p><ul><li>Average CPU utilization of the Auto Scaling group.</li><li>Average number of bytes received on all network interfaces by the Auto Scaling group.</li><li>Average number of bytes sent out on all network interfaces by the Auto Scaling group.</li><li>Number of requests completed per target in an Application Load Balancer target group.</li></ul><p>You can also define custom metrics if you wish, but the scaling metric pitfall of Simple Scaling still applies.</p><h2 id="AWS-Auto-Scaling"><a href="#AWS-Auto-Scaling" class="headerlink" title="AWS Auto Scaling"></a>AWS Auto Scaling</h2><p><a href="https://docs.aws.amazon.com/autoscaling/plans/userguide/what-is-aws-auto-scaling.html" target="_blank" rel="noopener">AWS Auto Scaling</a> can be confusing. It also provides target tracking, which is called dynamic scaling, but t. The difference is that you also get predictive scaling. To do so, AWS Auto Scaling takes the past 14 days into account to predict the next two days. This works great for workloads that are less used on weekends or during the night. Black Friday and large end-of-month batch jobs will not be detected.</p><p>A couple of pitfalls here:</p><ul><li>Remember that only the past 14 days are used to predict future capacity needs.</li></ul><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>No matter which option you choose, don’t forget to run extensive load tests to validate your configuration. Load testing is very time-consuming. You have to wait a lot to see how the system behaves while load patterns change.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th>Simple Scaling</th><th>Step Scaling</th><th>Target Tracking</th><th>AWS Auto Scaling</th></tr></thead><tbody><tr><td>Mode</td><td>reactive</td><td>reactive</td><td>reactive</td><td>proactive</td></tr><tr><td>Requires predefined thresholds</td><td>✅</td><td>✅</td><td>❌</td><td>❌</td></tr><tr><td>Becomes more aggressive during steep rise</td><td>❌</td><td>⚠️</td><td>✅</td><td>✅</td></tr></tbody></table><p>We covered to scale based on demand. You can also use scheduled rules to add and remove capacity proactively.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Calculate AWS Costs in Detail</title>
      <link>https://cloudonaut.io/calculate-aws-costs-in-detail/</link>
      <description>
        <![CDATA[<p>Calculating AWS costs upfront is a challenge. To get accurate numbers, you have to understand the pricing model of the used AWS services.]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/costs/">costs</category>
      <guid isPermaLink="true">https://cloudonaut.io/calculate-aws-costs-in-detail/</guid>
      <pubDate>Wed, 19 May 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Calculating AWS costs upfront is a challenge. To get accurate numbers, you have to understand the pricing model of the used AWS services. A pricing model consists of one or many pricing dimensions. E.g., GBs stored, number of requests, etc. Keep in mind that each service is different. EC2 instances are billed based on how long they run, how much storage you provision, and how much traffic they receive and send (some traffic is free). The pricing dimensions of an S3 bucket are stored data, number of requests, number of tags, and many more. After you understand the pricing model, you have to come up with the numbers. How much traffic does your application process? How many requests to S3 do you plan to make? The second step is usually the more challenging part. In this blog post, I share the tools and techniques I use to calculate AWS costs.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/05/cost-calculation@730w.webp 730w, /images/2021/05/cost-calculation@730w2x.webp 1460w, /images/2021/05/cost-calculation@610w.webp 610w, /images/2021/05/cost-calculation@610w2x.webp 1220w, /images/2021/05/cost-calculation@450w.webp 450w, /images/2021/05/cost-calculation@450w2x.webp 900w, /images/2021/05/cost-calculation@330w.webp 330w, /images/2021/05/cost-calculation@330w2x.webp 660w, /images/2021/05/cost-calculation@545w.webp 545w, /images/2021/05/cost-calculation@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/05/cost-calculation@730w.jpg 730w, /images/2021/05/cost-calculation@730w2x.jpg 1460w, /images/2021/05/cost-calculation@610w.jpg 610w, /images/2021/05/cost-calculation@610w2x.jpg 1220w, /images/2021/05/cost-calculation@450w.jpg 450w, /images/2021/05/cost-calculation@450w2x.jpg 900w, /images/2021/05/cost-calculation@330w.jpg 330w, /images/2021/05/cost-calculation@330w2x.jpg 660w, /images/2021/05/cost-calculation@545w.jpg 545w, /images/2021/05/cost-calculation@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/05/cost-calculation.jpg" alt="Calculate AWS Costs in Detail" title="Calculate AWS Costs in Detail"></picture></p><h2 id="The-ins-and-outs-of-the-architecture"><a href="#The-ins-and-outs-of-the-architecture" class="headerlink" title="The ins and outs of the architecture"></a>The ins and outs of the architecture</h2><p>To calculate costs, you need a detailed understanding of the architecture. What AWS services are used? How are the services used? What communication paths exist? What data is processed and stored? What are data retention policies? And many more questions.</p><p>I usually start by looking at the architecture diagram. I recommend diving as deep as possible to understand what’s going on. VPC Flow Logs can be helpful to understand communication paths. IAM policies provide a hint of what APIs are called.</p><p>Once you know what AWS services are used, you can familiarize yourself with the pricing models of each service.</p><h2 id="Understanding-the-pricing-models"><a href="#Understanding-the-pricing-models" class="headerlink" title="Understanding the pricing models"></a>Understanding the pricing models</h2><p>Each AWS service has a pricing page. I recommend you to read them from top to bottom. There are no shortcuts if you need an accurate cost calculation. Check out the following pricing pages to get a sense of the differences between services:</p><ul><li><a href="https://aws.amazon.com/ec2/pricing/" target="_blank" rel="noopener">Amazon EC2 pricing</a></li><li><a href="https://aws.amazon.com/s3/pricing/" target="_blank" rel="noopener">Amazon S3 pricing</a></li><li><a href="https://aws.amazon.com/sqs/pricing/" target="_blank" rel="noopener">Amazon SQS pricing</a></li></ul><p>Keep in mind that prices are likely different in each AWS region!</p><h2 id="Estimating-price-dimension-usage"><a href="#Estimating-price-dimension-usage" class="headerlink" title="Estimating price dimension usage"></a>Estimating price dimension usage</h2><p>Once you understand the pricing dimensions, you have to estimate the usage for each of the dimensions. Some dimensions are straightforward to guess. Others are more tricky. Let me provide you two examples.</p><p>A CloudWath dashboard is priced at $3 &#x2F; month. I know that I create one dashboard, so it is easy to estimate the costs.</p><p>CloudWatch Logs are priced based on the data volume you ingest and the data volume you store. The problem: How much log data does your application produce? The log volume likely correlates with the number of requests the application processes. I recommend running an experiment to get a better understanding of the usage pattern:</p><ol><li>Set up your application.</li><li>Take a note of the <code>storedBytes</code> value of the log group.</li><li>Run <code>x</code> number of requests through the system. If you are lucky, you can reuse a load test that you already have. Make sure that <code>x</code> is large enough (e.g., 100,000 requests).</li><li>Take a note of the <code>storedBytes</code> value again.</li><li>Calculate the difference between the two <code>storedBytes</code> values and divide them by <code>x</code>.</li></ol><h2 id="Tools"><a href="#Tools" class="headerlink" title="Tools"></a>Tools</h2><p>AWS provides tools to help us with cost calculations. the <a href="https://calculator.aws/" target="_blank" rel="noopener">Pricing Calculator</a> is the new kid on the block. The <a href="https://calculator.s3.amazonaws.com/index.html" target="_blank" rel="noopener">Simple Monthly Calculator</a> is the older version you might need to fall back to. Both tools miss services and pricing dimensions! I highly recommend not skipping the <strong>Understanding the pricing models</strong> step to avoid surprises.</p><p>I use the AWS tools to come up with rough estimates only. If I need detailed calculations, I create a spreadsheet. I believe you need a spreadsheet anyway because you usually need to calculate the pricing dimension usage to enter into the tools.</p><p>I prefer to use a “business” metric as a starting point, such as daily active users or the number of files uploaded. Based on this number, I can calculate the pricing dimension usage. The screenshot shows a real-world calculation of mine:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/05/calc-spreadsheet@730w.webp 730w, /images/2021/05/calc-spreadsheet@730w2x.webp 1460w, /images/2021/05/calc-spreadsheet@610w.webp 610w, /images/2021/05/calc-spreadsheet@610w2x.webp 1220w, /images/2021/05/calc-spreadsheet@450w.webp 450w, /images/2021/05/calc-spreadsheet@450w2x.webp 900w, /images/2021/05/calc-spreadsheet@330w.webp 330w, /images/2021/05/calc-spreadsheet@330w2x.webp 660w, /images/2021/05/calc-spreadsheet@545w.webp 545w, /images/2021/05/calc-spreadsheet@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/05/calc-spreadsheet@730w.png 730w, /images/2021/05/calc-spreadsheet@730w2x.png 1460w, /images/2021/05/calc-spreadsheet@610w.png 610w, /images/2021/05/calc-spreadsheet@610w2x.png 1220w, /images/2021/05/calc-spreadsheet@450w.png 450w, /images/2021/05/calc-spreadsheet@450w2x.png 900w, /images/2021/05/calc-spreadsheet@330w.png 330w, /images/2021/05/calc-spreadsheet@330w2x.png 660w, /images/2021/05/calc-spreadsheet@545w.png 545w, /images/2021/05/calc-spreadsheet@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/05/calc-spreadsheet.png" alt="AWS calculation spreadsheet" title="AWS calculation spreadsheet"></picture></p><h2 id="Pitfall-Traffic"><a href="#Pitfall-Traffic" class="headerlink" title="Pitfall: Traffic"></a>Pitfall: Traffic</h2><p>I see many calculations miss an important aspect: traffic. Traffic costs are very tricky:</p><ul><li>Traffic from the Internet to EC2 is free.</li><li>Traffic from EC2 to the Internet is billed.</li><li>Traffic between EC2 instances in the same AZ is free.</li><li>Traffic between EC2 instances in different AZs&#x2F;regions is billed.</li><li>Traffic between EC2 and RDS is free.</li><li>Traffic between EC2 and MSK (Kafka) is billed.</li></ul><p>I usually think about traffic costs this way: I assume all traffic is billed. I check the pricing pages to find the traffic costs. In some cases, you will find that traffic is free.</p><p>In the following video, I share my workflow in more details based on a real-world example:</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=nkB560-aCIA">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/nkB560-aCIA" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Calculating AWS costs is tricky. I recommend following these steps:</p><ol><li>Understand the architecture</li><li>Research pricing models of used AWS services</li><li>Estimate price dimension usage</li></ol><p>Creating an accurate calculation for a medium complex architecture can take a working day.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Getting Started with Free Templates for AWS CloudFormation</title>
      <link>https://cloudonaut.io/getting-started-with-aws-cf-templates/</link>
      <description>
        <![CDATA[<p>Writing CloudFormation templates from scratch is a lot of work. You will run into many issues along the way: the documentation is incompl]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <guid isPermaLink="true">https://cloudonaut.io/getting-started-with-aws-cf-templates/</guid>
      <pubDate>Thu, 13 May 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Writing CloudFormation templates from scratch is a lot of work. You will run into many issues along the way: the documentation is incomplete, magic values are required, unsupported combinations of attributes, etc. The feedback cycles are long. In the end, we have to provision real infrastructure to test the template. If you ever created an Elastisearch cluster, you feel the pain. We also observe that AWS architectures follow similar patterns (aka best practices). So why not make a collection of templates and share them with the world? That’s what we did in late 2015. We launched <a href="https://github.com/widdix/aws-cf-templates/" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a>. In this blog post, I provide you an overview of the project and show you typical use cases.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/05/getting-started@730w.webp 730w, /images/2021/05/getting-started@730w2x.webp 1460w, /images/2021/05/getting-started@610w.webp 610w, /images/2021/05/getting-started@610w2x.webp 1220w, /images/2021/05/getting-started@450w.webp 450w, /images/2021/05/getting-started@450w2x.webp 900w, /images/2021/05/getting-started@330w.webp 330w, /images/2021/05/getting-started@330w2x.webp 660w, /images/2021/05/getting-started@545w.webp 545w, /images/2021/05/getting-started@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/05/getting-started@730w.jpg 730w, /images/2021/05/getting-started@730w2x.jpg 1460w, /images/2021/05/getting-started@610w.jpg 610w, /images/2021/05/getting-started@610w2x.jpg 1220w, /images/2021/05/getting-started@450w.jpg 450w, /images/2021/05/getting-started@450w2x.jpg 900w, /images/2021/05/getting-started@330w.jpg 330w, /images/2021/05/getting-started@330w2x.jpg 660w, /images/2021/05/getting-started@545w.jpg 545w, /images/2021/05/getting-started@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/05/getting-started.jpg" alt="Getting Started with Free Templates for AWS CloudFormation" title="Getting Started with Free Templates for AWS CloudFormation"></picture></p><p>You can speed up development and migration projects by reusing our templates to create complex environments for everyday use cases with ease. All templates are <a href="https://github.com/widdix/aws-cf-templates/pulls" target="_blank" rel="noopener">peer-reviewed</a> by an expert and <a href="https://github.com/widdix/aws-cf-templates/tree/master/test" target="_blank" rel="noopener">verified with automated tests</a>. We even go one step further. All templates are production-ready.</p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/41-getting-started-with-aws-cf-templates/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="Production-ready"><a href="#Production-ready" class="headerlink" title="Production-ready"></a>Production-ready</h2><p>If no other limitations are documented, the following applies:</p><ul><li>Highly available: The template has no single point of failure.</li><li>Scalable: The capacity increases or decreases based on utilization (auto-scaling).</li><li>Frictionless deployment: You can deploy new versions of the templates or your application without downtime.</li><li>Secure: We use the latest operating systems and software components. We follow the least privilege principle in all areas (IAM, network). We support encryption. We enable backups.</li><li>Operator-friendly: Logging, monitoring, and alerting are configured out-of-the-box.</li></ul><p>Let’s see what you can build with our templates.</p><h2 id="Use-cases"><a href="#Use-cases" class="headerlink" title="Use cases"></a>Use cases</h2><p>Our templates are designed in a reusable way. Most templates depend on other templates. In many cases, you need to create a VPC first.</p><h3 id="VPC-setup"><a href="#VPC-setup" class="headerlink" title="VPC setup"></a>VPC setup</h3><p>Many AWS workloads run on a VPC setup like this:</p><ul><li>Three public subnets</li><li>Three private subnets<ul><li>access to the Internet via NAT gateways </li><li>access to the AWS API via endpoints</li></ul></li><li>VPC Flow Logs to record network activity</li><li>VPN bastion host that admins&#x2F;devs can use to access EC2 instances, RSD databases, etc. from local machines</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/05/aws-cf-templates-vpc-setup@730w.webp 730w, /images/2021/05/aws-cf-templates-vpc-setup@730w2x.webp 1460w, /images/2021/05/aws-cf-templates-vpc-setup@610w.webp 610w, /images/2021/05/aws-cf-templates-vpc-setup@610w2x.webp 1220w, /images/2021/05/aws-cf-templates-vpc-setup@450w.webp 450w, /images/2021/05/aws-cf-templates-vpc-setup@450w2x.webp 900w, /images/2021/05/aws-cf-templates-vpc-setup@330w.webp 330w, /images/2021/05/aws-cf-templates-vpc-setup@330w2x.webp 660w, /images/2021/05/aws-cf-templates-vpc-setup@545w.webp 545w, /images/2021/05/aws-cf-templates-vpc-setup@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/05/aws-cf-templates-vpc-setup@730w.png 730w, /images/2021/05/aws-cf-templates-vpc-setup@730w2x.png 1460w, /images/2021/05/aws-cf-templates-vpc-setup@610w.png 610w, /images/2021/05/aws-cf-templates-vpc-setup@610w2x.png 1220w, /images/2021/05/aws-cf-templates-vpc-setup@450w.png 450w, /images/2021/05/aws-cf-templates-vpc-setup@450w2x.png 900w, /images/2021/05/aws-cf-templates-vpc-setup@330w.png 330w, /images/2021/05/aws-cf-templates-vpc-setup@330w2x.png 660w, /images/2021/05/aws-cf-templates-vpc-setup@545w.png 545w, /images/2021/05/aws-cf-templates-vpc-setup@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/05/aws-cf-templates-vpc-setup.png" alt="VPC setup" title="VPC setup"></picture></p><p>To deploy this architecture, start with a template that has no dependencies (no outgoing arrows). Once the CloudFirmation stacks are created, you can continue with the rest of the templates. Those templates with dependencies have parameters that start with <strong>Parent</strong>. The value is the CloudFormtion stack name that you want to link this new stack with.</p><p>The cool thing is that you can re-use the dependencies. E.g., you can use the same VPC for many different workloads.</p><p>Used templates:</p><ul><li><a href="https://templates.cloudonaut.io/en/stable/operations/#alert-topic" target="_blank" rel="noopener">operations&#x2F;alert</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#vpc-with-private-and-public-subnets-in-three-availability-zones" target="_blank" rel="noopener">vpc&#x2F;vpc-3azs</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#nat-gateway" target="_blank" rel="noopener">vpc&#x2F;vpc-nat-gateway</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#vpn-bastion-hostinstance" target="_blank" rel="noopener">vpc&#x2F;vpc-vpn-bastion</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#gateway-endpoint-s3" target="_blank" rel="noopener">vpc&#x2F;vpc-endpoint-s3</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#gateway-endpoint-dynamodb" target="_blank" rel="noopener">vpc&#x2F;vpc-endpoint-dynamodb</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#interface-endpoint" target="_blank" rel="noopener">vpc&#x2F;vpc-endpoint</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#vpc-flow-logs-to-cloudwatch-logs" target="_blank" rel="noopener">vpc&#x2F;vpc-flow-logs</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#public-dns-zone" target="_blank" rel="noopener">vpc&#x2F;zone-public</a></li></ul><p>You can also run real workloads with our templates.</p><h3 id="Containerized-app"><a href="#Containerized-app" class="headerlink" title="Containerized app"></a>Containerized app</h3><p>There are many options to run containerized workloads on AWS. We recommend using ECS Fargate. Templates for a large collection of datastores exist as well. In this case, we use RDS. Additionally, we take care of DNS, alerting, and the VPN connection to the database for your team.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/05/aws-cf-templates-fargate-service@730w.webp 730w, /images/2021/05/aws-cf-templates-fargate-service@730w2x.webp 1460w, /images/2021/05/aws-cf-templates-fargate-service@610w.webp 610w, /images/2021/05/aws-cf-templates-fargate-service@610w2x.webp 1220w, /images/2021/05/aws-cf-templates-fargate-service@450w.webp 450w, /images/2021/05/aws-cf-templates-fargate-service@450w2x.webp 900w, /images/2021/05/aws-cf-templates-fargate-service@330w.webp 330w, /images/2021/05/aws-cf-templates-fargate-service@330w2x.webp 660w, /images/2021/05/aws-cf-templates-fargate-service@545w.webp 545w, /images/2021/05/aws-cf-templates-fargate-service@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/05/aws-cf-templates-fargate-service@730w.png 730w, /images/2021/05/aws-cf-templates-fargate-service@730w2x.png 1460w, /images/2021/05/aws-cf-templates-fargate-service@610w.png 610w, /images/2021/05/aws-cf-templates-fargate-service@610w2x.png 1220w, /images/2021/05/aws-cf-templates-fargate-service@450w.png 450w, /images/2021/05/aws-cf-templates-fargate-service@450w2x.png 900w, /images/2021/05/aws-cf-templates-fargate-service@330w.png 330w, /images/2021/05/aws-cf-templates-fargate-service@330w2x.png 660w, /images/2021/05/aws-cf-templates-fargate-service@545w.png 545w, /images/2021/05/aws-cf-templates-fargate-service@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/05/aws-cf-templates-fargate-service.png" alt="Containerized app" title="Containerized app"></picture></p><p>Used templates:</p><ul><li><a href="https://templates.cloudonaut.io/en/stable/operations/#alert-topic" target="_blank" rel="noopener">operations&#x2F;alert</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#vpc-with-private-and-public-subnets-in-three-availability-zones" target="_blank" rel="noopener">vpc&#x2F;vpc-3azs</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#vpn-bastion-hostinstance" target="_blank" rel="noopener">vpc&#x2F;vpc-vpn-bastion</a></li><li><a href="https://templates.cloudonaut.io/en/stable/fargate/#fargate-cluster" target="_blank" rel="noopener">fargate&#x2F;cluster</a></li><li><a href="https://templates.cloudonaut.io/en/stable/fargate/#using-the-clusters-load-balancer-and-path-andor-host-based-routing" target="_blank" rel="noopener">fargate&#x2F;service-cluster-alb</a></li><li><a href="https://templates.cloudonaut.io/en/stable/state/#client-security-group" target="_blank" rel="noopener">state&#x2F;client-sg</a></li><li><a href="https://templates.cloudonaut.io/en/stable/state/#rds-mysql" target="_blank" rel="noopener">state&#x2F;rds-mysql</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#public-dns-zone" target="_blank" rel="noopener">vpc&#x2F;zone-public</a></li></ul><p>Check out the following template catalog to get an idea of what we support.</p><h2 id="Template-catalog"><a href="#Template-catalog" class="headerlink" title="Template catalog"></a>Template catalog</h2><p>The following templates are available:</p><ul><li>Elastic Compute Cloud (EC2)<ul><li><a href="https://templates.cloudonaut.io/en/stable/ec2/#amazon-linux-2-mutable-public" target="_blank" rel="noopener">Amazon Linux 2 (mutable, public)</a></li><li><a href="https://templates.cloudonaut.io/en/stable/ec2/#amazon-linux-2-mutable-private" target="_blank" rel="noopener">Amazon Linux 2 (mutable, private)</a></li></ul></li><li>EC2 Container Service (ECS)<ul><li><a href="https://templates.cloudonaut.io/en/stable/ecs/#ecs-cluster" target="_blank" rel="noopener">ECS cluster</a></li><li><a href="https://templates.cloudonaut.io/en/stable/ecs/#ecs-cluster-cost-optimzed" target="_blank" rel="noopener">ECS cluster (cost optimzed)</a></li><li><a href="https://templates.cloudonaut.io/en/stable/ecs/#using-the-clusters-load-balancer-and-path-andor-host-based-routing" target="_blank" rel="noopener">ECS service (cluster’s load balancer)</a></li><li><a href="https://templates.cloudonaut.io/en/stable/ecs/#using-a-dedicated-load-balancer-for-the-service" target="_blank" rel="noopener">ECS service (dedicated load balancer)</a></li></ul></li><li>Fargate<ul><li><a href="https://templates.cloudonaut.io/en/stable/fargate/#fargate-cluster" target="_blank" rel="noopener">Fargate cluster</a></li><li><a href="https://templates.cloudonaut.io/en/stable/fargate/#using-the-clusters-load-balancer-and-path-andor-host-based-routing" target="_blank" rel="noopener">Fargate service (cluster’s load balancer)</a></li><li><a href="https://templates.cloudonaut.io/en/stable/fargate/#using-a-dedicated-load-balancer-for-the-service" target="_blank" rel="noopener">Fargate service (dedicated load balancer)</a></li><li><a href="https://templates.cloudonaut.io/en/stable/fargate/#using-service-discovery-aka-cloud-map" target="_blank" rel="noopener">Fargate service (service discovery aka. Cloud Map)</a></li></ul></li><li>Jenkins<ul><li><a href="https://templates.cloudonaut.io/en/stable/jenkins/#jenkins-20-highly-available-master" target="_blank" rel="noopener">Jenkins 2.0: highly available master</a></li><li><a href="https://templates.cloudonaut.io/en/stable/jenkins/#jenkins-20-highly-available-master-and-dynamic-agents" target="_blank" rel="noopener">Jenkins 2.0: highly available master and dynamic agents</a></li></ul></li><li>Operations<ul><li><a href="https://templates.cloudonaut.io/en/stable/operations/#alert-topic" target="_blank" rel="noopener">Alert topic</a></li><li><a href="https://templates.cloudonaut.io/en/stable/operations/#cloudfront" target="_blank" rel="noopener">Access Logs Anonymizer (CloudFront)</a></li><li><a href="https://templates.cloudonaut.io/en/stable/operations/#alb" target="_blank" rel="noopener">Access Logs Anonymizer (ALB)</a></li><li><a href="https://templates.cloudonaut.io/en/stable/operations/#terraform-state" target="_blank" rel="noopener">Terraform State</a></li></ul></li><li>Security<ul><li><a href="https://templates.cloudonaut.io/en/stable/security/#account-password-policy" target="_blank" rel="noopener">Account Password Policy</a></li><li><a href="https://templates.cloudonaut.io/en/stable/security/#authentication-proxy-using-your-github-organization" target="_blank" rel="noopener">Authentication Proxy (GitHub Organization)</a></li><li><a href="https://templates.cloudonaut.io/en/stable/security/#cloudtrail-across-all-regions" target="_blank" rel="noopener">CloudTrail across all regions</a></li><li><a href="https://templates.cloudonaut.io/en/stable/security/#aws-config-setup" target="_blank" rel="noopener">AWS Config setup</a></li><li><a href="https://templates.cloudonaut.io/en/stable/security/#kms-customer-managed-cmk-for-aws-services" target="_blank" rel="noopener">KMS customer managed CMK for AWS services</a></li><li><a href="https://templates.cloudonaut.io/en/stable/security/#web-application-firewall" target="_blank" rel="noopener">Web Application Firewall</a></li></ul></li><li>State &#x2F; Data<ul><li><a href="https://templates.cloudonaut.io/en/stable/state/#client-security-group" target="_blank" rel="noopener">Client Security Group</a></li><li><a href="https://templates.cloudonaut.io/en/stable/state/#documentdb" target="_blank" rel="noopener">DocumentDB</a></li><li><a href="https://templates.cloudonaut.io/en/stable/state/#dynamodb-table" target="_blank" rel="noopener">DynamoDB table</a></li><li><a href="https://templates.cloudonaut.io/en/stable/state/#elasticache-memcached" target="_blank" rel="noopener">ElastiCache memcached</a></li><li><a href="https://templates.cloudonaut.io/en/stable/state/#elasticache-redis" target="_blank" rel="noopener">ElastiCache Redis</a></li><li><a href="https://templates.cloudonaut.io/en/stable/state/#elasticsearch" target="_blank" rel="noopener">Elasticsearch</a></li><li><a href="https://templates.cloudonaut.io/en/stable/state/#rds-aurora" target="_blank" rel="noopener">RDS Aurora (MySQL &amp; Postgres)</a></li><li><a href="https://templates.cloudonaut.io/en/stable/state/#rds-aurora-serverless-mysql" target="_blank" rel="noopener">RDS Aurora Serverless MySQL</a></li><li><a href="https://templates.cloudonaut.io/en/stable/state/#rds-aurora-serverless-postgres" target="_blank" rel="noopener">RDS Aurora Serverless Postgres</a></li><li><a href="https://templates.cloudonaut.io/en/stable/state/#rds-mysql" target="_blank" rel="noopener">RDS MySQL</a></li><li><a href="https://templates.cloudonaut.io/en/stable/state/#rds-postgres" target="_blank" rel="noopener">RDS Postgres</a></li><li><a href="https://templates.cloudonaut.io/en/stable/state/#s3" target="_blank" rel="noopener">S3 Bucket</a></li><li><a href="https://templates.cloudonaut.io/en/stable/state/#database-secret" target="_blank" rel="noopener">Database Secret</a></li></ul></li><li>Static Website<ul><li><a href="https://templates.cloudonaut.io/en/stable/static-website/#static-website-with-cdn" target="_blank" rel="noopener">Static website with CDN</a></li></ul></li><li>Virtual Private Cloud (VPC)<ul><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#vpc-with-private-and-public-subnets-in-two-availability-zones" target="_blank" rel="noopener">VPC with private and public subnets in two Availability Zones</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#vpc-with-private-and-public-subnets-in-three-availability-zones" target="_blank" rel="noopener">VPC with private and public subnets in three Availability Zones</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#vpc-with-private-and-public-subnets-in-four-availability-zones" target="_blank" rel="noopener">VPC with private and public subnets in four Availability Zones</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#nat-gateway" target="_blank" rel="noopener">NAT Gateway</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#nat-instance" target="_blank" rel="noopener">NAT instance</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#ssh-bastion-hostinstance" target="_blank" rel="noopener">SSH bastion host&#x2F;instance</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#vpn-bastion-hostinstance" target="_blank" rel="noopener">VPN bastion host&#x2F;instance</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#gateway-endpoint-s3" target="_blank" rel="noopener">Gateway Endpoint (S3)</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#gateway-endpoint-dynamodb" target="_blank" rel="noopener">Gateway Endpoint (DynamoDB)</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#interface-endpoint" target="_blank" rel="noopener">Interface Endpoint</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#vpc-flow-logs-to-cloudwatch-logs" target="_blank" rel="noopener">VPC Flow Logs to CloudWatch Logs</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#public-dns-zone" target="_blank" rel="noopener">Public DNS Zone</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#private-dns-zone" target="_blank" rel="noopener">Private DNS Zone</a></li><li><a href="https://templates.cloudonaut.io/en/stable/vpc/#dnssec" target="_blank" rel="noopener">DNSSEC</a></li></ul></li><li>WordPress<ul><li><a href="https://templates.cloudonaut.io/en/stable/wordpress/#wordpress-fault-tolerant-and-scalable-mysql" target="_blank" rel="noopener">WordPress: fault tolerant and scalable (MySQL)</a></li><li><a href="https://templates.cloudonaut.io/en/stable/wordpress/#wordpress-fault-tolerant-and-scalable-aurora" target="_blank" rel="noopener">WordPress: fault tolerant and scalable (Aurora)</a></li></ul></li></ul><p>All templates are published under Apache License Version 2.0. <a href="https://github.com/sponsors/widdix" target="_blank" rel="noopener">Become a sponsor</a> or contributor and support the project!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Unboxing S3 Object Lambda</title>
      <link>https://cloudonaut.io/unboxing-s3-object-lambda/</link>
      <description>
        <![CDATA[<p><a href="https://aws.amazon.com/s3/features/object-lambda/" target="_blank" rel="noopener">Amazon S3 Object Lambda</a> offers a way to ex]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <guid isPermaLink="true">https://cloudonaut.io/unboxing-s3-object-lambda/</guid>
      <pubDate>Wed, 05 May 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><a href="https://aws.amazon.com/s3/features/object-lambda/" target="_blank" rel="noopener">Amazon S3 Object Lambda</a> offers a way to execute a Lambda function when someone wants to download a file (GetObject) from an S3 bucket. You can implement whatever logic you wish and return any data as the response via the <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_WriteGetObjectResponse.html" target="_blank" rel="noopener">WriteGetObjectResponse</a> API. Keep in mind that the Lambda function must finish within 60 seconds and is called synchronously. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/05/unboxing@730w.webp 730w, /images/2021/05/unboxing@730w2x.webp 1460w, /images/2021/05/unboxing@610w.webp 610w, /images/2021/05/unboxing@610w2x.webp 1220w, /images/2021/05/unboxing@450w.webp 450w, /images/2021/05/unboxing@450w2x.webp 900w, /images/2021/05/unboxing@330w.webp 330w, /images/2021/05/unboxing@330w2x.webp 660w, /images/2021/05/unboxing@545w.webp 545w, /images/2021/05/unboxing@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/05/unboxing@730w.jpg 730w, /images/2021/05/unboxing@730w2x.jpg 1460w, /images/2021/05/unboxing@610w.jpg 610w, /images/2021/05/unboxing@610w2x.jpg 1220w, /images/2021/05/unboxing@450w.jpg 450w, /images/2021/05/unboxing@450w2x.jpg 900w, /images/2021/05/unboxing@330w.jpg 330w, /images/2021/05/unboxing@330w2x.jpg 660w, /images/2021/05/unboxing@545w.jpg 545w, /images/2021/05/unboxing@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/05/unboxing.jpg" alt="Unboxing S3 Object Lambda" title="Unboxing S3 Object Lambda"></picture></p><h2 id="How-S3-Object-Lambda-works"><a href="#How-S3-Object-Lambda-works" class="headerlink" title="How S3 Object Lambda works"></a>How S3 Object Lambda works</h2><p>The following figure shows the needed components: </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/05/s3-object-lambda@730w.webp 730w, /images/2021/05/s3-object-lambda@730w2x.webp 1460w, /images/2021/05/s3-object-lambda@610w.webp 610w, /images/2021/05/s3-object-lambda@610w2x.webp 1220w, /images/2021/05/s3-object-lambda@450w.webp 450w, /images/2021/05/s3-object-lambda@450w2x.webp 900w, /images/2021/05/s3-object-lambda@330w.webp 330w, /images/2021/05/s3-object-lambda@330w2x.webp 660w, /images/2021/05/s3-object-lambda@545w.webp 545w, /images/2021/05/s3-object-lambda@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/05/s3-object-lambda@730w.png 730w, /images/2021/05/s3-object-lambda@730w2x.png 1460w, /images/2021/05/s3-object-lambda@610w.png 610w, /images/2021/05/s3-object-lambda@610w2x.png 1220w, /images/2021/05/s3-object-lambda@450w.png 450w, /images/2021/05/s3-object-lambda@450w2x.png 900w, /images/2021/05/s3-object-lambda@330w.png 330w, /images/2021/05/s3-object-lambda@330w2x.png 660w, /images/2021/05/s3-object-lambda@545w.png 545w, /images/2021/05/s3-object-lambda@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/05/s3-object-lambda.png" alt="S3 Object Lambda" title="S3 Object Lambda"></picture></p><p>Let’s look at the parts more closely:</p><ul><li>S3 Object Lamda Access Point: References your Lambda function and your S3 Access Point. Block Public Access is always enabled.</li><li>Lambda function: Invoked when a GetObject request is made to the S3 Object Lambda Access Point.</li><li>S3 Access Point: Takes care of the non GetObject requests (such as ListObjects) and serves the pre-signed URL of the original file passed into the Lambda function.</li><li>S3 Bucket: Stores the original file (in fact, the original file could be non-existent).</li></ul><p>In the following video, I go into more details and share code with you:</p><ul><li>Introducing S3 Object Lambda</li><li>Use Cases</li><li>Demo: Generating content</li><li>Understanding Pricing</li><li>Pitfall: Keep the Lambda function running</li></ul><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=WCK57DVd8Ok">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/WCK57DVd8Ok" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="Pricing"><a href="#Pricing" class="headerlink" title="Pricing"></a>Pricing</h2><p>You pay for the following:</p><ul><li>S3 Get Object API (as usual)</li><li>Lambda invocation</li><li>Lambda GB-second</li><li>$0.005  per GB returned via WriteGetObjectResponse</li></ul><h2 id="Source-Code"><a href="#Source-Code" class="headerlink" title="Source Code"></a>Source Code</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&#x27;S3 Object Lambda Demo&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Bucket:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::S3::Bucket&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">FunctionLogGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Logs::LogGroup&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">LogGroupName:</span> <span class="type">!Sub</span> <span class="string">&#x27;/aws/lambda/$&#123;Function&#125;&#x27;</span></span><br><span class="line">      <span class="attr">RetentionInDays:</span> <span class="number">14</span></span><br><span class="line">  <span class="attr">FunctionRole:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">Service:</span> <span class="string">&#x27;lambda.amazonaws.com&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">      <span class="attr">Policies:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">&#x27;s3-object-lambda&#x27;</span></span><br><span class="line">        <span class="attr">PolicyDocument:</span></span><br><span class="line">          <span class="attr">Statement:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">            <span class="attr">Action:</span> <span class="string">&#x27;s3-object-lambda:WriteGetObjectResponse&#x27;</span></span><br><span class="line">            <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">  <span class="attr">FunctionPolicy:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Policy&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Roles:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">FunctionRole</span></span><br><span class="line">      <span class="attr">PolicyName:</span> <span class="string">lambda</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogStream&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;FunctionLogGroup.Arn&#x27;</span></span><br><span class="line">  <span class="attr">Function:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Lambda::Function&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Code:</span></span><br><span class="line">        <span class="attr">ZipFile:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          const AWS = require(&#x27;aws-sdk&#x27;);</span></span><br><span class="line"><span class="string">          const s3 = new AWS.S3(&#123;apiVersion: &#x27;2006-03-01&#x27;&#125;);</span></span><br><span class="line"><span class="string">          exports.handler = async (event) =&gt; &#123;</span></span><br><span class="line"><span class="string">              console.log(JSON.stringify(event));</span></span><br><span class="line"><span class="string">              await s3.writeGetObjectResponse(&#123;</span></span><br><span class="line"><span class="string">                RequestRoute: event.getObjectContext.outputRoute,</span></span><br><span class="line"><span class="string">                RequestToken: event.getObjectContext.outputToken,</span></span><br><span class="line"><span class="string">                Body: &#x27;hello&#x27;</span></span><br><span class="line"><span class="string">              &#125;).promise()</span></span><br><span class="line"><span class="string">              return &#123;&#125;;</span></span><br><span class="line"><span class="string">          &#125;;</span></span><br><span class="line"><span class="string"></span>      <span class="attr">Handler:</span> <span class="string">&#x27;index.handler&#x27;</span></span><br><span class="line">      <span class="attr">MemorySize:</span> <span class="number">128</span></span><br><span class="line">      <span class="attr">Role:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;FunctionRole.Arn&#x27;</span></span><br><span class="line">      <span class="attr">Runtime:</span> <span class="string">&#x27;nodejs12.x&#x27;</span></span><br><span class="line">      <span class="attr">Timeout:</span> <span class="number">60</span></span><br><span class="line">  <span class="attr">AccessPoint:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::S3::AccessPoint&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Bucket:</span> <span class="type">!Ref</span> <span class="string">Bucket</span></span><br><span class="line">  <span class="attr">LambdaAccessPoint:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::S3ObjectLambda::AccessPoint&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span> </span><br><span class="line">      <span class="attr">Name:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line">      <span class="attr">ObjectLambdaConfiguration:</span> </span><br><span class="line">        <span class="attr">SupportingAccessPoint:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:s3:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:accesspoint/$&#123;AccessPoint&#125;&#x27;</span></span><br><span class="line">        <span class="attr">TransformationConfigurations:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Actions:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">GetObject</span></span><br><span class="line">          <span class="attr">ContentTransformation:</span></span><br><span class="line">            <span class="attr">AwsLambda:</span></span><br><span class="line">              <span class="attr">FunctionArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Function.Arn&#x27;</span></span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">BucketName:</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">Bucket</span></span><br><span class="line">  <span class="attr">LambdaAccessPointArn:</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;LambdaAccessPoint.Arn&#x27;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>To invoke the S3 Object Lambda Access Point, run with your ARN: <code>aws s3api get-object --bucket arn:aws:s3-object-lambda:us-east-1:123456789123:accesspoint/s3-object-lambda --key file.txt outfile.txt</code></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How I use AWS Security Hub</title>
      <link>https://cloudonaut.io/how-i-use-aws-security-hub/</link>
      <description>
        <![CDATA[<p><a href="https://aws.amazon.com/security-hub/" target="_blank" rel="noopener">AWS Security Hub</a> provides a centralized and org-wide ov]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/securityhub/">securityhub</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-i-use-aws-security-hub/</guid>
      <pubDate>Wed, 28 Apr 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><a href="https://aws.amazon.com/security-hub/" target="_blank" rel="noopener">AWS Security Hub</a> provides a centralized and org-wide overview of how well you are doing in terms of security. Security Hub follows two strategies to collect the needed information: First, Security Hub runs checks based on security standards. Second, Security Hub integrates with other AWS services such as GuardDuty, Inspector, and many more. In this blog post, I show you how I use Security Hub to improve the security of my AWS accounts.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/security@730w.webp 730w, /images/2021/04/security@730w2x.webp 1460w, /images/2021/04/security@610w.webp 610w, /images/2021/04/security@610w2x.webp 1220w, /images/2021/04/security@450w.webp 450w, /images/2021/04/security@450w2x.webp 900w, /images/2021/04/security@330w.webp 330w, /images/2021/04/security@330w2x.webp 660w, /images/2021/04/security@545w.webp 545w, /images/2021/04/security@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/security@730w.jpg 730w, /images/2021/04/security@730w2x.jpg 1460w, /images/2021/04/security@610w.jpg 610w, /images/2021/04/security@610w2x.jpg 1220w, /images/2021/04/security@450w.jpg 450w, /images/2021/04/security@450w2x.jpg 900w, /images/2021/04/security@330w.jpg 330w, /images/2021/04/security@330w2x.jpg 660w, /images/2021/04/security@545w.jpg 545w, /images/2021/04/security@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/security.jpg" alt="How I use AWS Security Hub" title="How I use AWS Security Hub"></picture></p><p>To get started with Security Hub, I recommend enabling at least one security standard.</p><h2 id="Complying-with-security-standards"><a href="#Complying-with-security-standards" class="headerlink" title="Complying with security standards"></a>Complying with security standards</h2><p>I prefer the <a href="https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-fsbp.html" target="_blank" rel="noopener">AWS Foundational Security Best Practices security standard</a>. It covers many areas and services. I recommend starting with a single security standard to not overwhelm yourself with findings. Once you reach a score in the heights of 80%, think twice if you really need to enable another security standard or not.</p><h2 id="Dealing-with-false-positives"><a href="#Dealing-with-false-positives" class="headerlink" title="Dealing with false positives"></a>Dealing with false positives</h2><p>Any security tool will generate false positives. Security Hub provides two ways to deal with false positives.</p><p>You can <strong>disable a control</strong> entirely. I usually disable <a href="https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-iam-6" target="_blank" rel="noopener">[IAM.6] Hardware MFA should be enabled for the root user</a> because it doesn’t make much sense in an AWS Organizations setup. Learn more about <a href="https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-enable-disable-controls.html" target="_blank" rel="noopener">disabling a control</a>.</p><p>You can <strong>suppress a finding</strong>. Sometimes, you need an S3 bucket to be public. The control ][S3.2] S3 buckets should prohibit public read access](<a href="https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-s3-2" target="_blank" rel="noopener">https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-s3-2</a>) will flag that bucket as insecure. You can whitelist the finding by setting the <a href="https://docs.aws.amazon.com/securityhub/latest/userguide/finding-workflow-status.html" target="_blank" rel="noopener">workflow status</a> to suppressed.</p><h2 id="How-I-use-Security-Hub"><a href="#How-I-use-Security-Hub" class="headerlink" title="How I use Security Hub"></a>How I use Security Hub</h2><p>tag<br>In the following video, I demo how I use Security Hub. I also share two pitfalls with you:</p><ul><li>Security Score in orgs</li><li>IAM Unknown error</li></ul><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=BNH7b3YBmWM">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/BNH7b3YBmWM" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="Integration-with-other-AWS-services"><a href="#Integration-with-other-AWS-services" class="headerlink" title="Integration with other AWS services"></a>Integration with other AWS services</h2><p>AWS Security Hub integrates with a bunch of other AWS services. You can forward all the findings from those services to Security Hub for a centralized view.</p><p>The following services are supported:</p><ul><li>Amazon Inspector</li><li>Amazon GuardDuty</li><li>IAM Access Analyzer</li><li>AWS Systems Manager Patch Manager</li><li>AWS Firewall Manager</li><li>Amazon Macie</li></ul><p>I recommend enabling integrations one after another. Nothing is more frustrating than a million findings that no one resolves! Think twice before using one of those services. They only provide value if you have the capacity to resolve the findings.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>
        <![CDATA[AWS Architecture Pattern for Scheduled & Serverless Batch Processing]]>
      </title>
      <link>https://cloudonaut.io/aws-architecture-pattern-for-scheduled-serverless-batch-processing/</link>
      <description>
        <![CDATA[<p>Scheduled batch jobs are the heart of many business processes implemented by enterprise applications. Reports are generated daily, databa]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <category domain="https://cloudonaut.io/tag/batch/">batch</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-architecture-pattern-for-scheduled-serverless-batch-processing/</guid>
      <pubDate>Mon, 26 Apr 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Scheduled batch jobs are the heart of many business processes implemented by enterprise applications. Reports are generated daily, databases are optimized over the weekend, and business logic is executed nightly. The importance of batch jobs satisfies an investment into a reliable architecture to execute the jobs. </p><p>Let’s look at how batch jobs are mostly executed and what options you have to run them on AWS in a more robust way.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/batch@730w.webp 730w, /images/2021/04/batch@730w2x.webp 1460w, /images/2021/04/batch@610w.webp 610w, /images/2021/04/batch@610w2x.webp 1220w, /images/2021/04/batch@450w.webp 450w, /images/2021/04/batch@450w2x.webp 900w, /images/2021/04/batch@330w.webp 330w, /images/2021/04/batch@330w2x.webp 660w, /images/2021/04/batch@545w.webp 545w, /images/2021/04/batch@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/batch@730w.jpg 730w, /images/2021/04/batch@730w2x.jpg 1460w, /images/2021/04/batch@610w.jpg 610w, /images/2021/04/batch@610w2x.jpg 1220w, /images/2021/04/batch@450w.jpg 450w, /images/2021/04/batch@450w2x.jpg 900w, /images/2021/04/batch@330w.jpg 330w, /images/2021/04/batch@330w2x.jpg 660w, /images/2021/04/batch@545w.jpg 545w, /images/2021/04/batch@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/batch.jpg" alt="AWS Architecture Pattern for Scheduled &amp; Serverless Batch Processing" title="AWS Architecture Pattern for Scheduled &amp; Serverless Batch Processing"></picture></p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><p>One of the most straightforward implementations of a scheduled batch job is a Linux cronjob that executes a script. Though straightforward this implementation comes with many pitfalls:</p><ul><li>How can you monitor the successful execution?</li><li>What if the machine fails and the cronjob is not executed at all?</li><li>Can we keep a timeout and stop the job if it takes too long?</li><li>The machine is not powerful enough; how can we scale the job execution?</li></ul><p>Luckily, AWS provides us with all the building blocks needed to take care of all the pitfalls. In good old AWS fashion, we have to wire up the best building blocks to achieve our goal. </p><p>In this post, I’ll show you a reference architecture to run scheduled and serverless batch jobs on AWS. By scheduled, I mean jobs that run at specific time intervals. By serverless, we’re referring to an operation mode where the environment is fully managed by AWS, scales on-demand, and we only pay for what we use (never for idle).</p><h2 id="High-level-architecture"><a href="#High-level-architecture" class="headerlink" title="High-level architecture"></a>High-level architecture</h2><p>Two AWS services are at the heart of the architecture. Amazon EventBridge and AWS Batch. You can think of Amazon <a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-schedule-expressions.html" target="_blank" rel="noopener">EventBridge as the cron daemon</a> that triggers on a schedule while <a href="https://docs.aws.amazon.com/batch/latest/userguide/compute_environments.html" target="_blank" rel="noopener">AWS Batch provides the execution environment</a> for your batch job. The high-level architecture follows:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/aws-batch-architecture@730w.webp 730w, /images/2021/04/aws-batch-architecture@730w2x.webp 1460w, /images/2021/04/aws-batch-architecture@610w.webp 610w, /images/2021/04/aws-batch-architecture@610w2x.webp 1220w, /images/2021/04/aws-batch-architecture@450w.webp 450w, /images/2021/04/aws-batch-architecture@450w2x.webp 900w, /images/2021/04/aws-batch-architecture@330w.webp 330w, /images/2021/04/aws-batch-architecture@330w2x.webp 660w, /images/2021/04/aws-batch-architecture@545w.webp 545w, /images/2021/04/aws-batch-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/aws-batch-architecture@730w.png 730w, /images/2021/04/aws-batch-architecture@730w2x.png 1460w, /images/2021/04/aws-batch-architecture@610w.png 610w, /images/2021/04/aws-batch-architecture@610w2x.png 1220w, /images/2021/04/aws-batch-architecture@450w.png 450w, /images/2021/04/aws-batch-architecture@450w2x.png 900w, /images/2021/04/aws-batch-architecture@330w.png 330w, /images/2021/04/aws-batch-architecture@330w2x.png 660w, /images/2021/04/aws-batch-architecture@545w.png 545w, /images/2021/04/aws-batch-architecture@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/aws-batch-architecture.png" alt="AWS Batch architecture" title="AWS Batch architecture"></picture></p><p>AWS Batch relies on Docker and AWS ECS. The ECS part is abstracted away, but it might be handy to know the internals if you are debugging an issue.</p><p>Batch can be used in different flavors:</p><ul><li>Unmanaged &amp; EC2 based: You run the ECS cluster, the EC2 instances, and ensure they connect with AWS Batch properly. You are also responsible for scaling the system.</li><li>Managed &amp; EC2 based (on-demand or spot): AWS takes care of the scaling.</li><li>Managed &amp; Fargate based (on-demand or spot): AWS takes care of everything.</li></ul><p>For minimal management overhead, I recommend going with a managed option, specifically Fargate. If you use Fargate, you benefit from a Serverless experience. There is nothing to operate, nothing to patch, nothing to provision and nothing to pay for if no jobs are running.</p><p>The managed environments are either powered by on-demand or spot capacity. Spot capacity is significantly cheaper, but you always risk that your workload is terminated before it completes. You can safely use spot environments if your jobs can deal with being interrupted in the middle of the run (which they should be anyway). If a workload is interrupted because of a capacity shortage on the spot market, AWS Batch can restart the job later.</p><p>A scheduled EventBridge (formerly known as CloudWatch Events) rule can trigger AWS Batch in a <a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html" target="_blank" rel="noopener">cron like fashion</a>. You can either configure a cron expression such as <code>cron(10 * * ? *)</code>, or a rate expression such as <code>rate(12 hours)</code>.</p><p><strong>Pro-tip</strong>: Keep in mind that EventBridge comes with a minute granularity. You can not control the exact second a trigger fires.</p><h2 id="Batch-Jobs"><a href="#Batch-Jobs" class="headerlink" title="Batch Jobs"></a>Batch Jobs</h2><p>An AWS batch job runs inside a Linux Docker container. This comes with great flexibility on your end, allowing you to choose whatever programming language you wish to implement the job. </p><p>Existing cronjobs that run on Linux can run in a container as well. This provides an excellent migration path from cronjobs to AWS Batch. If a job execution fails, AWS Batch will retry the execution. You have complete control over how many retries should be performed. This can be very handy if your job fails because of unavailable downstream dependencies. </p><p>AWS Batch also keeps track of a timeout for each execution. If the execution takes too long, AWS Batch will terminate the container and try again. And of course, AWS Batch provides CloudWatch Metrics and emits events that you can use to monitor the health of your jobs.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/aws-batch-job-isolation@730w.webp 730w, /images/2021/04/aws-batch-job-isolation@730w2x.webp 1460w, /images/2021/04/aws-batch-job-isolation@610w.webp 610w, /images/2021/04/aws-batch-job-isolation@610w2x.webp 1220w, /images/2021/04/aws-batch-job-isolation@450w.webp 450w, /images/2021/04/aws-batch-job-isolation@450w2x.webp 900w, /images/2021/04/aws-batch-job-isolation@330w.webp 330w, /images/2021/04/aws-batch-job-isolation@330w2x.webp 660w, /images/2021/04/aws-batch-job-isolation@545w.webp 545w, /images/2021/04/aws-batch-job-isolation@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/aws-batch-job-isolation@730w.png 730w, /images/2021/04/aws-batch-job-isolation@730w2x.png 1460w, /images/2021/04/aws-batch-job-isolation@610w.png 610w, /images/2021/04/aws-batch-job-isolation@610w2x.png 1220w, /images/2021/04/aws-batch-job-isolation@450w.png 450w, /images/2021/04/aws-batch-job-isolation@450w2x.png 900w, /images/2021/04/aws-batch-job-isolation@330w.png 330w, /images/2021/04/aws-batch-job-isolation@330w2x.png 660w, /images/2021/04/aws-batch-job-isolation@545w.png 545w, /images/2021/04/aws-batch-job-isolation@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/aws-batch-job-isolation.png" alt="AWS Batch job isolation" title="AWS Batch job isolation"></picture></p><p>Workload isolation is achieved by a separate IAM role and security group (firewall) for each job. This helps you to follow the least privilege principle: Only allow the job to call the AWS APIs that it needs and only allow the outbound network connectivity that is needed.</p><h2 id="Job-queues-and-compute-environments"><a href="#Job-queues-and-compute-environments" class="headerlink" title="Job queues and compute environments"></a>Job queues and compute environments</h2><p>You can submit a job to AWS Batch by specifying a job queue. You can assign priorities to job queues to ensure that important jobs are taken care of first. A job queue is also linked to one or many prioritized compute environments. For example, you could first try to run a job on a spot-based compute environment to reduce costs. If no spot capacity is available, you can fall back to on-demand. This is very handy to keep costs as low as possible without sacrificing availability.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/aws-batch-job-queues@730w.webp 730w, /images/2021/04/aws-batch-job-queues@730w2x.webp 1460w, /images/2021/04/aws-batch-job-queues@610w.webp 610w, /images/2021/04/aws-batch-job-queues@610w2x.webp 1220w, /images/2021/04/aws-batch-job-queues@450w.webp 450w, /images/2021/04/aws-batch-job-queues@450w2x.webp 900w, /images/2021/04/aws-batch-job-queues@330w.webp 330w, /images/2021/04/aws-batch-job-queues@330w2x.webp 660w, /images/2021/04/aws-batch-job-queues@545w.webp 545w, /images/2021/04/aws-batch-job-queues@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/aws-batch-job-queues@730w.png 730w, /images/2021/04/aws-batch-job-queues@730w2x.png 1460w, /images/2021/04/aws-batch-job-queues@610w.png 610w, /images/2021/04/aws-batch-job-queues@610w2x.png 1220w, /images/2021/04/aws-batch-job-queues@450w.png 450w, /images/2021/04/aws-batch-job-queues@450w2x.png 900w, /images/2021/04/aws-batch-job-queues@330w.png 330w, /images/2021/04/aws-batch-job-queues@330w2x.png 660w, /images/2021/04/aws-batch-job-queues@545w.png 545w, /images/2021/04/aws-batch-job-queues@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/aws-batch-job-queues.png" alt="AWS Batch job queues and compute environments" title="AWS Batch job queues and compute environments"></picture></p><p>Another handy AWS batch feature is job dependencies. You can specify that specific jobs have to finish before another job can run. For example, you could first run your database optimization before you generate a weekly sales report.</p><h2 id="Alternatives"><a href="#Alternatives" class="headerlink" title="Alternatives"></a>Alternatives</h2><p>Are you looking for a simpler solution? If you don’t need priorities, dependencies, retries, and your jobs always finish within 15 minutes, you can replace AWS Batch with AWS Lambda. Some orchestration can be achieved with Step Functions as well.</p><p><strong>Pro-tip</strong>: The proposed solution works well for a reasonable number (thousands) of triggers. The solution likely does not work well if users can create their own cronjobs in the system.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>The bottom line is that AWS Batch executes batch jobs in a controlled manner. Compute environments can be powered by Fargate for a serverless experience and job queues can be used to prioritize work under heavy load situations. The cron like trigger is provided by EventBridge and integrates natively with AWS Batch. </p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Real-world CodePipeline CI/CD examples</title>
      <link>https://cloudonaut.io/real-world-codepipeline-ci-cd-examples/</link>
      <description>
        <![CDATA[<p><a href="https://aws.amazon.com/codepipeline/" target="_blank" rel="noopener">AWS CodePipeline</a> helps us to orchestrate CI&#x2F;CD pip]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/codepipeline/">codepipeline</category>
      <guid isPermaLink="true">https://cloudonaut.io/real-world-codepipeline-ci-cd-examples/</guid>
      <pubDate>Wed, 21 Apr 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><a href="https://aws.amazon.com/codepipeline/" target="_blank" rel="noopener">AWS CodePipeline</a> helps us to orchestrate CI&#x2F;CD pipelines. To implement real-world pipelines, CodePipeline calls additional AWS services to do the work. E.g., CodeBuild to run arbitrary scripts, CloudFormation to create or update stacks, or CodeDeploy to deploy software to running EC2 instances. A pipeline is connected to sources such as a git repository (Bitbucket, GitHub, and GitHub Enterprise Server, CodeCommit), an S3 bucket, or an ECR repository. If a source changes, the pipeline executes.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/pipeline@730w.webp 730w, /images/2021/04/pipeline@730w2x.webp 1460w, /images/2021/04/pipeline@610w.webp 610w, /images/2021/04/pipeline@610w2x.webp 1220w, /images/2021/04/pipeline@450w.webp 450w, /images/2021/04/pipeline@450w2x.webp 900w, /images/2021/04/pipeline@330w.webp 330w, /images/2021/04/pipeline@330w2x.webp 660w, /images/2021/04/pipeline@545w.webp 545w, /images/2021/04/pipeline@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/pipeline@730w.jpg 730w, /images/2021/04/pipeline@730w2x.jpg 1460w, /images/2021/04/pipeline@610w.jpg 610w, /images/2021/04/pipeline@610w2x.jpg 1220w, /images/2021/04/pipeline@450w.jpg 450w, /images/2021/04/pipeline@450w2x.jpg 900w, /images/2021/04/pipeline@330w.jpg 330w, /images/2021/04/pipeline@330w2x.jpg 660w, /images/2021/04/pipeline@545w.jpg 545w, /images/2021/04/pipeline@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/pipeline.jpg" alt="Real-world CodePipeline CI/CD examples" title="Real-world CodePipeline CI/CD examples"></picture></p><p>But how do real-world pipelines look like? In this post, I share pipelines with you, from simple to complex. </p><h2 id="Simple-but-bold-pipeline"><a href="#Simple-but-bold-pipeline" class="headerlink" title="Simple but bold pipeline"></a>Simple but bold pipeline</h2><p>The following pipeline connects to a GitHub repository. CodeBuild is used to build a Docker image, and CodeBuild is used to push the Docker image and deploy it into production.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/codepipeline-simple@730w.webp 730w, /images/2021/04/codepipeline-simple@730w2x.webp 1460w, /images/2021/04/codepipeline-simple@610w.webp 610w, /images/2021/04/codepipeline-simple@610w2x.webp 1220w, /images/2021/04/codepipeline-simple@450w.webp 450w, /images/2021/04/codepipeline-simple@450w2x.webp 900w, /images/2021/04/codepipeline-simple@330w.webp 330w, /images/2021/04/codepipeline-simple@330w2x.webp 660w, /images/2021/04/codepipeline-simple@545w.webp 545w, /images/2021/04/codepipeline-simple@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/codepipeline-simple@730w.png 730w, /images/2021/04/codepipeline-simple@730w2x.png 1460w, /images/2021/04/codepipeline-simple@610w.png 610w, /images/2021/04/codepipeline-simple@610w2x.png 1220w, /images/2021/04/codepipeline-simple@450w.png 450w, /images/2021/04/codepipeline-simple@450w2x.png 900w, /images/2021/04/codepipeline-simple@330w.png 330w, /images/2021/04/codepipeline-simple@330w2x.png 660w, /images/2021/04/codepipeline-simple@545w.png 545w, /images/2021/04/codepipeline-simple@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/codepipeline-simple.png" alt="Simple but bold pipeline" title="Simple but bold pipeline"></picture></p><p>This pipeline comes with several disadvantages:</p><ol><li>It runs no unit tests</li><li>It runs no acceptance tests in a pre-prod environment</li></ol><h2 id="Manual-acceptance-testing"><a href="#Manual-acceptance-testing" class="headerlink" title="Manual acceptance testing"></a>Manual acceptance testing</h2><p>The following pipeline excerpt shows a pipeline where manual acceptance tests prevent bugs from reaching production. Not perfect, but better than before!</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/codepipeline-pre-prod@730w.webp 730w, /images/2021/04/codepipeline-pre-prod@730w2x.webp 1460w, /images/2021/04/codepipeline-pre-prod@610w.webp 610w, /images/2021/04/codepipeline-pre-prod@610w2x.webp 1220w, /images/2021/04/codepipeline-pre-prod@450w.webp 450w, /images/2021/04/codepipeline-pre-prod@450w2x.webp 900w, /images/2021/04/codepipeline-pre-prod@330w.webp 330w, /images/2021/04/codepipeline-pre-prod@330w2x.webp 660w, /images/2021/04/codepipeline-pre-prod@545w.webp 545w, /images/2021/04/codepipeline-pre-prod@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/codepipeline-pre-prod@730w.png 730w, /images/2021/04/codepipeline-pre-prod@730w2x.png 1460w, /images/2021/04/codepipeline-pre-prod@610w.png 610w, /images/2021/04/codepipeline-pre-prod@610w2x.png 1220w, /images/2021/04/codepipeline-pre-prod@450w.png 450w, /images/2021/04/codepipeline-pre-prod@450w2x.png 900w, /images/2021/04/codepipeline-pre-prod@330w.png 330w, /images/2021/04/codepipeline-pre-prod@330w2x.png 660w, /images/2021/04/codepipeline-pre-prod@545w.png 545w, /images/2021/04/codepipeline-pre-prod@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/codepipeline-pre-prod.png" alt="Manual acceptance testing" title="Manual acceptance testing"></picture></p><p>We could still do better by running automated acceptance tests, right?</p><p>The following video goes into details and includes demos:</p><ul><li>CodePipeline 101</li><li>Real-world CodePipeline examples</li><li>Demo: marbot pipeline</li><li>Pitfall: Update pipeline in pipeline</li></ul><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=MNt2HGxClZ0">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/MNt2HGxClZ0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="Phased-rollout"><a href="#Phased-rollout" class="headerlink" title="Phased rollout"></a>Phased rollout</h2><p>My last example shows a pipeline excerpt that <a href="https://x.com/TysonTrautmann/status/1235609941408284674" target="_blank" rel="noopener">AWS uses to deploy parts of CodePipeline itself</a>. You can see how new changes are rolled out in phases—starting with a single region to lower the risk.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/codepipeline-phased@730w.webp 730w, /images/2021/04/codepipeline-phased@730w2x.webp 1460w, /images/2021/04/codepipeline-phased@610w.webp 610w, /images/2021/04/codepipeline-phased@610w2x.webp 1220w, /images/2021/04/codepipeline-phased@450w.webp 450w, /images/2021/04/codepipeline-phased@450w2x.webp 900w, /images/2021/04/codepipeline-phased@330w.webp 330w, /images/2021/04/codepipeline-phased@330w2x.webp 660w, /images/2021/04/codepipeline-phased@545w.webp 545w, /images/2021/04/codepipeline-phased@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/codepipeline-phased@730w.png 730w, /images/2021/04/codepipeline-phased@730w2x.png 1460w, /images/2021/04/codepipeline-phased@610w.png 610w, /images/2021/04/codepipeline-phased@610w2x.png 1220w, /images/2021/04/codepipeline-phased@450w.png 450w, /images/2021/04/codepipeline-phased@450w2x.png 900w, /images/2021/04/codepipeline-phased@330w.png 330w, /images/2021/04/codepipeline-phased@330w2x.png 660w, /images/2021/04/codepipeline-phased@545w.png 545w, /images/2021/04/codepipeline-phased@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/codepipeline-phased.png" alt="hased rollout" title="hased rollout"></picture></p><p>I used a similar approach in a pipeline to deploy an environment per customer (the application was missing multi-tenant capabilities).</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>CodePipeline is the pipeline orchestrator. CodePipeline calls <a href="https://docs.aws.amazon.com/codepipeline/latest/userguide/integrations-action-type.html" target="_blank" rel="noopener">other AWS services</a> to implement a CI&#x2F;CD pipeline.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Serving content only to logged-in users with CloudFront Signed Cookies</title>
      <link>https://cloudonaut.io/serving-content-only-to-logged-in-users-with-cloudfront-signed-cookies/</link>
      <description>
        <![CDATA[<p>This blog can be accessed by anyone with access to the free Internet. It’s a public website. But many websites offer a members-only area.]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/cloudfront/">cloudfront</category>
      <category domain="https://cloudonaut.io/tag/lambda-at-edge/">lambda-at-edge</category>
      <guid isPermaLink="true">https://cloudonaut.io/serving-content-only-to-logged-in-users-with-cloudfront-signed-cookies/</guid>
      <pubDate>Mon, 19 Apr 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>This blog can be accessed by anyone with access to the free Internet. It’s a public website. But many websites offer a members-only area. You have to log in to get access to parts of the website. In this blog post, I demonstrate how CloudFront can be used to protect parts of your website from the public.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/private@730w.webp 730w, /images/2021/04/private@730w2x.webp 1460w, /images/2021/04/private@610w.webp 610w, /images/2021/04/private@610w2x.webp 1220w, /images/2021/04/private@450w.webp 450w, /images/2021/04/private@450w2x.webp 900w, /images/2021/04/private@330w.webp 330w, /images/2021/04/private@330w2x.webp 660w, /images/2021/04/private@545w.webp 545w, /images/2021/04/private@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/private@730w.jpg 730w, /images/2021/04/private@730w2x.jpg 1460w, /images/2021/04/private@610w.jpg 610w, /images/2021/04/private@610w2x.jpg 1220w, /images/2021/04/private@450w.jpg 450w, /images/2021/04/private@450w2x.jpg 900w, /images/2021/04/private@330w.jpg 330w, /images/2021/04/private@330w2x.jpg 660w, /images/2021/04/private@545w.jpg 545w, /images/2021/04/private@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/private.jpg" alt="Serving content only to logged-in users with CloudFront Signed Cookies" title="Serving content only to logged-in users with CloudFront Signed Cookies"></picture></p><p>To serve content only to logged-in users with CloudFront, we have to wire three pieces together:</p><ol><li>A private &amp; public key pair.</li><li>A signed cookie protected CloudFront origin that uses the public key to verify signed cookies.</li><li>A component that generates and returns signed cookies with the private key. We use Lambda@Edge here because CloudFront can trigger it directly.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/cloudfront-signed-cookies@730w.webp 730w, /images/2021/04/cloudfront-signed-cookies@730w2x.webp 1460w, /images/2021/04/cloudfront-signed-cookies@610w.webp 610w, /images/2021/04/cloudfront-signed-cookies@610w2x.webp 1220w, /images/2021/04/cloudfront-signed-cookies@450w.webp 450w, /images/2021/04/cloudfront-signed-cookies@450w2x.webp 900w, /images/2021/04/cloudfront-signed-cookies@330w.webp 330w, /images/2021/04/cloudfront-signed-cookies@330w2x.webp 660w, /images/2021/04/cloudfront-signed-cookies@545w.webp 545w, /images/2021/04/cloudfront-signed-cookies@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/cloudfront-signed-cookies@730w.png 730w, /images/2021/04/cloudfront-signed-cookies@730w2x.png 1460w, /images/2021/04/cloudfront-signed-cookies@610w.png 610w, /images/2021/04/cloudfront-signed-cookies@610w2x.png 1220w, /images/2021/04/cloudfront-signed-cookies@450w.png 450w, /images/2021/04/cloudfront-signed-cookies@450w2x.png 900w, /images/2021/04/cloudfront-signed-cookies@330w.png 330w, /images/2021/04/cloudfront-signed-cookies@330w2x.png 660w, /images/2021/04/cloudfront-signed-cookies@545w.png 545w, /images/2021/04/cloudfront-signed-cookies@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/cloudfront-signed-cookies.png" alt="CloudFront Signed Cookies architecture" title="CloudFront Signed Cookies architecture"></picture></p><p>I wired up the pieces using CloudFormation. Let’s have a look at the template step by step.</p><p><strong>First</strong>, We define the S3 bucket that we use as origins for our CloudFront distribution. We use a single bucket here and separate the public and private files into folders. You could also use two S3 buckets if you wish, or you could use other origin types such as load balancers or external HTTP endpoints.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&#x27;CloudFront Signed Cookies demo by cloudonaut.io&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Bucket:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::S3::Bucket&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">BucketPolicy:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::S3::BucketPolicy&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Bucket:</span> <span class="type">!Ref</span> <span class="string">Bucket</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Action:</span> <span class="string">&#x27;s3:GetObject&#x27;</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;Bucket.Arn&#125;/*&#x27;</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">CanonicalUser:</span> <span class="type">!GetAtt</span> <span class="string">CloudFrontOriginAccessIdentity.S3CanonicalUserId</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Action:</span> <span class="string">&#x27;s3:ListBucket&#x27;</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Bucket.Arn&#x27;</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">CanonicalUser:</span> <span class="type">!GetAtt</span> <span class="string">CloudFrontOriginAccessIdentity.S3CanonicalUserId</span></span><br><span class="line">  <span class="attr">CloudFrontOriginAccessIdentity:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFront::CloudFrontOriginAccessIdentity&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">CloudFrontOriginAccessIdentityConfig:</span></span><br><span class="line">        <span class="attr">Comment:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br></pre></td></tr></table></figure><p><strong>Second</strong>, we create the public key that CloudFront uses to verify the signature in the cookies. To generate a private&#x2F;Public key pair, run the following commands:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">openssl genrsa -out private_key.pem 2048</span><br><span class="line">openssl rsa -pubout -<span class="keyword">in</span> private_key.pem -out public_key.pem</span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">CloudFrontPublicKey1:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFront::PublicKey&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">PublicKeyConfig:</span></span><br><span class="line">      <span class="attr">CallerReference:</span> <span class="string">&#x27;key1&#x27;</span></span><br><span class="line">      <span class="attr">EncodedKey:</span> <span class="string">|</span></span><br><span class="line"><span class="string">        -----BEGIN PUBLIC KEY-----</span></span><br><span class="line"><span class="string">        REPLACE THIS WITH THE CONTENTS OF public_key.pem</span></span><br><span class="line"><span class="string">        -----END PUBLIC KEY-----</span></span><br><span class="line"><span class="string"></span>      <span class="attr">Name:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-1&#x27;</span></span><br><span class="line"><span class="attr">CloudFrontKeyGroup:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFront::KeyGroup&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">KeyGroupConfig:</span></span><br><span class="line">      <span class="attr">Items:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">CloudFrontPublicKey1</span></span><br><span class="line">      <span class="attr">Name:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br></pre></td></tr></table></figure><blockquote><p>Managing key rotation in CloudFormation is possible but cumbersome. To add a new public key, duplicate the <code>CloudFrontPublicKey1</code> resource and reference the new resource in the key group. Please don’t delete the old public key while it is in use. The expiry of your cookies (1 day in this example) defines the minimum wait duration.</p></blockquote><p><strong>Third</strong>, it’s time to implement the Lambda@Edge function that generates the signed cookies.</p><p>You might want to adjust the following:</p><ul><li><code>MAX_AGE_IN_SECONDS</code>: Expiry of the signed cookie (both the cookie and the content expires)</li><li>Private key (from <code>private_key.pem</code>)</li><li>The Lambda function checks if the <code>Authorization</code> header contains the value <em>Bearer secret</em>. You likely want to replace this with something different to authenticate the user, e.g., by verifying a JWT.</li></ul><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">LambdaEdgeFunctionRole:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">Service:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;lambda.amazonaws.com&#x27;</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;edgelambda.amazonaws.com&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">  <span class="attr">LambdaEdgeFunctionPolicy:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Policy&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogStream&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;LambdaEdgeFunctionLogGroup.Arn&#x27;</span></span><br><span class="line">      <span class="attr">PolicyName:</span> <span class="string">lambda</span></span><br><span class="line">      <span class="attr">Roles:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">LambdaEdgeFunctionRole</span></span><br><span class="line">  <span class="attr">LambdaEdgeFunctionEdgePolicy:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Policy&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;logs:CreateLogGroup&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:logs:*:$&#123;AWS::AccountId&#125;:log-group:/aws/lambda/us-east-1.$&#123;LambdaEdgeFunction&#125;:log-stream:&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogStream&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:logs:*:$&#123;AWS::AccountId&#125;:log-group:/aws/lambda/us-east-1.$&#123;LambdaEdgeFunction&#125;:log-stream:*&#x27;</span></span><br><span class="line">      <span class="attr">PolicyName:</span> <span class="string">&#x27;lambda-edge&#x27;</span></span><br><span class="line">      <span class="attr">Roles:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">LambdaEdgeFunctionRole</span></span><br><span class="line">  <span class="attr">LambdaEdgeFunction:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Lambda::Function&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Code:</span></span><br><span class="line">        <span class="comment"># If you change the ZipFile, rename the logical id LambdaEdgeFunctionVersionVX to trigger a new version creation!</span></span><br><span class="line">        <span class="attr">ZipFile:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">          const MAX_AGE_IN_SECONDS = 86400; // 1 day</span></span><br><span class="line"><span class="string">          const AWS = require(&#x27;aws-sdk&#x27;);</span></span><br><span class="line"><span class="string">          const signer = new AWS.CloudFront.Signer(</span></span><br><span class="line"><span class="string">            &#x27;$&#123;CloudFrontPublicKey1&#125;&#x27;,</span></span><br><span class="line"><span class="string">            `-----BEGIN RSA PRIVATE KEY-----</span></span><br><span class="line"><span class="string">          REPLACE THIS WITH THE CONTENTS OF private_key.pem</span></span><br><span class="line"><span class="string">          -----END RSA PRIVATE KEY-----`</span></span><br><span class="line"><span class="string">          );</span></span><br><span class="line"><span class="string">          exports.handler = async function(event) &#123;</span></span><br><span class="line"><span class="string">            if (</span></span><br><span class="line"><span class="string">              &#x27;authorization&#x27; in event.Records[0].cf.request.headers &amp;&amp;</span></span><br><span class="line"><span class="string">              event.Records[0].cf.request.headers.authorization.length === 1 &amp;&amp;</span></span><br><span class="line"><span class="string">              event.Records[0].cf.request.headers.authorization[0].value === &#x27;Bearer secret&#x27;</span></span><br><span class="line"><span class="string">            ) &#123;</span></span><br><span class="line"><span class="string">              const cookies = signer.getSignedCookie(&#123;</span></span><br><span class="line"><span class="string">                policy: JSON.stringify(&#123;</span></span><br><span class="line"><span class="string">                  Statement: [&#123;</span></span><br><span class="line"><span class="string">                    Resource: `https://$&#123;!event.Records[0].cf.config.distributionDomainName&#125;/private/*`,</span></span><br><span class="line"><span class="string">                    Condition: &#123;</span></span><br><span class="line"><span class="string">                      DateLessThan: &#123;</span></span><br><span class="line"><span class="string">                        &#x27;AWS:EpochTime&#x27;: Math.round(Date.now() / 1000) + MAX_AGE_IN_SECONDS</span></span><br><span class="line"><span class="string">                      &#125;</span></span><br><span class="line"><span class="string">                    &#125;</span></span><br><span class="line"><span class="string">                  &#125;]</span></span><br><span class="line"><span class="string">                &#125;)</span></span><br><span class="line"><span class="string">              &#125;);</span></span><br><span class="line"><span class="string">              const setCookieHeader = Object.keys(cookies).map(key =&gt; (&#123;</span></span><br><span class="line"><span class="string">                key: &#x27;Set-Cookie&#x27;,</span></span><br><span class="line"><span class="string">                value: `$&#123;!key&#125;=$&#123;!cookies[key]&#125;; Path=/; Max-Age=$&#123;!MAX_AGE_IN_SECONDS&#125;; Secure`</span></span><br><span class="line"><span class="string">              &#125;));</span></span><br><span class="line"><span class="string">              return &#123;</span></span><br><span class="line"><span class="string">                headers: &#123;</span></span><br><span class="line"><span class="string">                  &#x27;location&#x27;: [&#123;</span></span><br><span class="line"><span class="string">                    key: &#x27;Location&#x27;,</span></span><br><span class="line"><span class="string">                    value: &#x27;/private/index.html&#x27;</span></span><br><span class="line"><span class="string">                  &#125;],</span></span><br><span class="line"><span class="string">                  &#x27;set-cookie&#x27;: setCookieHeader,</span></span><br><span class="line"><span class="string">                  &#x27;cache-control&#x27;: [&#123;</span></span><br><span class="line"><span class="string">                    key: &#x27;Cache-Control&#x27;,</span></span><br><span class="line"><span class="string">                    value: &#x27;no-cache&#x27;</span></span><br><span class="line"><span class="string">                  &#125;]</span></span><br><span class="line"><span class="string">                &#125;,</span></span><br><span class="line"><span class="string">                status: &#x27;307&#x27;,</span></span><br><span class="line"><span class="string">                statusDescription: &#x27;Temporary Redirect&#x27;</span></span><br><span class="line"><span class="string">              &#125;;</span></span><br><span class="line"><span class="string">            &#125; else &#123;</span></span><br><span class="line"><span class="string">              return &#123;</span></span><br><span class="line"><span class="string">                body: &#x27;missing or invalid authorization header&#x27;,</span></span><br><span class="line"><span class="string">                bodyEncoding: &#x27;text&#x27;,</span></span><br><span class="line"><span class="string">                status: &#x27;403&#x27;,</span></span><br><span class="line"><span class="string">                statusDescription: &#x27;Forbidden&#x27;</span></span><br><span class="line"><span class="string">              &#125;;</span></span><br><span class="line"><span class="string">            &#125;</span></span><br><span class="line"><span class="string">          &#125;;</span></span><br><span class="line"><span class="string"></span>      <span class="attr">Handler:</span> <span class="string">&#x27;index.handler&#x27;</span></span><br><span class="line">      <span class="attr">MemorySize:</span> <span class="number">128</span></span><br><span class="line">      <span class="attr">Role:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;LambdaEdgeFunctionRole.Arn&#x27;</span></span><br><span class="line">      <span class="attr">Runtime:</span> <span class="string">&#x27;nodejs12.x&#x27;</span></span><br><span class="line">      <span class="attr">Timeout:</span> <span class="number">5</span></span><br><span class="line">  <span class="attr">LambdaEdgeFunctionVersionV1:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Lambda::Version&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">FunctionName:</span> <span class="type">!Ref</span> <span class="string">LambdaEdgeFunction</span></span><br><span class="line">  <span class="attr">LambdaEdgeFunctionLogGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Logs::LogGroup&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">LogGroupName:</span> <span class="type">!Sub</span> <span class="string">&#x27;/aws/lambda/$&#123;LambdaEdgeFunction&#125;&#x27;</span></span><br><span class="line">      <span class="attr">RetentionInDays:</span> <span class="number">14</span></span><br></pre></td></tr></table></figure><blockquote><p>We use a <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html" target="_blank" rel="noopener">custom policy</a> here and <strong>not</strong> a <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-canned-policy.html" target="_blank" rel="noopener">canned policy</a>. Canned policies only grant access to specific files, while custom policies can use the wildcard <code>*</code> character to specify a larger group of files at once.</p></blockquote><p><strong>Last but not least</strong>, we define the CloudFront distribution:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line">  <span class="attr">CloudFrontCachePolicy:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFront::CachePolicy&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">CachePolicyConfig:</span></span><br><span class="line">        <span class="attr">DefaultTTL:</span> <span class="number">3600</span> <span class="comment"># 1 hour</span></span><br><span class="line">        <span class="attr">MaxTTL:</span> <span class="number">86400</span> <span class="comment"># 1 day</span></span><br><span class="line">        <span class="attr">MinTTL:</span> <span class="number">0</span></span><br><span class="line">        <span class="attr">Name:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line">        <span class="attr">ParametersInCacheKeyAndForwardedToOrigin:</span></span><br><span class="line">          <span class="attr">CookiesConfig:</span></span><br><span class="line">            <span class="attr">CookieBehavior:</span> <span class="string">none</span></span><br><span class="line">          <span class="attr">EnableAcceptEncodingBrotli:</span> <span class="literal">true</span></span><br><span class="line">          <span class="attr">EnableAcceptEncodingGzip:</span> <span class="literal">true</span></span><br><span class="line">          <span class="attr">HeadersConfig:</span></span><br><span class="line">            <span class="attr">HeaderBehavior:</span> <span class="string">none</span></span><br><span class="line">          <span class="attr">QueryStringsConfig:</span></span><br><span class="line">            <span class="attr">QueryStringBehavior:</span> <span class="string">none</span></span><br><span class="line">  <span class="attr">CloudFrontDistribution:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFront::Distribution&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">DistributionConfig:</span></span><br><span class="line">        <span class="attr">CacheBehaviors:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">CachePolicyId:</span> <span class="type">!Ref</span> <span class="string">CloudFrontCachePolicy</span></span><br><span class="line">          <span class="attr">Compress:</span> <span class="literal">true</span></span><br><span class="line">          <span class="attr">PathPattern:</span> <span class="string">&#x27;private/*&#x27;</span></span><br><span class="line">          <span class="attr">TargetOriginId:</span> <span class="string">private</span></span><br><span class="line">          <span class="attr">TrustedKeyGroups:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">CloudFrontKeyGroup</span></span><br><span class="line">          <span class="attr">ViewerProtocolPolicy:</span> <span class="string">&#x27;redirect-to-https&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">AllowedMethods:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">GET</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">HEAD</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">OPTIONS</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">PUT</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">PATCH</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">POST</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">DELETE</span></span><br><span class="line">          <span class="attr">CachedMethods:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">GET</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">HEAD</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">OPTIONS</span></span><br><span class="line">          <span class="attr">CachePolicyId:</span> <span class="type">!Ref</span> <span class="string">CloudFrontCachePolicy</span></span><br><span class="line">          <span class="attr">Compress:</span> <span class="literal">true</span></span><br><span class="line">          <span class="attr">LambdaFunctionAssociations:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">EventType:</span> <span class="string">&#x27;viewer-request&#x27;</span></span><br><span class="line">            <span class="attr">LambdaFunctionARN:</span> <span class="type">!Ref</span> <span class="string">LambdaEdgeFunctionVersionV1</span></span><br><span class="line">          <span class="attr">PathPattern:</span> <span class="string">&#x27;cookie/*&#x27;</span></span><br><span class="line">          <span class="attr">TargetOriginId:</span> <span class="string">public</span> <span class="comment"># Is never invoked!</span></span><br><span class="line">          <span class="attr">ViewerProtocolPolicy:</span> <span class="string">&#x27;redirect-to-https&#x27;</span></span><br><span class="line">        <span class="attr">DefaultCacheBehavior:</span></span><br><span class="line">          <span class="attr">CachePolicyId:</span> <span class="type">!Ref</span> <span class="string">CloudFrontCachePolicy</span></span><br><span class="line">          <span class="attr">Compress:</span> <span class="literal">true</span></span><br><span class="line">          <span class="attr">TargetOriginId:</span> <span class="string">public</span></span><br><span class="line">          <span class="attr">ViewerProtocolPolicy:</span> <span class="string">&#x27;redirect-to-https&#x27;</span></span><br><span class="line">        <span class="attr">DefaultRootObject:</span> <span class="string">&#x27;index.html&#x27;</span></span><br><span class="line">        <span class="attr">Enabled:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">HttpVersion:</span> <span class="string">http2and3</span></span><br><span class="line">        <span class="attr">IPV6Enabled:</span> <span class="literal">true</span></span><br><span class="line">        <span class="attr">Origins:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">DomainName:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Bucket.RegionalDomainName&#x27;</span></span><br><span class="line">          <span class="attr">Id:</span> <span class="string">public</span></span><br><span class="line">          <span class="attr">OriginPath:</span> <span class="string">&#x27;/public&#x27;</span></span><br><span class="line">          <span class="attr">S3OriginConfig:</span></span><br><span class="line">            <span class="attr">OriginAccessIdentity:</span> <span class="type">!Sub</span> <span class="string">&#x27;origin-access-identity/cloudfront/$&#123;CloudFrontOriginAccessIdentity&#125;&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">DomainName:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Bucket.RegionalDomainName&#x27;</span></span><br><span class="line">          <span class="attr">Id:</span> <span class="string">private</span></span><br><span class="line">          <span class="attr">S3OriginConfig:</span></span><br><span class="line">            <span class="attr">OriginAccessIdentity:</span> <span class="type">!Sub</span> <span class="string">&#x27;origin-access-identity/cloudfront/$&#123;CloudFrontOriginAccessIdentity&#125;&#x27;</span></span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">BucketName:</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">Bucket</span></span><br><span class="line">  <span class="attr">CloudFrontDistributionDomainName:</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CloudFrontDistribution.DomainName&#x27;</span></span><br></pre></td></tr></table></figure><p>To test the demo, create a CloudFormation stack in <code>us-east-1</code> based on the template. Once the stack is created, upload two files to the created S3 bucket:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws s3 <span class="built_in">cp</span> index.html s3://BUCKET_NAME/public/index.html</span><br><span class="line">aws s3 <span class="built_in">cp</span> index.html s3://BUCKET_NAME/private/index.html</span><br></pre></td></tr></table></figure><p>Now you can send requests against CloudFront. To get the public file:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl https://DOMAIN_NAME/index.html </span><br></pre></td></tr></table></figure><p>To set up the cookies:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl -c cookie.txt -I -X POST -H <span class="string">&#x27;Authorization: Bearer secret&#x27;</span> https://DOMAIN_NAME/cookie/</span><br></pre></td></tr></table></figure><p>To get a private file with the cookies from the previous request:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl -b cookie.txt https://DOMAIN_NAME/index.html </span><br></pre></td></tr></table></figure><p>That’s all you need to run a public and private website behind CloudFront. I hope it helps!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Running containers on spot infrastructure</title>
      <link>https://cloudonaut.io/running-containers-on-spot-infrastructure/</link>
      <description>
        <![CDATA[<p>Running workloads on spot infrastructure is significantly cheaper. You can reduce your bill by 50% or more. Keep in mind that spot worklo]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <category domain="https://cloudonaut.io/tag/eks/">eks</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <guid isPermaLink="true">https://cloudonaut.io/running-containers-on-spot-infrastructure/</guid>
      <pubDate>Wed, 14 Apr 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Running workloads on spot infrastructure is significantly cheaper. You can reduce your bill by 50% or more. Keep in mind that spot workloads can be terminated at any time. If you are lucky, you will be noticed 2 minutes upfront to shut down gracefully. In this blog post, I compare the options that AWS provides to run container workloads on spot infrastructure.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/spot@730w.webp 730w, /images/2021/04/spot@730w2x.webp 1460w, /images/2021/04/spot@610w.webp 610w, /images/2021/04/spot@610w2x.webp 1220w, /images/2021/04/spot@450w.webp 450w, /images/2021/04/spot@450w2x.webp 900w, /images/2021/04/spot@330w.webp 330w, /images/2021/04/spot@330w2x.webp 660w, /images/2021/04/spot@545w.webp 545w, /images/2021/04/spot@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/spot@730w.jpg 730w, /images/2021/04/spot@730w2x.jpg 1460w, /images/2021/04/spot@610w.jpg 610w, /images/2021/04/spot@610w2x.jpg 1220w, /images/2021/04/spot@450w.jpg 450w, /images/2021/04/spot@450w2x.jpg 900w, /images/2021/04/spot@330w.jpg 330w, /images/2021/04/spot@330w2x.jpg 660w, /images/2021/04/spot@545w.jpg 545w, /images/2021/04/spot@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/spot.jpg" alt="Reduce your AWS bill" title="Reduce your AWS bill"></picture></p><h2 id="Options"><a href="#Options" class="headerlink" title="Options"></a>Options</h2><p>After you select your scheduler <a href="/eks-vs-ecs-orchestrating-containers-on-aws/">EKS or ECS</a>, you can choose from the following options to run your containers on spot:</p><ul><li>ECS<ul><li><strong>Fargate</strong></li><li>Self-managed EC2</li></ul></li><li>EKS<ul><li><strong>Managed node groups</strong></li><li>Self-managed EC2</li></ul></li></ul><p>Self-managed EC2 benefits from all the spot features that are available for EC2 instances. The downside is the self-managed part: You are responsible for scaling, patching, monitoring, and so on. Therefore, I recommend using the option highlighted in <strong>bold</strong>. If your scheduler of choice is ECS, I recommend running your workload on Fargate Spot. All you need to do is to <a href="https://github.com/widdix/aws-cf-templates/pull/540" target="_blank" rel="noopener">turn a single knob</a>. If you prefer EKS, managed node groups are the way to go with native support for spot.</p><blockquote><p>EKS Fargate does not support spot at this time!</p></blockquote><h2 id="Fallback-to-on-demand"><a href="#Fallback-to-on-demand" class="headerlink" title="Fallback to on-demand?"></a>Fallback to on-demand?</h2><p>How can we fall back to on-demand capacity if the spot capacity was terminated? My usual answer is this: Spot capacity is cheap because AWS can, at any time, decide to take it away from you. <strong>If you can not accept that spot capacity can be terminated at any time, it might not be a good fit for your workload!</strong></p><p>But wouldn’t it be nice if we could benefit from the spot savings for most of the time but with a safety net of on-demand capacity? If you use ECS, there is no native way of implementing a fallback. If you use EKS, kubernetes and the Autoscaler can do the job. The following video goes into details and includes demos:</p><ul><li>Spot Refresher</li><li>Demo: ECS Fargate</li><li>Pitfall: Handle interruption notice</li><li>Pitfall: Fallback to on-demand</li><li>Demo: EKS managed node group</li></ul><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=EiJM6CbK5HM">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/EiJM6CbK5HM" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>AWS supports many options to run containers on spot infrastructure to optimize your AWS bill. The clear winners in terms of ease of use are ECS Fargate and EKS managed node groups. I was able to switch from on-demand to spot using ECS Fargate in a couple of minutes. Since then, I enjoy the lower AWS bill :)</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Review: AWS Fault Injection Simulator (FIS) – Chaos as a Service?</title>
      <link>https://cloudonaut.io/review-fault-injection-simulator-fis-chaos-as-a-service/</link>
      <description>
        <![CDATA[<p>AWS allows us to run applications distributed across EC2 instances and availability zones. By adding load balancers or message queues to]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/review/">review</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <category domain="https://cloudonaut.io/tag/fis/">fis</category>
      <guid isPermaLink="true">https://cloudonaut.io/review-fault-injection-simulator-fis-chaos-as-a-service/</guid>
      <pubDate>Tue, 13 Apr 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>AWS allows us to run applications distributed across EC2 instances and availability zones. By adding load balancers or message queues to the architecture, we can achieve fault tolerance or high availability. But how can we test that our system can survive faults in reality? Assuming an application has five consumers and seven downstream dependencies. What happens if one of them fails? Are all timeouts configured accurately? Are all applications retrying? What happens if the network is slow? So many things can go wrong. It is not possible to understand all consequences upfront. Therefore, a new approach emerged: Chaos Engineering. With chaos engineering, we simulate faults in our systems and observe the consequences. The trick is that we can simulate faults as often as we wish. We don’t have to wait for the one day a year where things go horribly wrong. AWS released <a href="https://aws.amazon.com/blogs/aws/aws-fault-injection-simulator-use-controlled-experiments-to-boost-resilience/" target="_blank" rel="noopener">Fault Injection Simulator (FIS)</a> as a tool to run controlled fault experiments within our AWS accounts.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/chaos@730w.webp 730w, /images/2021/04/chaos@730w2x.webp 1460w, /images/2021/04/chaos@610w.webp 610w, /images/2021/04/chaos@610w2x.webp 1220w, /images/2021/04/chaos@450w.webp 450w, /images/2021/04/chaos@450w2x.webp 900w, /images/2021/04/chaos@330w.webp 330w, /images/2021/04/chaos@330w2x.webp 660w, /images/2021/04/chaos@545w.webp 545w, /images/2021/04/chaos@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/chaos@730w.jpg 730w, /images/2021/04/chaos@730w2x.jpg 1460w, /images/2021/04/chaos@610w.jpg 610w, /images/2021/04/chaos@610w2x.jpg 1220w, /images/2021/04/chaos@450w.jpg 450w, /images/2021/04/chaos@450w2x.jpg 900w, /images/2021/04/chaos@330w.jpg 330w, /images/2021/04/chaos@330w2x.jpg 660w, /images/2021/04/chaos@545w.jpg 545w, /images/2021/04/chaos@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/chaos.jpg" alt="Chaos as a Service" title="Chaos as a Service"></picture></p><p>In this blog post, you will get an in-depth understanding of what FIS can do for you today. Not what marketing is hoping it to become.</p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/40-review-fault-injection-simulator-fis-chaos-as-a-service/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="Concepts"><a href="#Concepts" class="headerlink" title="Concepts"></a>Concepts</h2><p>Everything starts with an <strong>experiment template</strong>. The experiment template defines the <strong>targets</strong> that participate in the experiment. Supported targets are:</p><ul><li>EC2 instances</li><li>ECS container instances</li><li>EKS node groups</li><li>RDS clusters &amp; instances</li><li>IAM roles</li></ul><p>You can select targets by ARN, tag, or filter. I used the <code>aws:autoscaling:groupName</code> tag to select all EC2 instances launched by one auto scaling group. The <strong>selection mode</strong> picks targets by an absolute or relative number in a random way. I picked one EC2 instance randomly from the fleet of instances launched by my auto scaling group.</p><p>The <strong>actions</strong> define the injected faults. You can run actions in parallel or sequence. Possible actions are (see next section for details):</p><ul><li>AWS API level errors for the EC2 service</li><li>Stop&#x2F;reboot&#x2F;terminate EC2 instances</li><li>Run SSM commands on EC2 instances to stress CPU or memory, add network latency, or kill a process</li><li>Reboot RDS instance</li><li>Failover RDS cluster</li><li>Drain ECS container instance</li><li>Terminate EKS node group instance</li></ul><p>My experiment looks like this:</p><ol><li>Terminate one EC2 instance from my auto scaling group</li><li>Wait 8 minutes</li><li>Stress CPU on one EC2 instance for three minutes</li><li>Wait 5 minutes</li></ol><p>The most important question is this: What do you expect based on the chaos you simulate? My architecture is using SQS to decouple producers from my EC2 consumers. I expect that if an instance is randomly terminated, the message is processed again by another EC2 instance. How long will it take? I expect the worst-case scenario to be: <code>2 * processing time + SQS visibility timeout</code>. In my case, 5 minutes is a reasonable threshold that should never be reached.</p><p>The <strong>stop condition</strong> references a CloudWatch alarm (or alarms) to stop and roll back the experiment if things go wrong. Based on my expectation, I created an alarm to monitor the age of the oldest message in my SQS queue with a threshold of 5 minutes. Suppose my experiment impacts the application so badly that the queue contains messages older than five minutes. In that case, the experiment is stopped, and I have to double-check my assumption or fix the problem.</p><p>Last but not least, you can run an <strong>experiment</strong> based on the template created before.</p><h2 id="Supported-chaos"><a href="#Supported-chaos" class="headerlink" title="Supported chaos"></a>Supported chaos</h2><p>If FIS is helpful for you or not depends on the technology you use to run your workloads. The following list summarizes the chaos you can simulate today:</p><ul><li>EC2<ul><li>API<ul><li>Simulate internal errors</li><li>Simulate throttle errors</li><li>Simulate unavailable errors</li></ul></li><li>Reboot EC2 instance</li><li>Stop EC2 instance</li><li>Terminate EC2 instance</li><li>SSM commands (requires SSM agent):<ul><li>CPU stress (Linux only)</li><li>Memory stress (Linux only)</li><li>Network latency (Linux only)</li><li>Kill process (Linux only)</li></ul></li></ul></li><li>ECS<ul><li>Drain cluster instance</li></ul></li><li>EKS<ul><li>Terminate node group instance</li></ul></li><li>RDS<ul><li>Reboot database instance</li><li>Failover database cluster</li></ul></li></ul><p><strong>In a nutshell, if your workloads run on EC2 Linux, you are lucky. Otherwise, FIS is not yet for you.</strong></p><p>Things I miss in no particular order:</p><ul><li>API chaos for DynamoDB, SQS, S3, …</li><li>Lambda</li><li>EC2: Run out of disk space</li><li>EC2&#x2F;EBS: Slow disk</li><li>EC2: Network packet loss</li><li>EC2&#x2F;Java: GC stress</li><li>ECS&#x2F;EKS&#x2F;Fargate: Container level stress</li><li>EC2: Spot Market capacity shortage</li><li>EC2: AZ failure</li><li>VPC: network partitions</li><li>VPC: NAT failure</li></ul><p>I bet you can add many items to this list as well! Feel free to <a href="mailto:&#x6d;&#x69;&#99;&#x68;&#x61;&#x65;&#x6c;&#x40;&#x77;&#105;&#100;&#100;&#x69;&#x78;&#x2e;&#100;&#101;">reach out to me</a>. I will add your ideas to the list.</p><h2 id="Integrations"><a href="#Integrations" class="headerlink" title="Integrations"></a>Integrations</h2><p>AWS FIS is like a lost island. No other service likes to integrate with FIS yet. This leads to the problem: How can you start experiments in an automated way? We have no CodePipeline integration. You could start an experiment using the AWS CLI. But the CLI does not support any wait helpers to wait for the experiment to be complete. You need to implement the status polling yourself.</p><p>The unbelievable fact that made me smile: CloudFormation support is available while <a href="https://github.com/hashicorp/terraform-provider-aws/issues/18125" target="_blank" rel="noopener">Terraform support is missing</a>. To save you some time and frustration, I share the CloudFormation template I used to set up my experiment template:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">ExperimentTemplate:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::FIS::ExperimentTemplate&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Actions:</span></span><br><span class="line">      <span class="attr">&#x27;terminate-asg&#x27;:</span></span><br><span class="line">        <span class="attr">ActionId:</span> <span class="string">&#x27;aws:ec2:terminate-instances&#x27;</span></span><br><span class="line">        <span class="attr">Targets:</span></span><br><span class="line">          <span class="attr">Instances:</span> <span class="string">asg</span> <span class="comment"># Instances seems to be an undocumented magic value</span></span><br><span class="line">      <span class="attr">&#x27;await-terminate-asg&#x27;:</span></span><br><span class="line">        <span class="attr">ActionId:</span> <span class="string">&#x27;aws:fis:wait&#x27;</span></span><br><span class="line">        <span class="attr">Parameters:</span></span><br><span class="line">          <span class="attr">duration:</span> <span class="string">PT8M</span></span><br><span class="line">        <span class="attr">StartAfter:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&#x27;terminate-asg&#x27;</span></span><br><span class="line">      <span class="attr">&#x27;cpu-stress-asg&#x27;:</span></span><br><span class="line">        <span class="attr">ActionId:</span> <span class="string">&#x27;aws:ssm:send-command&#x27;</span></span><br><span class="line">        <span class="attr">Parameters:</span></span><br><span class="line">          <span class="attr">documentArn:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:ssm:$&#123;AWS::Region&#125;::document/AWSFIS-Run-CPU-Stress&#x27;</span></span><br><span class="line">          <span class="attr">documentVersion:</span> <span class="number">4</span></span><br><span class="line">          <span class="attr">documentParameters:</span> <span class="string">&#x27;&#123;&quot;DurationSeconds&quot;:&quot;180&quot;, &quot;InstallDependencies&quot;:&quot;True&quot;&#125;&#x27;</span></span><br><span class="line">          <span class="attr">duration:</span> <span class="string">&#x27;PT3M&#x27;</span></span><br><span class="line">        <span class="attr">StartAfter:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&#x27;await-terminate-asg&#x27;</span></span><br><span class="line">        <span class="attr">Targets:</span></span><br><span class="line">          <span class="attr">Instances:</span> <span class="string">asg</span> <span class="comment"># Instances seems to be an undocumented magic value</span></span><br><span class="line">      <span class="attr">&#x27;await-cpu-stress-asg&#x27;:</span></span><br><span class="line">        <span class="attr">ActionId:</span> <span class="string">&#x27;aws:fis:wait&#x27;</span></span><br><span class="line">        <span class="attr">Parameters:</span></span><br><span class="line">          <span class="attr">duration:</span> <span class="string">PT5M</span></span><br><span class="line">        <span class="attr">StartAfter:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&#x27;cpu-stress-asg&#x27;</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;cloudonaut&#x27;</span></span><br><span class="line">    <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Role.Arn&#x27;</span></span><br><span class="line">    <span class="attr">StopConditions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Source:</span> <span class="string">&#x27;aws:cloudwatch:alarm&#x27;</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Alarm.Arn&#x27;</span></span><br><span class="line">    <span class="attr">Tags:</span> <span class="comment"># Why are tags required? Only AWS knows.</span></span><br><span class="line">      <span class="attr">PLACE:</span> <span class="string">HOLDER</span></span><br><span class="line">    <span class="attr">Targets:</span></span><br><span class="line">      <span class="attr">asg:</span></span><br><span class="line">        <span class="attr">ResourceTags:</span></span><br><span class="line">          <span class="attr">&#x27;aws:autoscaling:groupName&#x27;:</span> <span class="string">&#x27;NAME_OF_YOUR_ASG&#x27;</span></span><br><span class="line">        <span class="attr">ResourceType:</span> <span class="string">&#x27;aws:ec2:instance&#x27;</span></span><br><span class="line">        <span class="attr">SelectionMode:</span> <span class="string">&#x27;COUNT(1)&#x27;</span> <span class="comment"># alternative &#x27;PERCENT(50)&#x27;</span></span><br><span class="line"><span class="attr">Alarm:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AlarmDescription:</span> <span class="string">&#x27;FIS stop condition&#x27;</span></span><br><span class="line">    <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/SQS&#x27;</span></span><br><span class="line">    <span class="attr">MetricName:</span> <span class="string">ApproximateAgeOfOldestMessage</span></span><br><span class="line">    <span class="attr">Dimensions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">QueueName</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="string">&#x27;NAME_OF_YOUR_QUEUE&#x27;</span></span><br><span class="line">    <span class="attr">Statistic:</span> <span class="string">Maximum</span></span><br><span class="line">    <span class="attr">Period:</span> <span class="number">60</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">300</span> <span class="comment"># 5 minutes</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanOrEqualToThreshold</span></span><br><span class="line">    <span class="attr">TreatMissingData:</span> <span class="string">notBreaching</span></span><br><span class="line"><span class="attr">Role:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">      <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">      <span class="attr">Statement:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">        <span class="attr">Principal:</span></span><br><span class="line">          <span class="attr">Service:</span> <span class="string">&#x27;fis.amazonaws.com&#x27;</span></span><br><span class="line">        <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    <span class="attr">Policies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">fis</span> <span class="comment"># Source https://docs.aws.amazon.com/fis/latest/userguide/getting-started-iam.html#getting-started-iam-service-role</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">AllowFISExperimentRoleReadOnly</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeInstances&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ecs:DescribeClusters&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ecs:ListContainerInstances&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;eks:DescribeNodegroup&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;iam:ListRoles&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;rds:DescribeDBInstances&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;rds:DescribeDbClusters&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ssm:ListCommands&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">AllowFISExperimentRoleEC2Actions</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:RebootInstances&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:StopInstances&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:StartInstances&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:TerminateInstances&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:ec2:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:instance/*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">AllowFISExperimentRoleECSActions</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ecs:UpdateContainerInstancesState&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ecs:ListContainerInstances&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:ecs:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:container-instance/*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">AllowFISExperimentRoleEKSActions</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;ec2:TerminateInstances&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:ec2:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:instance/*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">AllowFISExperimentRoleFISActions</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;fis:InjectApiInternalError&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;fis:InjectApiThrottleError&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;fis:InjectApiUnavailableError&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:fis:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:experiment/*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">AllowFISExperimentRoleRDSReboot</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;rds:RebootDBInstance&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:rds:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:db:*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">AllowFISExperimentRoleRDSFailOver</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;rds:FailoverDBCluster&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:rds:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:cluster:*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">AllowFISExperimentRoleSSMSendCommand</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;ssm:SendCommand&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:ec2:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:instance/*&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:ssm:$&#123;AWS::Region&#125;::document/*&#x27;</span> <span class="comment"># AWS managed documents</span></span><br><span class="line">          <span class="bullet">-</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:ssm:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:document/*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">AllowFISExperimentRoleSSMCancelCommand</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;ssm:CancelCommand&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="Pricing"><a href="#Pricing" class="headerlink" title="Pricing"></a>Pricing</h2><p>You pay $0.10 per minute for each action running.</p><p>Fun fact: FIS wins the award of most expensive service while doing nothing:</p><ul><li>$0.0000105 Lambda function waiting for five minutes</li><li>$0.000025 Step Functions waiting for five minutes</li><li>$0.00035 EC2 instance t4g.nano waiting for five minutes</li><li>$0.50 FIS waiting for five minutes</li></ul><h2 id="Service-Maturity-Table"><a href="#Service-Maturity-Table" class="headerlink" title="Service Maturity Table"></a>Service Maturity Table</h2><p>The following table summarizes the maturity of the service:</p><table class="table table-striped table-responsive"><thead><tr><th>Criteria</th><th>Support</th><th style="text-align:right">Score</th></tr></thead><tbody><tr><td>Feature Completeness</td><td>✅</td><td style="text-align:right">5</td></tr><tr><td>Chaos support: EC2</td><td>✅</td><td style="text-align:right">7</td></tr><tr><td>Chaos support: Containers</td><td>⚠️</td><td style="text-align:right">2</td></tr><tr><td>Chaos support: Serverless</td><td>❌</td><td style="text-align:right">0</td></tr><tr><td>Documentation detailedness</td><td>✅</td><td style="text-align:right">7</td></tr><tr><td>Tags (Grouping + Billing)</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>CloudFormation + Terraform support</td><td>✅</td><td style="text-align:right">4</td></tr><tr><td>Emits CloudWatch Events</td><td>❌</td><td style="text-align:right">0</td></tr><tr><td>IAM granularity</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>Auditing via AWS CloudTrail</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>Available in all commercial regions</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td><strong>Total Maturity Score (0-10)</strong></td><td>⚠️</td><td style="text-align:right"><strong>5.9</strong></td></tr></tbody></table><p>Our maturity score for Fault Injection Simulator (FIS) is 5.7 on a scale from 0 to 10. I want to highlight that the IAM granularity is excellent! We have resource-level constraints, and we can implement tag-based policies if needed.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Besides the lacking chaos support, FIS is mature compared to other newly launched services. If you run workloads on EC2 and your architecture is designed to have no single point of failure, I would recommend giving FIS a try. You will likely learn something about your architecture, and the chances that you discover a bug are high as well!</p><p>One thing to keep in mind with EC2 based workloads: If you terminate EC2 instances that sit behind user-facing load balancers, your experiment will impact your users. The load balancer does not retry a request if the target dies! Neither does the user’s browser. I wish ALB’s could retry for us!</p><p>PS: I think I discovered a bug in FIS. The tag-based EC2 instance selection includes terminated instances. I reached out to AWS support to clarify. Turns out that this is expected behavior (not sure if I agree). If we want to filter out terminated instances we better use the filter based approach.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Programming your CDN: CloudFront and Lambda@Edge</title>
      <link>https://cloudonaut.io/programming-your-cdn-cloudfront-lambda-at-edge/</link>
      <description>
        <![CDATA[<p>Minimizing the load time of your websites and applications is essential for two reasons. First, search engines rank websites based on pag]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/cloudfront/">cloudfront</category>
      <category domain="https://cloudonaut.io/tag/lambda-at-edge/">lambda-at-edge</category>
      <guid isPermaLink="true">https://cloudonaut.io/programming-your-cdn-cloudfront-lambda-at-edge/</guid>
      <pubDate>Fri, 09 Apr 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Minimizing the load time of your websites and applications is essential for two reasons. First, search engines rank websites based on page load times. Second, users are impatient and might cancel loading your application to jump to a competitor instead. That’s why content delivery networks (CDNs) became more and more popular since they came into existence in the late 1990s. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/programming@730w.webp 730w, /images/2021/04/programming@730w2x.webp 1460w, /images/2021/04/programming@610w.webp 610w, /images/2021/04/programming@610w2x.webp 1220w, /images/2021/04/programming@450w.webp 450w, /images/2021/04/programming@450w2x.webp 900w, /images/2021/04/programming@330w.webp 330w, /images/2021/04/programming@330w2x.webp 660w, /images/2021/04/programming@545w.webp 545w, /images/2021/04/programming@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/programming@730w.jpg 730w, /images/2021/04/programming@730w2x.jpg 1460w, /images/2021/04/programming@610w.jpg 610w, /images/2021/04/programming@610w2x.jpg 1220w, /images/2021/04/programming@450w.jpg 450w, /images/2021/04/programming@450w2x.jpg 900w, /images/2021/04/programming@330w.jpg 330w, /images/2021/04/programming@330w2x.jpg 660w, /images/2021/04/programming@545w.jpg 545w, /images/2021/04/programming@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/programming.jpg" alt="Programming your CDN" title="Programming your CDN"></picture></p><p>A CDN typically consists of hundreds of proxy servers distributed among data centers all around the world. The idea is to minimize the distance between the users and the server. Doing so allows reducing latency significantly. On top of that, a CDN caches the responses from the origins like your web servers, for example. Another way to minimize latency and, therefore, page load times.</p><p>But, a CDN is much more than that. Learn how to benefit from programming your CDN in the following.</p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><h2 id="Distributing-media-assets-with-CloudFront"><a href="#Distributing-media-assets-with-CloudFront" class="headerlink" title="Distributing media assets with CloudFront"></a>Distributing media assets with CloudFront</h2><p>There are many CDN providers. Akamai, Fastly, and Cloudflare, to name a few. AWS offers a content delivery network as well: <a href="https://aws.amazon.com/cloudfront/" target="_blank" rel="noopener">CloudFront</a>. Amazon’s CDN consists of more than 200 edge locations and spans 88 cities across 45 countries. For example, I live in Ulm - a small town in the south of Germany - the next edge location is in Munich, which is about 120 km (about 75 miles) away.</p><p>To distribute your media assets with CloudFront, you need to configure a so-called distribution that forwards incoming requests to an origin if the response is not already cached. As shown in the following diagram, CloudFront supports multiple origins.</p><ul><li>Amazon <strong>S3</strong>, an object store, is the most obvious choice for media assets</li><li>Any infrastructure running on AWS, typically behind an <strong>Elastic Load Balancing (ELB) load balancer</strong></li><li>Any <strong>HTTP(S) endpoint</strong> accessible from the Internet</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/cloudfront-origins@730w.webp 730w, /images/2021/04/cloudfront-origins@730w2x.webp 1460w, /images/2021/04/cloudfront-origins@610w.webp 610w, /images/2021/04/cloudfront-origins@610w2x.webp 1220w, /images/2021/04/cloudfront-origins@450w.webp 450w, /images/2021/04/cloudfront-origins@450w2x.webp 900w, /images/2021/04/cloudfront-origins@330w.webp 330w, /images/2021/04/cloudfront-origins@330w2x.webp 660w, /images/2021/04/cloudfront-origins@545w.webp 545w, /images/2021/04/cloudfront-origins@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/cloudfront-origins@730w.png 730w, /images/2021/04/cloudfront-origins@730w2x.png 1460w, /images/2021/04/cloudfront-origins@610w.png 610w, /images/2021/04/cloudfront-origins@610w2x.png 1220w, /images/2021/04/cloudfront-origins@450w.png 450w, /images/2021/04/cloudfront-origins@450w2x.png 900w, /images/2021/04/cloudfront-origins@330w.png 330w, /images/2021/04/cloudfront-origins@330w2x.png 660w, /images/2021/04/cloudfront-origins@545w.png 545w, /images/2021/04/cloudfront-origins@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/cloudfront-origins.png" alt="Distributing media assets with CloudFront" title="Distributing media assets with CloudFront"></picture></p><p>Of course, CloudFront supports HTTPS. Bring your certificate or use the Amazon Certificate Manager (ACM) to generate a free certificate for your domain.</p><p>CloudFront costs per request and data transfer. That’s $0.0100 per 10,000 HTTPS requests and $0.085 per GB. For example, I paid $40 in November to deliver a blog’s assets with about 100,000 page impressions.</p><p>I’m using CloudFront for many projects. For example, static websites typically generated by a website generator like <a href="https://hexo.io/" target="_blank" rel="noopener">Hexo</a>, <a href="https://jekyllrb.com/" target="_blank" rel="noopener">Jekyll</a>, or <a href="https://gohugo.io/" target="_blank" rel="noopener">Hugo</a> are a perfect fit for S3 and CloudFront. Also, I’m using CloudFront to distribute the assets - HTML, CSS, and more - of a single page application (SPA).</p><p>One more thing, CloudFront works fine for assets like HTML, CSS, JPG, PNG, but is an excellent choice to deliver video-on-demand and downloads as well. That being said,the file size limit is 20 GB.</p><h2 id="Programming-your-CDN-with-Lambda-Edge"><a href="#Programming-your-CDN-with-Lambda-Edge" class="headerlink" title="Programming your CDN with Lambda@Edge"></a>Programming your CDN with Lambda@Edge</h2><p>Interestingly, CloudFront is more than a CDN for static assets. Programming your CDN is possible by making use of Lambda@Edge.</p><p>But let’s start at the beginning. AWS Lambda is a platform to execute your source code without spinning up any servers. All you do is to program a function and upload it. AWS Lambda provides the runtime environment -  for example, Python, Java, or Node.js - and provisions the required infrastructure underneath. It is possible to <a href="https://blog.cloudcraft.co/comparing-api-gateways-on-aws/" target="_blank" rel="noopener">build Serverless web applications with this approach</a>.</p><p>Lambda@Edge enables you to execute your source code at the CDN layer. Hook your code in one of the following steps.</p><ul><li>When CloudFront receives a request from a user.</li><li>Before CloudFront forwards a request to the origin (e.g., S3).</li><li>When CloudFront receives a response from the origin.</li><li>Before CloudFront returns the response to the user.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/cloudfront-lambda-at-edge@730w.webp 730w, /images/2021/04/cloudfront-lambda-at-edge@730w2x.webp 1460w, /images/2021/04/cloudfront-lambda-at-edge@610w.webp 610w, /images/2021/04/cloudfront-lambda-at-edge@610w2x.webp 1220w, /images/2021/04/cloudfront-lambda-at-edge@450w.webp 450w, /images/2021/04/cloudfront-lambda-at-edge@450w2x.webp 900w, /images/2021/04/cloudfront-lambda-at-edge@330w.webp 330w, /images/2021/04/cloudfront-lambda-at-edge@330w2x.webp 660w, /images/2021/04/cloudfront-lambda-at-edge@545w.webp 545w, /images/2021/04/cloudfront-lambda-at-edge@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/cloudfront-lambda-at-edge@730w.png 730w, /images/2021/04/cloudfront-lambda-at-edge@730w2x.png 1460w, /images/2021/04/cloudfront-lambda-at-edge@610w.png 610w, /images/2021/04/cloudfront-lambda-at-edge@610w2x.png 1220w, /images/2021/04/cloudfront-lambda-at-edge@450w.png 450w, /images/2021/04/cloudfront-lambda-at-edge@450w2x.png 900w, /images/2021/04/cloudfront-lambda-at-edge@330w.png 330w, /images/2021/04/cloudfront-lambda-at-edge@330w2x.png 660w, /images/2021/04/cloudfront-lambda-at-edge@545w.png 545w, /images/2021/04/cloudfront-lambda-at-edge@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/cloudfront-lambda-at-edge.png" alt="Programming your CDN with Lambda@Edge" title="Programming your CDN with Lambda@Edge"></picture></p><p>Being able to execute your code at the CDN layer opens up many possibilities. Next, I will share some use cases from practice.</p><h2 id="Use-Cases"><a href="#Use-Cases" class="headerlink" title="Use Cases"></a>Use Cases</h2><p>The following use cases should serve as inspiration for utilizing Lamnda@Edge as part of your cloud architecture.</p><h3 id="HTTP-redirects-and-index-document"><a href="#HTTP-redirects-and-index-document" class="headerlink" title="HTTP redirects and index document"></a>HTTP redirects and index document</h3><p>When using S3 and CloudFront to host static websites, there are two common challenges.</p><ol><li>Redirect requests from a subdomain to the second-level domain.</li><li>Define root documents for subfolders. CloudFront allows configuring a root document for the root folder only.</li></ol><p>Adding those missing features to CloudFront is possible with Lambda@Edge. The following diagram illustrates the process for an HTTP redirect.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/cloudfront-lambda-at-edge-redirects@730w.webp 730w, /images/2021/04/cloudfront-lambda-at-edge-redirects@730w2x.webp 1460w, /images/2021/04/cloudfront-lambda-at-edge-redirects@610w.webp 610w, /images/2021/04/cloudfront-lambda-at-edge-redirects@610w2x.webp 1220w, /images/2021/04/cloudfront-lambda-at-edge-redirects@450w.webp 450w, /images/2021/04/cloudfront-lambda-at-edge-redirects@450w2x.webp 900w, /images/2021/04/cloudfront-lambda-at-edge-redirects@330w.webp 330w, /images/2021/04/cloudfront-lambda-at-edge-redirects@330w2x.webp 660w, /images/2021/04/cloudfront-lambda-at-edge-redirects@545w.webp 545w, /images/2021/04/cloudfront-lambda-at-edge-redirects@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/cloudfront-lambda-at-edge-redirects@730w.png 730w, /images/2021/04/cloudfront-lambda-at-edge-redirects@730w2x.png 1460w, /images/2021/04/cloudfront-lambda-at-edge-redirects@610w.png 610w, /images/2021/04/cloudfront-lambda-at-edge-redirects@610w2x.png 1220w, /images/2021/04/cloudfront-lambda-at-edge-redirects@450w.png 450w, /images/2021/04/cloudfront-lambda-at-edge-redirects@450w2x.png 900w, /images/2021/04/cloudfront-lambda-at-edge-redirects@330w.png 330w, /images/2021/04/cloudfront-lambda-at-edge-redirects@330w2x.png 660w, /images/2021/04/cloudfront-lambda-at-edge-redirects@545w.png 545w, /images/2021/04/cloudfront-lambda-at-edge-redirects@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/cloudfront-lambda-at-edge-redirects.png" alt="HTTP redirects and index document" title="HTTP redirects and index document"></picture></p><p>CloudFront invokes the Lambda function for every incoming request - a so-called viewer request. The Lambda function checks the domain name and returns an HTTP response with status code 302 if a redirect is needed. The following code snippet - written in JavaScript - from <a href="https://github.com/widdix/aws-cf-templates/blob/master/static-website/lambdaedge-index-document.yaml" target="_blank" rel="noopener">aws-cf-templates</a> shows how to do so exemplarily.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> domainName = <span class="string">&#x27;example.com&#x27;</span>.<span class="title function_">toLowerCase</span>();</span><br><span class="line"><span class="keyword">const</span> redirectDomainName = <span class="string">&#x27;www.example.com&#x27;</span>.<span class="title function_">toLowerCase</span>();</span><br><span class="line"></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">handler</span> = <span class="keyword">async</span> <span class="keyword">function</span> (<span class="params">event</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> cf = event.<span class="property">Records</span>[<span class="number">0</span>].<span class="property">cf</span>;</span><br><span class="line">  <span class="keyword">if</span> (cf.<span class="property">request</span>.<span class="property">headers</span>.<span class="property">host</span>[<span class="number">0</span>].<span class="property">value</span>.<span class="title function_">toLowerCase</span>() === redirectDomainName) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">status</span>: <span class="string">&#x27;301&#x27;</span>,</span><br><span class="line">      <span class="attr">statusDescription</span>: <span class="string">&#x27;Moved Permanently&#x27;</span>,</span><br><span class="line">      <span class="attr">headers</span>: &#123;</span><br><span class="line">        <span class="attr">location</span>: [&#123;</span><br><span class="line">          <span class="attr">key</span>: <span class="string">&#x27;Location&#x27;</span>,</span><br><span class="line">          <span class="attr">value</span>: <span class="string">`https://<span class="subst">$&#123;!domainName&#125;</span><span class="subst">$&#123;!cf.request.uri&#125;</span>`</span>,</span><br><span class="line">        &#125;],</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> cf.<span class="property">request</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>In this use case, using Lambda@Edge is simple, but powerful.</p><h3 id="Resize-or-manipulate-images-on-the-fly"><a href="#Resize-or-manipulate-images-on-the-fly" class="headerlink" title="Resize or manipulate images on-the-fly"></a>Resize or manipulate images on-the-fly</h3><p>Another use case for which I have used Lambda@Edge is to resize or manipulate images on-the-fly. Users access your website with their smartphones, tablets, laptops, and large screens. A responsive website renders images in different dimensions. To reduce network traffic and latency, you need to provide an image in multiple sizes. In theory, you can do so by storing each image in all the required dimensions. However, when the layout changes, you will need to re-create those images with the changed sizes. Doing so can be challenging.</p><p>Another approach is to resize images on-the-fly. Nowadays, so-called source sets allow you to define different variants of an image, and the browser decides which image to load based on the needed image dimensions, for example.</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">img</span> <span class="attr">srcset</span>=<span class="string">&quot;example-480w.jpg 480w,</span></span></span><br><span class="line"><span class="string"><span class="tag">             example-800w.jpg 800w&quot;</span></span></span><br><span class="line"><span class="tag">     <span class="attr">sizes</span>=<span class="string">&quot;(max-width: 600px) 480px,</span></span></span><br><span class="line"><span class="string"><span class="tag">            800px&quot;</span></span></span><br><span class="line"><span class="tag">     <span class="attr">src</span>=<span class="string">&quot;example-800w.jpg&quot;</span></span></span><br><span class="line"><span class="tag">     <span class="attr">alt</span>=<span class="string">&quot;Example&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>Therefore, the browser will request example-480w.jpg or example-800w.jpg based on the actual width of the responsive image. Check out <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images" target="_blank" rel="noopener">Responsive images</a> from Mozilla to learn more.</p><p>By using Lambda@Edge, quick resizing is possible. When CloudFront has not cached an image with specific dimensions yet, it will invoke a Lambda function that will fetch the original image from S3 and resize as needed.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/cloudfront-lambda-at-edge-resize@730w.webp 730w, /images/2021/04/cloudfront-lambda-at-edge-resize@730w2x.webp 1460w, /images/2021/04/cloudfront-lambda-at-edge-resize@610w.webp 610w, /images/2021/04/cloudfront-lambda-at-edge-resize@610w2x.webp 1220w, /images/2021/04/cloudfront-lambda-at-edge-resize@450w.webp 450w, /images/2021/04/cloudfront-lambda-at-edge-resize@450w2x.webp 900w, /images/2021/04/cloudfront-lambda-at-edge-resize@330w.webp 330w, /images/2021/04/cloudfront-lambda-at-edge-resize@330w2x.webp 660w, /images/2021/04/cloudfront-lambda-at-edge-resize@545w.webp 545w, /images/2021/04/cloudfront-lambda-at-edge-resize@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/cloudfront-lambda-at-edge-resize@730w.png 730w, /images/2021/04/cloudfront-lambda-at-edge-resize@730w2x.png 1460w, /images/2021/04/cloudfront-lambda-at-edge-resize@610w.png 610w, /images/2021/04/cloudfront-lambda-at-edge-resize@610w2x.png 1220w, /images/2021/04/cloudfront-lambda-at-edge-resize@450w.png 450w, /images/2021/04/cloudfront-lambda-at-edge-resize@450w2x.png 900w, /images/2021/04/cloudfront-lambda-at-edge-resize@330w.png 330w, /images/2021/04/cloudfront-lambda-at-edge-resize@330w2x.png 660w, /images/2021/04/cloudfront-lambda-at-edge-resize@545w.png 545w, /images/2021/04/cloudfront-lambda-at-edge-resize@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/cloudfront-lambda-at-edge-resize.png" alt="Resize or manipulate images on-the-fly" title="Resize or manipulate images on-the-fly"></picture></p><p>The benefit is that modifying the layout does not require you to re-generate all the images in different sizes. Instead, the provided image sizes are generated on-demand. That’s also a benefit when some assets are accessed very seldomly or not at all.</p><p>Besides resizing images, the same approach works for manipulating images in general. For example, to add a watermark or to optimize for smaller file sizes.</p><h3 id="User-and-Origin-Authentication"><a href="#User-and-Origin-Authentication" class="headerlink" title="User and Origin Authentication"></a>User and Origin Authentication</h3><p>Executing your code with Lambda@Edge enables you to implement custom authentication as well. There are two major scenarios for doing so:</p><ol><li>Authenticate the user that tries to access media assets. For example, to restrict access to paid content.</li><li>Add credentials when sending requests to the origin - for example, an ALB - to ensure that your web application only answers CloudFront requests. That’s important because CloudFront origins have to be accessible from the Internet. Without authenticating your CloudFront distribution, it is possible to bypass the CDN. Something a DDoS attacker might try to do.</li></ol><p>The functionality is similar to the previous use cases. Configuring a Lambda@Edge function to process viewer requests allows you to authenticate a user, for example, by using basic authentication or JWT. On top of that, hooking a Lambda@Edge function into the origin request allows you to add credentials to authenticate at the origin.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/04/cloudfront-lambda-at-edge-authn@730w.webp 730w, /images/2021/04/cloudfront-lambda-at-edge-authn@730w2x.webp 1460w, /images/2021/04/cloudfront-lambda-at-edge-authn@610w.webp 610w, /images/2021/04/cloudfront-lambda-at-edge-authn@610w2x.webp 1220w, /images/2021/04/cloudfront-lambda-at-edge-authn@450w.webp 450w, /images/2021/04/cloudfront-lambda-at-edge-authn@450w2x.webp 900w, /images/2021/04/cloudfront-lambda-at-edge-authn@330w.webp 330w, /images/2021/04/cloudfront-lambda-at-edge-authn@330w2x.webp 660w, /images/2021/04/cloudfront-lambda-at-edge-authn@545w.webp 545w, /images/2021/04/cloudfront-lambda-at-edge-authn@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/04/cloudfront-lambda-at-edge-authn@730w.png 730w, /images/2021/04/cloudfront-lambda-at-edge-authn@730w2x.png 1460w, /images/2021/04/cloudfront-lambda-at-edge-authn@610w.png 610w, /images/2021/04/cloudfront-lambda-at-edge-authn@610w2x.png 1220w, /images/2021/04/cloudfront-lambda-at-edge-authn@450w.png 450w, /images/2021/04/cloudfront-lambda-at-edge-authn@450w2x.png 900w, /images/2021/04/cloudfront-lambda-at-edge-authn@330w.png 330w, /images/2021/04/cloudfront-lambda-at-edge-authn@330w2x.png 660w, /images/2021/04/cloudfront-lambda-at-edge-authn@545w.png 545w, /images/2021/04/cloudfront-lambda-at-edge-authn@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/04/cloudfront-lambda-at-edge-authn.png" alt="User and Origin Authentication" title="User and Origin Authentication"></picture></p><p>In theory, it is possible to access a database from Lambda@Edge as well. DynamoDB is a good fit here because of its low latency and public REST API.</p><p>Hopefully, you were inspired by one of the described use cases for Lambda@Edge already. The programming you CDN is a powerful approach, however, you need to know about the limits of the architecture pattern as well.</p><h2 id="Limitations"><a href="#Limitations" class="headerlink" title="Limitations"></a>Limitations</h2><p>When looking at an AWS service, it is most interesting to look into its limitations. Because this is where you decide whether or not your architecture works out as planned.</p><ul><li>Lambda@Edge does support Internet-facing origins only. Private resources within a VPC are neither accessible from CloudFront nor Lambda@Edge.</li><li>Lambda@Edge supports the following runtime environments: Python 3.8, Python 3.7,  Node.js 12, and Node.js 10.</li><li>Lambda@Edge does not support environment variables.</li><li>Lambda@Edge does not support layers.</li><li>Lambda@Edge does not support dead letter queues.</li></ul><p>A Lambda@Edge function for processing viewer requests comes with additional restrictions.</p><ul><li>Maximum Memory: 128 MB</li><li>Function Timeout: 5 seconds</li><li>Response Size: 40 KB</li></ul><p>Check out the <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-requirements-limits.html#lambda-requirements-lambda-function-configuration" target="_blank" rel="noopener">official documentation</a> to learn more about the limitations of Lambda@Edge.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>When designing an architecture, you should consider adding a content delivery network (CDN) to decrease network latency and cache popular requests. AWS offers CloudFront, which integrates very well with the object store S3.</p><p>On top of that, extending and customizing CloudFront’s functionally with Lambda@Edge is powerful. Manipulate incoming requests and hook into forwarding requests to an origin if a request cannot be answered from the cache. For example, I have been using Lambda@Edge to implement HTTP redirects, generate resized and optimized images on-the-fly and implement authentication between a user and CloudFront and between CloudFront and your custom origin.</p><p>A fun fact at the end: Lambda@Edge is not running at the 200+ edge locations AWS operates. Instead, AWS deploys the Lambda function to their 20 regions. The name is somewhat misleading.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Defining IAM Policies with Terraform safely</title>
      <link>https://cloudonaut.io/defining-iam-policies-with-terraform/</link>
      <description>
        <![CDATA[<p>Are you still defining IAM policies using heredoc syntax (<code>&lt;&lt;EOF ... EOF</code>) or <code>jsonencode()</code>? You can do bett]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <category domain="https://cloudonaut.io/tag/terraform/">terraform</category>
      <guid isPermaLink="true">https://cloudonaut.io/defining-iam-policies-with-terraform/</guid>
      <pubDate>Wed, 31 Mar 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Are you still defining IAM policies using heredoc syntax (<code>&lt;&lt;EOF ... EOF</code>) or <code>jsonencode()</code>? You can do better! As a result, <code>terraform validate</code> can tell you about typos before you apply them, and you get better auto-complete support from your IDE. Read on to learn how to define IAM policies in Terraform safely.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/productivity@730w.webp 730w, /images/2021/03/productivity@730w2x.webp 1460w, /images/2021/03/productivity@610w.webp 610w, /images/2021/03/productivity@610w2x.webp 1220w, /images/2021/03/productivity@450w.webp 450w, /images/2021/03/productivity@450w2x.webp 900w, /images/2021/03/productivity@330w.webp 330w, /images/2021/03/productivity@330w2x.webp 660w, /images/2021/03/productivity@545w.webp 545w, /images/2021/03/productivity@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/productivity@730w.jpg 730w, /images/2021/03/productivity@730w2x.jpg 1460w, /images/2021/03/productivity@610w.jpg 610w, /images/2021/03/productivity@610w2x.jpg 1220w, /images/2021/03/productivity@450w.jpg 450w, /images/2021/03/productivity@450w2x.jpg 900w, /images/2021/03/productivity@330w.jpg 330w, /images/2021/03/productivity@330w2x.jpg 660w, /images/2021/03/productivity@545w.jpg 545w, /images/2021/03/productivity@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/productivity.jpg" alt="Defining IAM Policies with Terraform" title="Defining IAM Policies with Terraform"></picture></p><p>When looking at Terraform code I still see the following two ways to define IAM policies:</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_iam_policy&quot; &quot;inline&quot; &#123;</span><br><span class="line">  name        = &quot;tf-inline&quot;</span><br><span class="line">  policy = &lt;&lt;EOF</span><br><span class="line">&#123;</span><br><span class="line">  &quot;Version&quot;: &quot;2012-10-17&quot;,</span><br><span class="line">  &quot;Statement&quot;: [</span><br><span class="line">    &#123;</span><br><span class="line">      &quot;Action&quot;: [</span><br><span class="line">        &quot;s3:GetObject&quot;,</span><br><span class="line">        &quot;s3:PutObject&quot;</span><br><span class="line">      ],</span><br><span class="line">      &quot;Effect&quot;: &quot;Allow&quot;,</span><br><span class="line">      &quot;Resource&quot;: &quot;$&#123;aws_s3_bucket.example.arn&#125;/*&quot;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line">EOF</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The second approach looks like this:</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_iam_policy&quot; &quot;jsonencode&quot; &#123;</span><br><span class="line">  name        = &quot;tf-jsonencode&quot;</span><br><span class="line">  policy = jsonencode(&#123;</span><br><span class="line">    Version = &quot;2012-10-17&quot;</span><br><span class="line">    Statement = [</span><br><span class="line">      &#123;</span><br><span class="line">        Effect   = &quot;Allow&quot;</span><br><span class="line">        Action   = [</span><br><span class="line">          &quot;s3:GetObject&quot;,</span><br><span class="line">          &quot;s3:PutObject&quot;</span><br><span class="line">        ]</span><br><span class="line">        Resource = [</span><br><span class="line">          &quot;$&#123;aws_s3_bucket.example.arn&#125;/*&quot;</span><br><span class="line">        ]</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The problem with both approaches: If your policy is malformed, you have to <code>terraform apply</code> before you realize the mistake. Besides that, your IDE’s auto-complete can not help you much when using those approaches.</p><p>How can we do better? The following video demonstrates using the data source <code>aws_iam_policy_document</code>.  This way, Terraform can validate your IAM policy (at least from a structural perspective), and your IDE can do a much better job of increasing your productivity.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=cI9yoJ0qV8Q">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/cI9yoJ0qV8Q" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_iam_policy&quot; &quot;policydocument&quot; &#123;</span><br><span class="line">  <span class="type">name</span>        = &quot;tf-policydocument&quot;</span><br><span class="line">  <span class="keyword">policy</span>      = data.aws_iam_policy_document.example.json</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">data &quot;aws_iam_policy_document&quot; &quot;example&quot; &#123;</span><br><span class="line">  <span class="keyword">statement</span> &#123;</span><br><span class="line">    effect = &quot;Allow&quot;</span><br><span class="line">    actions = [</span><br><span class="line">      &quot;s3:ListBucket&quot;</span><br><span class="line">    ]</span><br><span class="line">    resources = [</span><br><span class="line">      aws_s3_bucket.example.arn</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">statement</span> &#123;</span><br><span class="line">    effect = &quot;Allow&quot;</span><br><span class="line">    actions = [</span><br><span class="line">      &quot;s3:GetObject&quot;,</span><br><span class="line">      &quot;s3:PutObject&quot;</span><br><span class="line">    ]</span><br><span class="line">    resources = [</span><br><span class="line">      &quot;$&#123;aws_s3_bucket.example.arn&#125;/*&quot;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>Managing application secrets: SSM Parameter Store vs. Secrets Manager</title>
      <link>https://cloudonaut.io/managing-application-secrets-ssm-parameter-store-vs-secrets-manager/</link>
      <description>
        <![CDATA[<p>Many applications interact with external or internal systems like databases or REST APIs. When your application talks to another system,]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/parameter-store/">parameter-store</category>
      <category domain="https://cloudonaut.io/tag/ssm/">ssm</category>
      <category domain="https://cloudonaut.io/tag/secrets-manager/">secrets-manager</category>
      <guid isPermaLink="true">https://cloudonaut.io/managing-application-secrets-ssm-parameter-store-vs-secrets-manager/</guid>
      <pubDate>Tue, 30 Mar 2021 15:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Many applications interact with external or internal systems like databases or REST APIs. When your application talks to another system, it usually authenticates with a secret, e.g., an API key, username + password, or a certificate. This leads to the question: How can we safely make the secret available to our application? In this blog post, I compare two options provided by AWS: Parameter Store and Secrets Manager.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/secret@730w.webp 730w, /images/2021/03/secret@730w2x.webp 1460w, /images/2021/03/secret@610w.webp 610w, /images/2021/03/secret@610w2x.webp 1220w, /images/2021/03/secret@450w.webp 450w, /images/2021/03/secret@450w2x.webp 900w, /images/2021/03/secret@330w.webp 330w, /images/2021/03/secret@330w2x.webp 660w, /images/2021/03/secret@545w.webp 545w, /images/2021/03/secret@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/secret@730w.jpg 730w, /images/2021/03/secret@730w2x.jpg 1460w, /images/2021/03/secret@610w.jpg 610w, /images/2021/03/secret@610w2x.jpg 1220w, /images/2021/03/secret@450w.jpg 450w, /images/2021/03/secret@450w2x.jpg 900w, /images/2021/03/secret@330w.jpg 330w, /images/2021/03/secret@330w2x.jpg 660w, /images/2021/03/secret@545w.jpg 545w, /images/2021/03/secret@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/secret.jpg" alt="Managing application secrets" title="Managing application secrets"></picture></p><p>When our application starts, the needed secrets are fetched at runtime from a secure system. But we need to authenticate with the secure system before we can get the secrets. Does this lead to a chicken and egg problem? Not if you use an AWS service where you can authenticate with AWS credentials. Luckily, AWS provides short-lived credentials to our application via the Metadata service (e.g., EC2 &amp; Fargate) or environment variables (e.g., Lambda). With the short-lived AWS credentials, we can reach out to fetch the secrets.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/secret-retrieval@730w.webp 730w, /images/2021/03/secret-retrieval@730w2x.webp 1460w, /images/2021/03/secret-retrieval@610w.webp 610w, /images/2021/03/secret-retrieval@610w2x.webp 1220w, /images/2021/03/secret-retrieval@450w.webp 450w, /images/2021/03/secret-retrieval@450w2x.webp 900w, /images/2021/03/secret-retrieval@330w.webp 330w, /images/2021/03/secret-retrieval@330w2x.webp 660w, /images/2021/03/secret-retrieval@545w.webp 545w, /images/2021/03/secret-retrieval@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/secret-retrieval@730w.png 730w, /images/2021/03/secret-retrieval@730w2x.png 1460w, /images/2021/03/secret-retrieval@610w.png 610w, /images/2021/03/secret-retrieval@610w2x.png 1220w, /images/2021/03/secret-retrieval@450w.png 450w, /images/2021/03/secret-retrieval@450w2x.png 900w, /images/2021/03/secret-retrieval@330w.png 330w, /images/2021/03/secret-retrieval@330w2x.png 660w, /images/2021/03/secret-retrieval@545w.png 545w, /images/2021/03/secret-retrieval@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/secret-retrieval.png" alt="Secret retrieval" title="Secret retrieval"></picture></p><p>AWS provides two services to store and retrieve secrets: <a href="https://aws.amazon.com/secrets-manager/" target="_blank" rel="noopener">AWS Secrets Manager</a> and <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html" target="_blank" rel="noopener">AWS SSM Parameter Store</a>. I compare both services in the following.</p><h2 id="Secrets-Manager"><a href="#Secrets-Manager" class="headerlink" title="Secrets Manager"></a>Secrets Manager</h2><p><a href="https://aws.amazon.com/secrets-manager/" target="_blank" rel="noopener">AWS Secrets Manager</a> is purpose build to store and retrieve secrets. We can restrict access to secrets with identity-based IAM policies (attached to IAM users and roles) and resource-based IAM policies attached to the secret directly. This allows you to share secrets with other AWS accounts as well.</p><p>A great feature that is poorly implemented is secret rotation. Once you define a secret, you also have to develop a process to rotate the secret from time to time (AWS recommends rotating secrets every 30 days). Rotation can be done in different ways. My preferred approach is this:</p><ol><li>Create a new secret (e.g., add database user) and store the value</li><li>Update the <code>latest</code> label to point to the new secret version</li><li>Wait for all applications to retrieve the new secret version (e.g., retrieve the secret every 12 hours)</li><li>Delete the old secret (e.g., remove database user)</li></ol><p>Unfortunately, not all systems support this kind of rotation. Sometimes we can only generate a single API key. If you still want to rotate the secret, you will risk a short downtime. AWS provides a set of Lambda function templates to rotate secrets for RDS, Redshift, and DocumntDB. But keep in mind that the rotation logic runs in your AWS account, so you are responsible if things go wrong.</p><p>Another handy feature of Secrets Manager are <a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/create-manage-multi-region-secrets.html" target="_blank" rel="noopener">multi-region secrets</a> to replicate secrets into multiple regions.</p><p>Finally, I want to mention <a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/use-client-side-caching.html" target="_blank" rel="noopener">AWS provided caching libs</a> to lower the costs and improve performance.</p><h2 id="Parameter-Store"><a href="#Parameter-Store" class="headerlink" title="Parameter Store"></a>Parameter Store</h2><p>Parameter Store is part of <a href="https://aws.amazon.com/systems-manager/" target="_blank" rel="noopener">AWS Systems Manager</a>. Parameters can be secrets or plain-text values. By default, Parameter Store does not support resource-based IAM policies. By using a KMS customer-managed CMK for encryption of the secret, you can add a resource-based IAM policy. The resource-based IAM policy of the KMS key controls access to the key and, therefore, to the parameter value.</p><p>A useful feature of Parameter Store are <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-policies.html" target="_blank" rel="noopener">parameter policies</a> to:</p><ul><li>Delete a parameter at a specific date</li><li>Send a notification if a parameter expires</li><li>Send a notification if a parameter was not changed for n days</li></ul><p>The notifications are delivered via <a href="https://aws.amazon.com/eventbridge/" target="_blank" rel="noopener">Amazon EventBridge</a> as well as a notification if a parameter changes. I used the parameter change notification in the past to trigger pipelines if a parameter value changed.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>The following table shows the differences between Secrets Manager and Parameter Store.</p><div class="table-responsive"><table class="table table-striped"><thead><tr><th></th><th>Secrets Manager</th><th>Parameter Store</th></tr></thead><tbody><tr><td>Pricing</td><td><p>$0.40 &#x2F; month &amp; $0.05 &#x2F; 10,000 API calls</p></td><td><p>free</p></td></tr><tr><td>Retrivals per second</td><td><p>5,000</p></td><td><p>0 (or 3,000)</p></td></tr><tr><td>Versions & Labels</td><td><p>✅ Yes</p></td><td><p>✅ Yes</p></td></tr><tr><td>Resource-based IAM policy</td><td><p>✅ Yes</p></td><td><p>❌ No (workaround: KMS CMK key policy)</p></td></tr><tr><td>Deletion protection</td><td><p>Schedule for deletion</p></td><td><p>❌ No</p></td></tr><tr><td>Secret Rotation</td><td><p>Lambda templates</p></td><td><p>❌ No</p></td></tr><tr><td>Multi-Region replication</td><td><p>✅ Yes</p></td><td><p>❌ No</p></td></tr><tr><td>Payload limit</td><td><p>~65KB</p></td><td><p>4KB (or 8KB)</p></td></tr><tr><td>ABAC (tag-based) </td><td><p>✅ Yes</p></td><td><p>✅ Yes</p></td></tr><tr><td>Auditing (CloudTrail)</td><td><p>✅ Yes</p></td><td><p>✅ Yes</p></td></tr><tr><td>EventBridge integration</td><td><p>❌ No</p></td><td><p>✅ Yes</p></td></tr><tr><td>Lifecycle policies</td><td><p>❌ No</p></td><td><p>✅ Yes</p></td></tr><tr><td>CloudFormation usage</td><td><p>✅ Yes</p></td><td><p>✅ Yes</p></td></tr><tr><td>Terraform usage</td><td><p>secret is leaked in the state file</p></td><td><p>secret is leaked in the state file</p></td></tr></tbody></table></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>What Architects Need to Know About Networking on AWS</title>
      <link>https://cloudonaut.io/what-architects-need-to-know-about-networking-on-aws/</link>
      <description>
        <![CDATA[<p>As an architect, you may not have thought too much about the network management before. At least that’s how it used to be for me. But sin]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <guid isPermaLink="true">https://cloudonaut.io/what-architects-need-to-know-about-networking-on-aws/</guid>
      <pubDate>Thu, 25 Mar 2021 15:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>As an architect, you may not have thought too much about the network management before. At least that’s how it used to be for me. But since I’ve been designing architectures for AWS, network structure has become much more important to me.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/networking@730w.webp 730w, /images/2021/03/networking@730w2x.webp 1460w, /images/2021/03/networking@610w.webp 610w, /images/2021/03/networking@610w2x.webp 1220w, /images/2021/03/networking@450w.webp 450w, /images/2021/03/networking@450w2x.webp 900w, /images/2021/03/networking@330w.webp 330w, /images/2021/03/networking@330w2x.webp 660w, /images/2021/03/networking@545w.webp 545w, /images/2021/03/networking@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/networking@730w.jpg 730w, /images/2021/03/networking@730w2x.jpg 1460w, /images/2021/03/networking@610w.jpg 610w, /images/2021/03/networking@610w2x.jpg 1220w, /images/2021/03/networking@450w.jpg 450w, /images/2021/03/networking@450w2x.jpg 900w, /images/2021/03/networking@330w.jpg 330w, /images/2021/03/networking@330w2x.jpg 660w, /images/2021/03/networking@545w.jpg 545w, /images/2021/03/networking@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/networking.jpg" alt="What Architects Need to Know About Networking on AWS" title="What Architects Need to Know About Networking on AWS"></picture></p><p>With Amazon VPC (Virtual Private Cloud), we have full control over our network and can adapt it to the workload’s needs, which was almost impossible on-premises. Also, the configuration of a VPC has effects on your architecture besides the networking aspect.</p><p>In the following, you will learn about all the concepts of a VPC that are important from an architect’s perspective.</p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><h2 id="VPC-and-Subnets"><a href="#VPC-and-Subnets" class="headerlink" title="VPC and Subnets"></a>VPC and Subnets</h2><p>By default, AWS provisions default VPCs in your AWS account. A default VPC allows you to spin up EC2 instances without thinking about the network at all. However, when deploying production-critical workloads, it is worth creating VPCs tailored to your needs.</p><p>To create a VPC, all you need to configure is a private IP address range. You will find some thoughts on that at the end of this blog post. A VPC consists of multiple subnets. The subnets segment the private IP address range of the VPC.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/vpc@730w.webp 730w, /images/2021/03/vpc@730w2x.webp 1460w, /images/2021/03/vpc@610w.webp 610w, /images/2021/03/vpc@610w2x.webp 1220w, /images/2021/03/vpc@450w.webp 450w, /images/2021/03/vpc@450w2x.webp 900w, /images/2021/03/vpc@330w.webp 330w, /images/2021/03/vpc@330w2x.webp 660w, /images/2021/03/vpc@545w.webp 545w, /images/2021/03/vpc@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/vpc@730w.jpg 730w, /images/2021/03/vpc@730w2x.jpg 1460w, /images/2021/03/vpc@610w.jpg 610w, /images/2021/03/vpc@610w2x.jpg 1220w, /images/2021/03/vpc@450w.jpg 450w, /images/2021/03/vpc@450w2x.jpg 900w, /images/2021/03/vpc@330w.jpg 330w, /images/2021/03/vpc@330w2x.jpg 660w, /images/2021/03/vpc@545w.jpg 545w, /images/2021/03/vpc@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/vpc.jpg" alt="VPC and Subnets" title="VPC and Subnets"></picture></p><h2 id="Routing-Table"><a href="#Routing-Table" class="headerlink" title="Routing Table"></a>Routing Table</h2><p>The default VPC routes traffic between all subnets and the Internet.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/public-subnet@730w.webp 730w, /images/2021/03/public-subnet@730w2x.webp 1460w, /images/2021/03/public-subnet@610w.webp 610w, /images/2021/03/public-subnet@610w2x.webp 1220w, /images/2021/03/public-subnet@450w.webp 450w, /images/2021/03/public-subnet@450w2x.webp 900w, /images/2021/03/public-subnet@330w.webp 330w, /images/2021/03/public-subnet@330w2x.webp 660w, /images/2021/03/public-subnet@545w.webp 545w, /images/2021/03/public-subnet@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/public-subnet@730w.jpg 730w, /images/2021/03/public-subnet@730w2x.jpg 1460w, /images/2021/03/public-subnet@610w.jpg 610w, /images/2021/03/public-subnet@610w2x.jpg 1220w, /images/2021/03/public-subnet@450w.jpg 450w, /images/2021/03/public-subnet@450w2x.jpg 900w, /images/2021/03/public-subnet@330w.jpg 330w, /images/2021/03/public-subnet@330w2x.jpg 660w, /images/2021/03/public-subnet@545w.jpg 545w, /images/2021/03/public-subnet@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/public-subnet.jpg" alt="Public subnets" title="Public subnets"></picture></p><p>By defining routing tables per subnet, you are in control of the traffic flow. For example, you could divide your VPC into public and private subnets. Only the public subnets come with a route to the Internet. Therefore, private subnets are neither reachable from the Internet nor can establish connectivity with the Internet.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/public-private-subnet@730w.webp 730w, /images/2021/03/public-private-subnet@730w2x.webp 1460w, /images/2021/03/public-private-subnet@610w.webp 610w, /images/2021/03/public-private-subnet@610w2x.webp 1220w, /images/2021/03/public-private-subnet@450w.webp 450w, /images/2021/03/public-private-subnet@450w2x.webp 900w, /images/2021/03/public-private-subnet@330w.webp 330w, /images/2021/03/public-private-subnet@330w2x.webp 660w, /images/2021/03/public-private-subnet@545w.webp 545w, /images/2021/03/public-private-subnet@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/public-private-subnet@730w.jpg 730w, /images/2021/03/public-private-subnet@730w2x.jpg 1460w, /images/2021/03/public-private-subnet@610w.jpg 610w, /images/2021/03/public-private-subnet@610w2x.jpg 1220w, /images/2021/03/public-private-subnet@450w.jpg 450w, /images/2021/03/public-private-subnet@450w2x.jpg 900w, /images/2021/03/public-private-subnet@330w.jpg 330w, /images/2021/03/public-private-subnet@330w2x.jpg 660w, /images/2021/03/public-private-subnet@545w.jpg 545w, /images/2021/03/public-private-subnet@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/public-private-subnet.jpg" alt="Public and private subnets" title="Public and private subnets"></picture></p><p>Additionally, it is common to define a separate zone connected with your on-premises network. For example, your VPC could consist of a public subnet with Internet connectivity, a subnet peered with your on-premises network, as well as a private subnet without a route to the Internet and on-premises network.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/public-hybrid-private-subnet@730w.webp 730w, /images/2021/03/public-hybrid-private-subnet@730w2x.webp 1460w, /images/2021/03/public-hybrid-private-subnet@610w.webp 610w, /images/2021/03/public-hybrid-private-subnet@610w2x.webp 1220w, /images/2021/03/public-hybrid-private-subnet@450w.webp 450w, /images/2021/03/public-hybrid-private-subnet@450w2x.webp 900w, /images/2021/03/public-hybrid-private-subnet@330w.webp 330w, /images/2021/03/public-hybrid-private-subnet@330w2x.webp 660w, /images/2021/03/public-hybrid-private-subnet@545w.webp 545w, /images/2021/03/public-hybrid-private-subnet@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/public-hybrid-private-subnet@730w.jpg 730w, /images/2021/03/public-hybrid-private-subnet@730w2x.jpg 1460w, /images/2021/03/public-hybrid-private-subnet@610w.jpg 610w, /images/2021/03/public-hybrid-private-subnet@610w2x.jpg 1220w, /images/2021/03/public-hybrid-private-subnet@450w.jpg 450w, /images/2021/03/public-hybrid-private-subnet@450w2x.jpg 900w, /images/2021/03/public-hybrid-private-subnet@330w.jpg 330w, /images/2021/03/public-hybrid-private-subnet@330w2x.jpg 660w, /images/2021/03/public-hybrid-private-subnet@545w.jpg 545w, /images/2021/03/public-hybrid-private-subnet@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/public-hybrid-private-subnet.jpg" alt="Public, hybrid, and private subnets" title="Public, hybrid, and private subnets"></picture></p><h2 id="Gateways-and-Endpoints"><a href="#Gateways-and-Endpoints" class="headerlink" title="Gateways and Endpoints"></a>Gateways and Endpoints</h2><p>Usually, a VPC does not exist in isolation but enables connectivity with other networks. The following list summarizes your options.</p><ul><li>An <strong>Internet Gateway</strong> ensures inbound and outbound Internet connectivity.</li><li>A <strong>NAT Gateway</strong> enables outbound Internet connectivity.</li><li>A <strong>Virtual Private Gateway</strong> routes traffic through site-to-site VPN connections or dedicated network connections (AWS Direct Connect).</li><li>A <strong>Transit Gateway</strong> enables a hub and spoke network topology.</li><li>VPC Endpoints enable connectivity to AWS services that would otherwise require outbound Internet connectivity.</li></ul><h2 id="Availability-Zone"><a href="#Availability-Zone" class="headerlink" title="Availability Zone"></a>Availability Zone</h2><p>AWS divides its global infrastructure into Regions and Availability Zones. A region consists of at least three Availability Zones. An Availability Zone itself is made up of at least one data center.</p><p>It is essential to know that there is a 1-to-1 relationship between a subnet and an availability zone. For example, if you want to distribute your workload among three availability zones to achieve high availability, you need to plan with at least three subnets.</p><p>Keep in mind that you need to create a subnet for each availability zone and network segment. For example, you need six subnets to utilize three availability zones and divide your network into public and private subnets.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/availability-zone@730w.webp 730w, /images/2021/03/availability-zone@730w2x.webp 1460w, /images/2021/03/availability-zone@610w.webp 610w, /images/2021/03/availability-zone@610w2x.webp 1220w, /images/2021/03/availability-zone@450w.webp 450w, /images/2021/03/availability-zone@450w2x.webp 900w, /images/2021/03/availability-zone@330w.webp 330w, /images/2021/03/availability-zone@330w2x.webp 660w, /images/2021/03/availability-zone@545w.webp 545w, /images/2021/03/availability-zone@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/availability-zone@730w.jpg 730w, /images/2021/03/availability-zone@730w2x.jpg 1460w, /images/2021/03/availability-zone@610w.jpg 610w, /images/2021/03/availability-zone@610w2x.jpg 1220w, /images/2021/03/availability-zone@450w.jpg 450w, /images/2021/03/availability-zone@450w2x.jpg 900w, /images/2021/03/availability-zone@330w.jpg 330w, /images/2021/03/availability-zone@330w2x.jpg 660w, /images/2021/03/availability-zone@545w.jpg 545w, /images/2021/03/availability-zone@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/availability-zone.jpg" alt="Availability Zones" title="Availability Zones"></picture></p><h2 id="Network-Access-Control-List-NACL"><a href="#Network-Access-Control-List-NACL" class="headerlink" title="Network Access Control List (NACL)"></a>Network Access Control List (NACL)</h2><p>A subnet is not only linked to a routing table but a Network Access Control List (NACL) as well. As mentioned before, traffic is routed between all subnets by default. However, using NACLs allows you to define firewall rules controlling traffic entering and leaving a subnet.</p><p>Be aware of the fact that NACLs are stateless firewall rules. Therefore, the granularity for controlling traffic is limited. Also, configuring stateless firewalls is complex.</p><h2 id="A-modern-approach-to-network-segmentation"><a href="#A-modern-approach-to-network-segmentation" class="headerlink" title="A modern approach to network segmentation"></a>A modern approach to network segmentation</h2><p>In former times, subnets and NACLs have been used to isolate workloads from each other and limit the blast radius of a security incident. However, things are different in the cloud. The most crucial difference is that machines are not static anymore and instead your infrastructure grows and shrinks automatically, making firewall rules based on IP addresses no longer sufficient. Segmenting a network into many subnets is getting harder, as you need one of those subnets per availability zone.</p><p>Luckily, there is a modern approach to control traffic within your VPC: Security Groups. A Security Group restricts inbound and outbound traffic to EC2 instances, RDS database instances, and many other resources with VPC integration. To define a firewall rule, you no longer need to specify an IP address or IP address range. Instead, it is possible to reference other Security Groups. Doing so allows you to control traffic on a fine-granular level.</p><p>In the following example, only the load balancer can connect to the EC2 instances, and only the EC2 instances can access the database—all of that without static IP addresses and IP address-based firewall rules.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/security-group@730w.webp 730w, /images/2021/03/security-group@730w2x.webp 1460w, /images/2021/03/security-group@610w.webp 610w, /images/2021/03/security-group@610w2x.webp 1220w, /images/2021/03/security-group@450w.webp 450w, /images/2021/03/security-group@450w2x.webp 900w, /images/2021/03/security-group@330w.webp 330w, /images/2021/03/security-group@330w2x.webp 660w, /images/2021/03/security-group@545w.webp 545w, /images/2021/03/security-group@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/security-group@730w.jpg 730w, /images/2021/03/security-group@730w2x.jpg 1460w, /images/2021/03/security-group@610w.jpg 610w, /images/2021/03/security-group@610w2x.jpg 1220w, /images/2021/03/security-group@450w.jpg 450w, /images/2021/03/security-group@450w2x.jpg 900w, /images/2021/03/security-group@330w.jpg 330w, /images/2021/03/security-group@330w2x.jpg 660w, /images/2021/03/security-group@545w.jpg 545w, /images/2021/03/security-group@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/security-group.jpg" alt="Security Groups" title="Security Groups"></picture></p><h2 id="Plan-ahead"><a href="#Plan-ahead" class="headerlink" title="Plan ahead"></a>Plan ahead</h2><p>When designing the architecture, make sure to plan ahead from a network perspective. There are two critical questions to consider.</p><p>First, estimate the number of private IP addresses the infrastructure will require within the following years. Then, breakdown those numbers per subnet. Compare those numbers with the available IP addresses within your VPC and subnets. Keep in mind that AWS reserves 5 IP addresses per subnet for internal use.</p><p>Second, make sure that the IP address range for your VPC does not overlap with any networks that you might need to peer with in the future. If necessary, ask the ones responsible for managing IP address ranges in your organization for help. Even if you are not planning to peer with any networks now, this requirement will likely come up in the future.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>As an architect for cloud infrastructures, it is necessary to know about the networking concepts on AWS. Keep in mind that some of the network-level decisions impact other areas such as Availability Zones, for example.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Cognito Under the Hood</title>
      <link>https://cloudonaut.io/cognito-under-the-hood/</link>
      <description>
        <![CDATA[<p>Have you ever implemented a user database and authentication layer yourself? There are many things to get right: Hashing and salting pass]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/apigateway/">apigateway</category>
      <category domain="https://cloudonaut.io/tag/alb/">alb</category>
      <category domain="https://cloudonaut.io/tag/cognito/">cognito</category>
      <guid isPermaLink="true">https://cloudonaut.io/cognito-under-the-hood/</guid>
      <pubDate>Wed, 24 Mar 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Have you ever implemented a user database and authentication layer yourself? There are many things to get right: Hashing and salting passwords, multi-factor authentication, brute force attacks, and many more. That’s why I recommend using a production-ready service instead of building authentication yourself. That’s what <a href="https://aws.amazon.com/cognito/" target="_blank" rel="noopener">Amazon Cognito</a> is all about.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/login@730w.webp 730w, /images/2021/03/login@730w2x.webp 1460w, /images/2021/03/login@610w.webp 610w, /images/2021/03/login@610w2x.webp 1220w, /images/2021/03/login@450w.webp 450w, /images/2021/03/login@450w2x.webp 900w, /images/2021/03/login@330w.webp 330w, /images/2021/03/login@330w2x.webp 660w, /images/2021/03/login@545w.webp 545w, /images/2021/03/login@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/login@730w.jpg 730w, /images/2021/03/login@730w2x.jpg 1460w, /images/2021/03/login@610w.jpg 610w, /images/2021/03/login@610w2x.jpg 1220w, /images/2021/03/login@450w.jpg 450w, /images/2021/03/login@450w2x.jpg 900w, /images/2021/03/login@330w.jpg 330w, /images/2021/03/login@330w2x.jpg 660w, /images/2021/03/login@545w.jpg 545w, /images/2021/03/login@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/login.jpg" alt="Cognito Under the Hood" title="Cognito Under the Hood"></picture></p><p>Let me start with typical use cases for Cognito:</p><h2 id="Serverless-app-with-API-Gateway"><a href="#Serverless-app-with-API-Gateway" class="headerlink" title="Serverless app with API Gateway"></a>Serverless app with API Gateway</h2><p>A typical Serverless application uses Cognito User Pools for authentication and authorization.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/api-gateway-user-pool@730w.webp 730w, /images/2021/03/api-gateway-user-pool@730w2x.webp 1460w, /images/2021/03/api-gateway-user-pool@610w.webp 610w, /images/2021/03/api-gateway-user-pool@610w2x.webp 1220w, /images/2021/03/api-gateway-user-pool@450w.webp 450w, /images/2021/03/api-gateway-user-pool@450w2x.webp 900w, /images/2021/03/api-gateway-user-pool@330w.webp 330w, /images/2021/03/api-gateway-user-pool@330w2x.webp 660w, /images/2021/03/api-gateway-user-pool@545w.webp 545w, /images/2021/03/api-gateway-user-pool@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/api-gateway-user-pool@730w.jpg 730w, /images/2021/03/api-gateway-user-pool@730w2x.jpg 1460w, /images/2021/03/api-gateway-user-pool@610w.jpg 610w, /images/2021/03/api-gateway-user-pool@610w2x.jpg 1220w, /images/2021/03/api-gateway-user-pool@450w.jpg 450w, /images/2021/03/api-gateway-user-pool@450w2x.jpg 900w, /images/2021/03/api-gateway-user-pool@330w.jpg 330w, /images/2021/03/api-gateway-user-pool@330w2x.jpg 660w, /images/2021/03/api-gateway-user-pool@545w.jpg 545w, /images/2021/03/api-gateway-user-pool@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/api-gateway-user-pool.jpg" alt="Serverless app with API Gateway" title="Serverless app with API Gateway"></picture></p><ol><li>The client authenticates with the User Pool.</li><li>The client sends the identity or access token as a header to the API Gateway.</li><li>The API Gateway verifies the token and grants access.</li><li>The API Gateway adds information from the identity&#x2F;access token to the context when invoking a Lambda function.</li></ol><h2 id="Obtain-temporary-AWS-credentials"><a href="#Obtain-temporary-AWS-credentials" class="headerlink" title="Obtain temporary AWS credentials"></a>Obtain temporary AWS credentials</h2><p>Cognito Identity Pool allows federated identities to access AWS resources by issuing temporary AWS access credentials. Users can use those credentials to interact with AWS APIs directly (e.g., AppSync, S3, DynamoDB, …). That allows clients to access data stores without a backend.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/temp-aws-credentials@730w.webp 730w, /images/2021/03/temp-aws-credentials@730w2x.webp 1460w, /images/2021/03/temp-aws-credentials@610w.webp 610w, /images/2021/03/temp-aws-credentials@610w2x.webp 1220w, /images/2021/03/temp-aws-credentials@450w.webp 450w, /images/2021/03/temp-aws-credentials@450w2x.webp 900w, /images/2021/03/temp-aws-credentials@330w.webp 330w, /images/2021/03/temp-aws-credentials@330w2x.webp 660w, /images/2021/03/temp-aws-credentials@545w.webp 545w, /images/2021/03/temp-aws-credentials@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/temp-aws-credentials@730w.jpg 730w, /images/2021/03/temp-aws-credentials@730w2x.jpg 1460w, /images/2021/03/temp-aws-credentials@610w.jpg 610w, /images/2021/03/temp-aws-credentials@610w2x.jpg 1220w, /images/2021/03/temp-aws-credentials@450w.jpg 450w, /images/2021/03/temp-aws-credentials@450w2x.jpg 900w, /images/2021/03/temp-aws-credentials@330w.jpg 330w, /images/2021/03/temp-aws-credentials@330w2x.jpg 660w, /images/2021/03/temp-aws-credentials@545w.jpg 545w, /images/2021/03/temp-aws-credentials@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/temp-aws-credentials.jpg" alt="Obtain temporary AWS credentials" title="Obtain temporary AWS credentials"></picture></p><h2 id="Cloud-native-app-with-ALB"><a href="#Cloud-native-app-with-ALB" class="headerlink" title="Cloud-native app with ALB"></a>Cloud-native app with ALB</h2><p>You can add authentication to any cloud-native app that sits behind an ALB. ALBs support authentication&#x2F;authorization based on Cognito User Pools.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/alb-user-pool@730w.webp 730w, /images/2021/03/alb-user-pool@730w2x.webp 1460w, /images/2021/03/alb-user-pool@610w.webp 610w, /images/2021/03/alb-user-pool@610w2x.webp 1220w, /images/2021/03/alb-user-pool@450w.webp 450w, /images/2021/03/alb-user-pool@450w2x.webp 900w, /images/2021/03/alb-user-pool@330w.webp 330w, /images/2021/03/alb-user-pool@330w2x.webp 660w, /images/2021/03/alb-user-pool@545w.webp 545w, /images/2021/03/alb-user-pool@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/alb-user-pool@730w.jpg 730w, /images/2021/03/alb-user-pool@730w2x.jpg 1460w, /images/2021/03/alb-user-pool@610w.jpg 610w, /images/2021/03/alb-user-pool@610w2x.jpg 1220w, /images/2021/03/alb-user-pool@450w.jpg 450w, /images/2021/03/alb-user-pool@450w2x.jpg 900w, /images/2021/03/alb-user-pool@330w.jpg 330w, /images/2021/03/alb-user-pool@330w2x.jpg 660w, /images/2021/03/alb-user-pool@545w.jpg 545w, /images/2021/03/alb-user-pool@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/alb-user-pool.jpg" alt="Cloud native app with ALB" title="Cloud native app with ALB"></picture></p><p>We use it to add a layer of security for traditional tools like Jenkins, phpMyAdmin, etc.<br>You can find a working example here: <a href="https://github.com/cfn-modules/docs/tree/master/examples/fargate-alb-auth-cognito" target="_blank" rel="noopener">https://github.com/cfn-modules/docs/tree/master/examples/fargate-alb-auth-cognito</a></p><p>The following video covers:</p><ul><li>When to use AWS Cognito?</li><li>Demo: ALB + Cognito User Pool</li><li>User Pool vs. Identity Pool</li><li>Pitfall: Backup your most valuable asset!</li></ul><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=XWrzRwSEXl4">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/XWrzRwSEXl4" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>The following table helps you to decide which option to choose.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th>User Pool</th><th>Identity Pool</th></tr></thead><tbody><tr><td>Built-in User Database</td><td>✅ Yes</td><td>❌ No</td></tr><tr><td>Grant access to AWS resources directly</td><td>✅ Yes in combination with an Identity Pool</td><td>✅ Yes</td></tr><tr><td>Social Login</td><td>Google, Facebook, Amazon, Apple</td><td>Google, Facebook, Amazon, Apple</td></tr><tr><td>SAML</td><td>✅ Yes</td><td>✅ Yes</td></tr><tr><td>OpenID Connect</td><td>✅ Yes</td><td>✅ Yes</td></tr><tr><td>Hosted UI</td><td>✅ Yes</td><td>❌ No</td></tr><tr><td>Costs</td><td>$0.0055 per Monthly Active User (MAU)</td><td>Free</td></tr></tbody></table>]]>
      </content:encoded>
    </item>
    <item>
      <title>DNSSEC with Route 53: Protecting the core of the Internet</title>
      <link>https://cloudonaut.io/dnssec-with-route53-protecting-the-core-of-the-internet/</link>
      <description>
        <![CDATA[<p>The Internet relies on DNS. This makes it all the more important to do everything possible to protect the global DNS infrastructure from]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/route53/">route53</category>
      <guid isPermaLink="true">https://cloudonaut.io/dnssec-with-route53-protecting-the-core-of-the-internet/</guid>
      <pubDate>Mon, 22 Mar 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The Internet relies on DNS. This makes it all the more important to do everything possible to protect the global DNS infrastructure from attacks. Andreas explains how DNSSEC protects from DNS spoofing. During the demo you will learn how to enable DNSSEC for your domains by using Route 53.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/logbook@730w.webp 730w, /images/2021/03/logbook@730w2x.webp 1460w, /images/2021/03/logbook@610w.webp 610w, /images/2021/03/logbook@610w2x.webp 1220w, /images/2021/03/logbook@450w.webp 450w, /images/2021/03/logbook@450w2x.webp 900w, /images/2021/03/logbook@330w.webp 330w, /images/2021/03/logbook@330w2x.webp 660w, /images/2021/03/logbook@545w.webp 545w, /images/2021/03/logbook@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/logbook@730w.jpg 730w, /images/2021/03/logbook@730w2x.jpg 1460w, /images/2021/03/logbook@610w.jpg 610w, /images/2021/03/logbook@610w2x.jpg 1220w, /images/2021/03/logbook@450w.jpg 450w, /images/2021/03/logbook@450w2x.jpg 900w, /images/2021/03/logbook@330w.jpg 330w, /images/2021/03/logbook@330w2x.jpg 660w, /images/2021/03/logbook@545w.jpg 545w, /images/2021/03/logbook@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/logbook.jpg" alt="DNSSEC with Route 53: Protecting the core of the Internet" title="DNSSEC with Route 53: Protecting the core of the Internet"></picture></p><p>What you will learn by watching the video and following the demo?</p><ul><li>Protect from DNS spoofing!</li><li>What is DNSSEC?</li><li>How to enable DNSSEC with Route 53?</li><li>Pros and Cons of DNSSEC</li></ul><p>Enjoy the video!</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=lT7P6hwTbik">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/lT7P6hwTbik" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p>Why is DNSSEC important? Because, DNSSEC protects from the following attach, called DNS spoofing.</p><ol><li>Attacker injects DNS records into DNS server&#x2F;resolver.</li><li>User resolves domain name, DNS server responds with “wrong” IP address.</li><li>User sends requests to “wrong” server.</li><li>Attacker steals password, reads sensitive information, …</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/dns-spoofing@730w.webp 730w, /images/2021/03/dns-spoofing@730w2x.webp 1460w, /images/2021/03/dns-spoofing@610w.webp 610w, /images/2021/03/dns-spoofing@610w2x.webp 1220w, /images/2021/03/dns-spoofing@450w.webp 450w, /images/2021/03/dns-spoofing@450w2x.webp 900w, /images/2021/03/dns-spoofing@330w.webp 330w, /images/2021/03/dns-spoofing@330w2x.webp 660w, /images/2021/03/dns-spoofing@545w.webp 545w, /images/2021/03/dns-spoofing@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/dns-spoofing@730w.png 730w, /images/2021/03/dns-spoofing@730w2x.png 1460w, /images/2021/03/dns-spoofing@610w.png 610w, /images/2021/03/dns-spoofing@610w2x.png 1220w, /images/2021/03/dns-spoofing@450w.png 450w, /images/2021/03/dns-spoofing@450w2x.png 900w, /images/2021/03/dns-spoofing@330w.png 330w, /images/2021/03/dns-spoofing@330w2x.png 660w, /images/2021/03/dns-spoofing@545w.png 545w, /images/2021/03/dns-spoofing@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/dns-spoofing.png" alt="DNS Spoofing" title="DNS Spoofing"></picture></p><p>The CloudFormation templates used in the video are available at <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">https://github.com/widdix/aws-cf-templates</a></p><p>I’ve used the following commands to deploy DNSSEC to <code>us-east-1</code>. Make sure to modify the stack names and parameters before using the commands.</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">aws cloudformation deploy --stack-name cloudonautio-key --template-file security/kms-key.yaml --parameter-overrides <span class="attribute">Service</span>=dnssec-route53 <span class="attribute">KeySpec</span>=ECC_NIST_P256 <span class="attribute">KeyUsage</span>=SIGN_VERIFY</span><br><span class="line">aws cloudformation deploy --stack-name cloudonautio-hz --template-file vpc/zone-legacy.yaml --parameter-overrides <span class="attribute">HostedZoneName</span>=cloudonaut.io <span class="attribute">HostedZoneId</span>=Z18W2IF733UZVC</span><br><span class="line">aws cloudformation deploy --stack-name cloudonautio-dnssec --template-file vpc/zone-dnssec.yaml --parameter-overrides <span class="attribute">ParentZoneStack</span>=cloudonautio-hz <span class="attribute">ParentKmsKeyStack</span>=cloudonautio-key <span class="attribute">ParentAlertStack</span>=operations-alert</span><br></pre></td></tr></table></figure><p>When it comes to DNS, I’m using the book <a href="https://www.oreilly.com/library/view/dns-and-bind/0596100574/" target="_blank" rel="noopener">DNS and BIND</a> to learn about the technical details.</p><p>A few links discussing DNSSEC:</p><ul><li><a href="https://sockpuppet.org/blog/2015/01/15/against-dnssec/" target="_blank" rel="noopener">Against DNSSEC</a></li><li><a href="https://blog.cloudflare.com/dnssec-an-introduction/" target="_blank" rel="noopener">DNSSEC: An Introduction</a></li><li><a href="https://blog.apnic.net/2019/03/14/the-state-of-dnssec-validation/" target="_blank" rel="noopener">The state of DNSSEC validation</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>Parental Leave (Andreas)</title>
      <link>https://cloudonaut.io/parental-leave-andreas/</link>
      <description>
        <![CDATA[<p>This is the last thing on my checklist before I leave for nine months. Until January 2022, I take on the joys and responsibilities of chi]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/parental-leave-andreas/</guid>
      <pubDate>Fri, 19 Mar 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>This is the last thing on my checklist before I leave for nine months. Until January 2022, I take on the joys and responsibilities of child care.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/children@730w.webp 730w, /images/2021/03/children@730w2x.webp 1460w, /images/2021/03/children@610w.webp 610w, /images/2021/03/children@610w2x.webp 1220w, /images/2021/03/children@450w.webp 450w, /images/2021/03/children@450w2x.webp 900w, /images/2021/03/children@330w.webp 330w, /images/2021/03/children@330w2x.webp 660w, /images/2021/03/children@545w.webp 545w, /images/2021/03/children@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/children@730w.jpg 730w, /images/2021/03/children@730w2x.jpg 1460w, /images/2021/03/children@610w.jpg 610w, /images/2021/03/children@610w2x.jpg 1220w, /images/2021/03/children@450w.jpg 450w, /images/2021/03/children@450w2x.jpg 900w, /images/2021/03/children@330w.jpg 330w, /images/2021/03/children@330w2x.jpg 660w, /images/2021/03/children@545w.jpg 545w, /images/2021/03/children@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/children.jpg" alt="Parental Leave" title="Parental Leave"></picture></p><h2 id="Having-children-in-Germany"><a href="#Having-children-in-Germany" class="headerlink" title="Having children in Germany"></a>Having children in Germany</h2><p>My wife and I have two children who are three years and nine months old.</p><p>In Germany, the state compensates parts of the salary for mothers and fathers taking care of their little children for up to 14 months - see <a href="https://en.wikipedia.org/wiki/Elterngeld" target="_blank" rel="noopener">Elterngeld</a> to learn more. Typically, women take 12 months off - men take off two months. This time, my wife and I split our parental leave in half, and both do nine months.</p><p>At the end of the year, our little one will start going to daycare. Commonly, children in Germany go to daycare from the age of 12 to 18 months.</p><h2 id="About-the-Business"><a href="#About-the-Business" class="headerlink" title="About the Business"></a>About the Business</h2><p>What happens to our business while I’m on parental leave? I’m fortunate to run a business together with my brother. Michael will take over a lot of my duties. For example, I’ve handed over some of my consulting clients to Michael. But we also found freelancers and consulting firms to take over some of our clients.</p><p>We will also reduce the content published on cloudonaut: 4-5 videos, two blog posts, and one podcast episode per month.</p><p>On top of that, we hired Steffen and Wilhelm to produce videos and manage our social media presence.</p><p>We are also investing heavily into cloudonaut plus, <a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV - Antivirus for Amazon S3</a>, and <a href="https://marbot.io/" target="_blank" rel="noopener">marbot</a> and hope to grow our monthly recurring revenue significantly until the end of the year.</p><h2 id="I’ll-be-back"><a href="#I’ll-be-back" class="headerlink" title="I’ll be back!"></a>I’ll be back!</h2><p>I’m looking forward to visiting playgrounds, swimming, laughing, and playing with my children. Also, I’m missing working together with Michael already. I’ll be back in January 2022. I wish you all a good time! 👋</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>A Deep Dive into AWS CloudTrail</title>
      <link>https://cloudonaut.io/deep-dive-aws-cloudtrail/</link>
      <description>
        <![CDATA[<p>Who made changes to sensitive parts of your cloud infrastructure? Capture audit logs with AWS CloudTrail. Learn how to analyze the audit]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/cloudtrail/">cloudtrail</category>
      <guid isPermaLink="true">https://cloudonaut.io/deep-dive-aws-cloudtrail/</guid>
      <pubDate>Thu, 18 Mar 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Who made changes to sensitive parts of your cloud infrastructure? Capture audit logs with AWS CloudTrail. Learn how to analyze the audit logs with the help of CloudWatch Logs Insights or Athena. On top of that, we discuss how to rollout CloudTrail to all AWS accounts belonging to your organization. Last but not least, you will learn about the blind spots and how to avoid extensive costs.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/logbook@730w.webp 730w, /images/2021/03/logbook@730w2x.webp 1460w, /images/2021/03/logbook@610w.webp 610w, /images/2021/03/logbook@610w2x.webp 1220w, /images/2021/03/logbook@450w.webp 450w, /images/2021/03/logbook@450w2x.webp 900w, /images/2021/03/logbook@330w.webp 330w, /images/2021/03/logbook@330w2x.webp 660w, /images/2021/03/logbook@545w.webp 545w, /images/2021/03/logbook@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/logbook@730w.jpg 730w, /images/2021/03/logbook@730w2x.jpg 1460w, /images/2021/03/logbook@610w.jpg 610w, /images/2021/03/logbook@610w2x.jpg 1220w, /images/2021/03/logbook@450w.jpg 450w, /images/2021/03/logbook@450w2x.jpg 900w, /images/2021/03/logbook@330w.jpg 330w, /images/2021/03/logbook@330w2x.jpg 660w, /images/2021/03/logbook@545w.jpg 545w, /images/2021/03/logbook@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/logbook.jpg" alt="A Deep Dive into AWS CloudTrail" title="A Deep Dive into AWS CloudTrail"></picture></p><p>What to expect from the video?</p><ul><li>Demo: Querying audit logs with CloudWatch</li><li>Demo: Querying audit logs with Athena</li><li>Best practices for configuring CloudTrail (multi-account)</li><li>About blind spots: S3, DynamoDB, SQS, SNS, …</li><li>About extensive costs: data events are expensive</li><li>Demo: Real-time alerts (CIS AWS Foundations)</li></ul><p>Enjoy the video!</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=-a3EX758Mq8">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/-a3EX758Mq8" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p>Capturing and storing audit logs is only half of the job. It would be best if you were capable of analyzing the logs as well.</p><h2 id="Querying-audit-logs-with-CloudWatch"><a href="#Querying-audit-logs-with-CloudWatch" class="headerlink" title="Querying audit logs with CloudWatch"></a>Querying audit logs with CloudWatch</h2><p>Here are some examples of queries for CloudWatch Logs Insights.</p><h3 id="Which-regions-are-used-within-account"><a href="#Which-regions-are-used-within-account" class="headerlink" title="Which regions are used within account?"></a>Which regions are used within account?</h3><figure class="highlight gherkin"><table><tr><td class="code"><pre><span class="line">fields <span class="meta">@timestamp,</span> <span class="meta">@message</span></span><br><span class="line">|<span class="string"> stats count() by awsRegion</span></span><br><span class="line"><span class="string"></span>|<span class="string"> sort awsRegion asc</span></span><br></pre></td></tr></table></figure><h3 id="Are-IAM-users-used-to-access-resources"><a href="#Are-IAM-users-used-to-access-resources" class="headerlink" title="Are IAM users used to access resources?"></a>Are IAM users used to access resources?</h3><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">fields @<span class="type">timestamp</span>, @message</span><br><span class="line">| <span class="keyword">filter</span> userIdentity.<span class="keyword">type</span> = &quot;IAMUser&quot;</span><br><span class="line">| stats count() <span class="keyword">by</span> userIdentity.arn</span><br><span class="line">| sort awsRegion <span class="keyword">asc</span></span><br></pre></td></tr></table></figure><h3 id="Who-signed-in-through-the-AWS-Management-Console"><a href="#Who-signed-in-through-the-AWS-Management-Console" class="headerlink" title="Who signed in through the AWS Management Console?"></a>Who signed in through the AWS Management Console?</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">fields <span class="meta">@timestamp</span>, <span class="meta">@message</span></span><br><span class="line">| <span class="type">filter</span> <span class="variable">eventSource</span> <span class="operator">=</span> <span class="string">&quot;signin.amazonaws.com&quot;</span></span><br><span class="line">| stats <span class="title function_">count</span><span class="params">()</span> by userIdentity.arn</span><br></pre></td></tr></table></figure><h3 id="Which-IAM-policies-have-been-changed"><a href="#Which-IAM-policies-have-been-changed" class="headerlink" title="Which IAM policies have been changed?"></a>Which IAM policies have been changed?</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">fields <span class="meta">@timestamp</span>, <span class="meta">@message</span></span><br><span class="line">| <span class="type">filter</span> <span class="variable">eventSource</span> <span class="operator">=</span> <span class="string">&quot;iam.amazonaws.com&quot;</span> and (eventName = <span class="string">&quot;AttachGroupPolicy&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;AttachRolePolicy&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;AttachUserPolicy&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;CreatePolicy&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;CreatePolicyVersion&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;DeleteGroupPolicy&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;DeletePolicy&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;DeletePolicyVersion&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;DeleteRolePolicy&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;DeleteUserPolicy&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;DetachGroupPolicy&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;DetachRolePolicy&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;DetachUserPolicy&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;PutGroupPolicy&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;PutRolePolicy&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;PutUserPolicy&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;UpdateAssumeRolePolicy&quot;</span>)</span><br></pre></td></tr></table></figure><h3 id="Detect-changes-to-Security-Groups"><a href="#Detect-changes-to-Security-Groups" class="headerlink" title="Detect changes to Security Groups."></a>Detect changes to Security Groups.</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">fields <span class="meta">@timestamp</span>, <span class="meta">@message</span></span><br><span class="line">| <span class="type">filter</span> <span class="variable">eventSource</span> <span class="operator">=</span> <span class="string">&quot;ec2.amazonaws.com&quot;</span> and (eventName = <span class="string">&quot;AuthorizeSecurityGroupEgress&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;AuthorizeSecurityGroupIngress&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;RevokeSecurityGroupEgress&quot;</span> <span class="type">or</span> <span class="variable">eventName</span> <span class="operator">=</span> <span class="string">&quot;RevokeSecurityGroupIngress&quot;</span>)</span><br></pre></td></tr></table></figure><h3 id="Did-anyone-make-use-of-leaked-AWS-credentials"><a href="#Did-anyone-make-use-of-leaked-AWS-credentials" class="headerlink" title="Did anyone make use of leaked AWS credentials?"></a>Did anyone make use of leaked AWS credentials?</h3><figure class="highlight mel"><table><tr><td class="code"><pre><span class="line">fields @timestamp, @message</span><br><span class="line">| <span class="keyword">filter</span> userIdentity.accessKeyId = <span class="string">&#x27;AKIA36A2NNHBPCARNKJG&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="Querying-audit-logs-with-Athena"><a href="#Querying-audit-logs-with-Athena" class="headerlink" title="Querying audit logs with Athena"></a>Querying audit logs with Athena</h2><p>Besides that, Athena offers a powerful way to search through audit logs captured by CloudTrail as well.</p><h2 id="Creating-the-table-to-analyze-CloudTrail-logs"><a href="#Creating-the-table-to-analyze-CloudTrail-logs" class="headerlink" title="Creating the table to analyze CloudTrail logs"></a>Creating the table to analyze CloudTrail logs</h2><p>Make sure to replace $BUCKETNAME with the name of the S3 bucket that you are storing CloudTrail logs in. Also you need to update the list of AWS accounts near projection.account.values.</p><figure class="highlight php"><table><tr><td class="code"><pre><span class="line">CREATE EXTERNAL TABLE <span class="title function_ invoke__">cloudtrail_logs_pp</span>(</span><br><span class="line">    eventVersion STRING,</span><br><span class="line">    userIdentity STRUCT&lt;</span><br><span class="line">        <span class="attr">type</span>: STRING,</span><br><span class="line">        <span class="attr">principalId</span>: STRING,</span><br><span class="line">        <span class="attr">arn</span>: STRING,</span><br><span class="line">        <span class="attr">accountId</span>: STRING,</span><br><span class="line">        <span class="attr">invokedBy</span>: STRING,</span><br><span class="line">        <span class="attr">accessKeyId</span>: STRING,</span><br><span class="line">        <span class="attr">userName</span>: STRING,</span><br><span class="line">        <span class="attr">sessionContext</span>: STRUCT&lt;</span><br><span class="line">            <span class="attr">attributes</span>: STRUCT&lt;</span><br><span class="line">                <span class="attr">mfaAuthenticated</span>: STRING,</span><br><span class="line">                <span class="attr">creationDate</span>: STRING&gt;,</span><br><span class="line">            <span class="attr">sessionIssuer</span>: STRUCT&lt;</span><br><span class="line">                <span class="attr">type</span>: STRING,</span><br><span class="line">                <span class="attr">principalId</span>: STRING,</span><br><span class="line">                <span class="attr">arn</span>: STRING,</span><br><span class="line">                <span class="attr">accountId</span>: STRING,</span><br><span class="line">                <span class="attr">userName</span>: STRING&gt;&gt;&gt;,</span><br><span class="line">    eventTime STRING,</span><br><span class="line">    eventSource STRING,</span><br><span class="line">    eventName STRING,</span><br><span class="line">    awsRegion STRING,</span><br><span class="line">    sourceIpAddress STRING,</span><br><span class="line">    userAgent STRING,</span><br><span class="line">    errorCode STRING,</span><br><span class="line">    errorMessage STRING,</span><br><span class="line">    requestParameters STRING,</span><br><span class="line">    responseElements STRING,</span><br><span class="line">    additionalEventData STRING,</span><br><span class="line">    requestId STRING,</span><br><span class="line">    eventId STRING,</span><br><span class="line">    readOnly STRING,</span><br><span class="line">    resources ARRAY&lt;STRUCT&lt;</span><br><span class="line">        <span class="attr">arn</span>: STRING,</span><br><span class="line">        <span class="attr">accountId</span>: STRING,</span><br><span class="line">        <span class="attr">type</span>: STRING&gt;&gt;,</span><br><span class="line">    eventType STRING,</span><br><span class="line">    apiVersion STRING,</span><br><span class="line">    recipientAccountId STRING,</span><br><span class="line">    serviceEventDetails STRING,</span><br><span class="line">    sharedEventID STRING,</span><br><span class="line">    vpcEndpointId STRING</span><br><span class="line">  )</span><br><span class="line">PARTITIONED <span class="title function_ invoke__">BY</span> (</span><br><span class="line">   `timestamp` <span class="keyword">string</span>,</span><br><span class="line">   `account` <span class="keyword">string</span>,</span><br><span class="line">   `region` <span class="keyword">string</span>)</span><br><span class="line">ROW FORMAT SERDE <span class="string">&#x27;com.amazon.emr.hive.serde.CloudTrailSerde&#x27;</span></span><br><span class="line">STORED AS INPUTFORMAT <span class="string">&#x27;com.amazon.emr.cloudtrail.CloudTrailInputFormat&#x27;</span></span><br><span class="line">OUTPUTFORMAT <span class="string">&#x27;org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat&#x27;</span></span><br><span class="line">LOCATION</span><br><span class="line">  <span class="string">&#x27;s3://$BUCKETNAME/AWSLogs/&#x27;</span></span><br><span class="line"><span class="title function_ invoke__">TBLPROPERTIES</span> (</span><br><span class="line">  <span class="string">&#x27;projection.enabled&#x27;</span>=<span class="string">&#x27;true&#x27;</span>, </span><br><span class="line">  <span class="string">&#x27;projection.timestamp.format&#x27;</span>=<span class="string">&#x27;yyyy/MM/dd&#x27;</span>, </span><br><span class="line">  <span class="string">&#x27;projection.timestamp.interval&#x27;</span>=<span class="string">&#x27;1&#x27;</span>, </span><br><span class="line">  <span class="string">&#x27;projection.timestamp.interval.unit&#x27;</span>=<span class="string">&#x27;DAYS&#x27;</span>, </span><br><span class="line">  <span class="string">&#x27;projection.timestamp.range&#x27;</span>=<span class="string">&#x27;2020/01/01,NOW&#x27;</span>, </span><br><span class="line">  <span class="string">&#x27;projection.timestamp.type&#x27;</span>=<span class="string">&#x27;date&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;projection.account.type&#x27;</span>=<span class="string">&#x27;enum&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;projection.account.values&#x27;</span>=<span class="string">&#x27;111111111111,222222222222&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;projection.region.type&#x27;</span>=<span class="string">&#x27;enum&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;projection.region.values&#x27;</span>=<span class="string">&#x27;eu-north-1,ap-south-1,eu-west-3,eu-west-2,eu-west-1,ap-northeast-2,ap-northeast-1,sa-east-1,ca-central-1,ap-southeast-1,ap-southeast-2,eu-central-1,us-east-1,us-east-2,us-west-1,us-west-2&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;storage.location.template&#x27;</span>=<span class="string">&#x27;s3://$BUCKETNAME/AWSLogs/$&#123;account&#125;/CloudTrail/$&#123;region&#125;/$&#123;timestamp&#125;&#x27;</span>)</span><br></pre></td></tr></table></figure><h3 id="Detect-changes-to-Security-Groups-1"><a href="#Detect-changes-to-Security-Groups-1" class="headerlink" title="Detect changes to Security Groups."></a>Detect changes to Security Groups.</h3><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> &quot;default&quot;.&quot;cloudtrail_logs_pp&quot; <span class="keyword">WHERE</span> eventsource = <span class="string">&#x27;ec2.amazonaws.com&#x27;</span> <span class="keyword">AND</span> eventname <span class="keyword">IN</span> (<span class="string">&#x27;AuthorizeSecurityGroupEgress&#x27;</span>, <span class="string">&#x27;AuthorizeSecurityGroupIngress&#x27;</span>, <span class="string">&#x27;RevokeSecurityGroupEgress&#x27;</span>, <span class="string">&#x27;RevokeSecurityGroupIngress&#x27;</span>) <span class="keyword">AND</span> account = <span class="string">&#x27;068189904525&#x27;</span> <span class="keyword">and</span> <span class="type">timestamp</span> &gt; <span class="string">&#x27;2021/01/01&#x27;</span> <span class="keyword">limit</span> <span class="number">50</span>;</span><br></pre></td></tr></table></figure><h3 id="CloudTrail-Data-Events"><a href="#CloudTrail-Data-Events" class="headerlink" title="CloudTrail Data Events"></a>CloudTrail Data Events</h3><p>Copy our <a href="https://docs.google.com/spreadsheets/d/1kwchSlYreH7oMrQw8OZZFSQuWEeDX8GsZrQqJPkD4YQ/edit?usp=sharing" target="_blank" rel="noopener">spreadsheet</a> to calculate costs for CloudTrail Data Events.</p><h2 id="Realtime-Alerts"><a href="#Realtime-Alerts" class="headerlink" title="Realtime Alerts"></a>Realtime Alerts</h2><p>Our CloudFormation template <a href="https://github.com/widdix/aws-cf-templates/blob/master/security/cloudtrail.yaml" target="_blank" rel="noopener">cloudtrail.yaml</a> includes the realtime alerts as defined in the CIS AWS Foundations Benchmark.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Serverless in the Enterprise</title>
      <link>https://cloudonaut.io/serverless-in-the-enterprise/</link>
      <description>
        <![CDATA[<p>Is Serverless ready for the Enterprise? I coached developers building their first Serverless applications for a large company recently an]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/serverless-in-the-enterprise/</guid>
      <pubDate>Wed, 17 Mar 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Is Serverless ready for the Enterprise? I coached developers building their first Serverless applications for a large company recently and want to share my learnings and observations with you.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/enterprise@730w.webp 730w, /images/2021/03/enterprise@730w2x.webp 1460w, /images/2021/03/enterprise@610w.webp 610w, /images/2021/03/enterprise@610w2x.webp 1220w, /images/2021/03/enterprise@450w.webp 450w, /images/2021/03/enterprise@450w2x.webp 900w, /images/2021/03/enterprise@330w.webp 330w, /images/2021/03/enterprise@330w2x.webp 660w, /images/2021/03/enterprise@545w.webp 545w, /images/2021/03/enterprise@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/enterprise@730w.jpg 730w, /images/2021/03/enterprise@730w2x.jpg 1460w, /images/2021/03/enterprise@610w.jpg 610w, /images/2021/03/enterprise@610w2x.jpg 1220w, /images/2021/03/enterprise@450w.jpg 450w, /images/2021/03/enterprise@450w2x.jpg 900w, /images/2021/03/enterprise@330w.jpg 330w, /images/2021/03/enterprise@330w2x.jpg 660w, /images/2021/03/enterprise@545w.jpg 545w, /images/2021/03/enterprise@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/enterprise.jpg" alt="Serverless in the Enterprise" title="Serverless in the Enterprise"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/39-serverless-in-the-enterprise/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="Why"><a href="#Why" class="headerlink" title="Why?"></a>Why?</h2><p>Going Serverless enables you to build scalable and fault-tolerant systems on AWS. The layer of abstraction on top of Infrastructure-as-a-Service solves the following challenges out-of-the-box.</p><ul><li>Avoid idle resources and scale within minutes based on demand.</li><li>Deploy changes without downtime or effects on in-flight requests.</li><li>Provision every part of your cloud infrastructure in an automated way.</li></ul><h2 id="When"><a href="#When" class="headerlink" title="When?"></a>When?</h2><p>In my experience, going the Serverless route is most valuable in scenarios with the following characteristics. </p><ul><li>The system is business-critical but used by less than 100 users.</li><li>The system is not business-critical but processes a very high number of requests with unpredictable load peaks.</li><li>Users access the system via the Internet.</li><li>A single developer or small team is responsible for the system.</li><li>The developers are familiar with Serverless or eager to learn about it.</li></ul><h2 id="People"><a href="#People" class="headerlink" title="People"></a>People</h2><p>The idea of a Serverless architecture will probably not go down well. Most of the arguments presented will be pseudo-arguments. You will notice sooner or later that this is a people problem.</p><blockquote><p>Find a small project and a small team of highly motivated people to build the first Serverless application within your organization. Get support from management. Deliver a solid solution. Everything else will follow.</p></blockquote><h2 id="Skills"><a href="#Skills" class="headerlink" title="Skills"></a>Skills</h2><p>Building a Serverless application requires skills that are most likely missing in your organization.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/skills@730w.webp 730w, /images/2021/03/skills@730w2x.webp 1460w, /images/2021/03/skills@610w.webp 610w, /images/2021/03/skills@610w2x.webp 1220w, /images/2021/03/skills@450w.webp 450w, /images/2021/03/skills@450w2x.webp 900w, /images/2021/03/skills@330w.webp 330w, /images/2021/03/skills@330w2x.webp 660w, /images/2021/03/skills@545w.webp 545w, /images/2021/03/skills@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/skills@730w.jpg 730w, /images/2021/03/skills@730w2x.jpg 1460w, /images/2021/03/skills@610w.jpg 610w, /images/2021/03/skills@610w2x.jpg 1220w, /images/2021/03/skills@450w.jpg 450w, /images/2021/03/skills@450w2x.jpg 900w, /images/2021/03/skills@330w.jpg 330w, /images/2021/03/skills@330w2x.jpg 660w, /images/2021/03/skills@545w.jpg 545w, /images/2021/03/skills@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/skills.jpg" alt="Skills are key to success!" title="Skills are key to success!"></picture></p><p>Here is a list of what one needs to know to get started.</p><ol><li><a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html" target="_blank" rel="noopener">Identity and Access Management (IAM)</a></li><li><a href="https://docs.aws.amazon.com/lambda/latest/dg/welcome.html" target="_blank" rel="noopener">Lambda</a></li><li><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html" target="_blank" rel="noopener">S3</a></li><li><a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html" target="_blank" rel="noopener">DynamoDB</a></li><li><a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html" target="_blank" rel="noopener">API Gateway</a></li><li><a href="https://aws.amazon.com/cdk/" target="_blank" rel="noopener">CDK</a>, <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html" target="_blank" rel="noopener">CloudFormation</a>, or Serverless Framework.</li><li><a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/CHAP_AuroraOverview.html" target="_blank" rel="noopener">Aurora Serverless</a></li><li><a href="https://aws.amazon.com/tools/" target="_blank" rel="noopener">AWS SDK</a></li></ol><blockquote><p>Starting from 0 is hard. Invest in training or hire experts. Skills are key to success!</p></blockquote><h2 id="Compute"><a href="#Compute" class="headerlink" title="Compute"></a>Compute</h2><p>Lambda is the obvious choice for the compute layer of a Serverless application. However, Lambda comes with a few limitations.</p><ul><li>Compute, memory, and networking resources are limited.</li><li>Special requirements for CPU, GPU, memory can only be met with difficulty or not at all.</li><li>A function invocation will timeout after 15 minutes. Therefore, you need to slice your problem into chunks that Lambda can process within less than 15 minutes - even the outliers.</li></ul><p>Another limitation of Lambda is that running your code outside of the cloud is a challenge. Therefore, you need to invest in some tooling to increase the efficiency of the development workflow.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/lambda-vs-fargate@730w.webp 730w, /images/2021/03/lambda-vs-fargate@730w2x.webp 1460w, /images/2021/03/lambda-vs-fargate@610w.webp 610w, /images/2021/03/lambda-vs-fargate@610w2x.webp 1220w, /images/2021/03/lambda-vs-fargate@450w.webp 450w, /images/2021/03/lambda-vs-fargate@450w2x.webp 900w, /images/2021/03/lambda-vs-fargate@330w.webp 330w, /images/2021/03/lambda-vs-fargate@330w2x.webp 660w, /images/2021/03/lambda-vs-fargate@545w.webp 545w, /images/2021/03/lambda-vs-fargate@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/lambda-vs-fargate@730w.jpg 730w, /images/2021/03/lambda-vs-fargate@730w2x.jpg 1460w, /images/2021/03/lambda-vs-fargate@610w.jpg 610w, /images/2021/03/lambda-vs-fargate@610w2x.jpg 1220w, /images/2021/03/lambda-vs-fargate@450w.jpg 450w, /images/2021/03/lambda-vs-fargate@450w2x.jpg 900w, /images/2021/03/lambda-vs-fargate@330w.jpg 330w, /images/2021/03/lambda-vs-fargate@330w2x.jpg 660w, /images/2021/03/lambda-vs-fargate@545w.jpg 545w, /images/2021/03/lambda-vs-fargate@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/lambda-vs-fargate.jpg" alt="Lambda vs. Fargate" title="Lambda vs. Fargate"></picture></p><p>An alternative is Fargate. With Fargate running containers in the cloud becomes a no-brainer. All you need to do is to bundle your application into a container image and tell AWS to run that image for you. That simplifies the development workflow a lot and is also closer to the programming model for on-premises.</p><p>Some say Fargate is not 100% Serverless. But how cares about those theoretical discussions? The main difference is that we pay per hour - instead of paying per request with Lambda - and that we need to configure auto-scaling ourselves.</p><p>Fargate is easier to handle from a programmer’s perspective, as there is a long-running process. This gives more flexibility and is closer to what developers and architects are already used to.</p><p>Want to learn more about the differences between Lambda and Fargate? Check out <a href="https://cloudonaut.io/containers-vs-serverless-cloud-strategy/">Containers vs. Serverless: Thoughts About Your Cloud Strategy</a>.</p><blockquote><p>Lambda is the obvious choice. But consider Fargate (ECS) as well to decrease complexity for developers.</p></blockquote><h2 id="Network"><a href="#Network" class="headerlink" title="Network"></a>Network</h2><p>The network disappears under another layer of abstraction. The Serverless infrastructure handles authentication, authorization, and throttling.</p><p>The Serverless offerings from AWS are <em>web services</em> per excellence. All of them provide an Internet-facing API. That’s what you should do when building Serverless applications as well. The days of the Intranet are numbered.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/serverless-internet@730w.webp 730w, /images/2021/03/serverless-internet@730w2x.webp 1460w, /images/2021/03/serverless-internet@610w.webp 610w, /images/2021/03/serverless-internet@610w2x.webp 1220w, /images/2021/03/serverless-internet@450w.webp 450w, /images/2021/03/serverless-internet@450w2x.webp 900w, /images/2021/03/serverless-internet@330w.webp 330w, /images/2021/03/serverless-internet@330w2x.webp 660w, /images/2021/03/serverless-internet@545w.webp 545w, /images/2021/03/serverless-internet@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/serverless-internet@730w.png 730w, /images/2021/03/serverless-internet@730w2x.png 1460w, /images/2021/03/serverless-internet@610w.png 610w, /images/2021/03/serverless-internet@610w2x.png 1220w, /images/2021/03/serverless-internet@450w.png 450w, /images/2021/03/serverless-internet@450w2x.png 900w, /images/2021/03/serverless-internet@330w.png 330w, /images/2021/03/serverless-internet@330w2x.png 660w, /images/2021/03/serverless-internet@545w.png 545w, /images/2021/03/serverless-internet@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/serverless-internet.png" alt="Build Serverless web services!" title="Build Serverless web services!"></picture></p><p>Yes, Serverless also works for hybrid cloud scenarios. But the complexity increases enormously. Check out <a href="https://cloudonaut.io/serverless-hybrid-cloud-accessing-an-api-gateway-via-vpn-or-direct-connect/">Serverless Hybrid Cloud: Accessing an API Gateway via VPN or Direct Connect</a> to learn more.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/serverless-intranet@730w.webp 730w, /images/2021/03/serverless-intranet@730w2x.webp 1460w, /images/2021/03/serverless-intranet@610w.webp 610w, /images/2021/03/serverless-intranet@610w2x.webp 1220w, /images/2021/03/serverless-intranet@450w.webp 450w, /images/2021/03/serverless-intranet@450w2x.webp 900w, /images/2021/03/serverless-intranet@330w.webp 330w, /images/2021/03/serverless-intranet@330w2x.webp 660w, /images/2021/03/serverless-intranet@545w.webp 545w, /images/2021/03/serverless-intranet@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/serverless-intranet@730w.png 730w, /images/2021/03/serverless-intranet@730w2x.png 1460w, /images/2021/03/serverless-intranet@610w.png 610w, /images/2021/03/serverless-intranet@610w2x.png 1220w, /images/2021/03/serverless-intranet@450w.png 450w, /images/2021/03/serverless-intranet@450w2x.png 900w, /images/2021/03/serverless-intranet@330w.png 330w, /images/2021/03/serverless-intranet@330w2x.png 660w, /images/2021/03/serverless-intranet@545w.png 545w, /images/2021/03/serverless-intranet@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/serverless-intranet.png" alt="Serverless infrastructure accessible via Intranet only" title="Serverless infrastructure accessible via Intranet only"></picture></p><blockquote><p>Even though it may be unfamiliar. Build internet-facing applications.</p></blockquote><h2 id="Database"><a href="#Database" class="headerlink" title="Database"></a>Database</h2><p>The most popular database choices are Aurora Serverless (SQL) and DynamoDB (NoSQL).</p><p>Aurora Serverless provides a relational database that scales compute and storage layers automatically. As Aurora Serverless comes with a PostgreSQL or MySQL compatible interface, it is still similar to what engineers are used to from an on-premises environment.</p><p>Note that Aurora Serverless comes with a <a href="https://cloudonaut.io/review-amazon-aurora-serverless/">few limitations</a>. We are all waiting for the next generation that AWS announced in December 2020 to become generally available.</p><p>Handling database connections with a Lambda function is tricky. The <a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html" target="_blank" rel="noopener">Data API</a> mitigates the problem by providing a REST API sending SQL queries to the database.</p><p>DynamoDB scales horizontally, not vertically as Aurora Serverless does. Therefore, DynamoDB is the database of choice for workloads with high throughput. Also, DynamoDB bills per request which reduces idle resources to a minimum.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/dynamodb-vs-aurora-serverless@730w.webp 730w, /images/2021/03/dynamodb-vs-aurora-serverless@730w2x.webp 1460w, /images/2021/03/dynamodb-vs-aurora-serverless@610w.webp 610w, /images/2021/03/dynamodb-vs-aurora-serverless@610w2x.webp 1220w, /images/2021/03/dynamodb-vs-aurora-serverless@450w.webp 450w, /images/2021/03/dynamodb-vs-aurora-serverless@450w2x.webp 900w, /images/2021/03/dynamodb-vs-aurora-serverless@330w.webp 330w, /images/2021/03/dynamodb-vs-aurora-serverless@330w2x.webp 660w, /images/2021/03/dynamodb-vs-aurora-serverless@545w.webp 545w, /images/2021/03/dynamodb-vs-aurora-serverless@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/dynamodb-vs-aurora-serverless@730w.jpg 730w, /images/2021/03/dynamodb-vs-aurora-serverless@730w2x.jpg 1460w, /images/2021/03/dynamodb-vs-aurora-serverless@610w.jpg 610w, /images/2021/03/dynamodb-vs-aurora-serverless@610w2x.jpg 1220w, /images/2021/03/dynamodb-vs-aurora-serverless@450w.jpg 450w, /images/2021/03/dynamodb-vs-aurora-serverless@450w2x.jpg 900w, /images/2021/03/dynamodb-vs-aurora-serverless@330w.jpg 330w, /images/2021/03/dynamodb-vs-aurora-serverless@330w2x.jpg 660w, /images/2021/03/dynamodb-vs-aurora-serverless@545w.jpg 545w, /images/2021/03/dynamodb-vs-aurora-serverless@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/dynamodb-vs-aurora-serverless.jpg" alt="DynamoDB vs. Aurora Serverless" title="DynamoDB vs. Aurora Serverless"></picture></p><p>But, DynamoDB is different than a typical SQL database. Architects and developers will need some time to get their heads around DynamoDB’s quirks. Also, DynamoDB requires a well-defined data schema upfront. Modifying the data schema and access patterns may result in significant conversion work.</p><blockquote><p>Whenever the data schema and query patterns are straightforward, go with DynamoDB. However, Aurora Serverless is a good alternative in scenarios where you do not know about the data structures upfront or where query patterns are complex.</p></blockquote><h2 id="API-Gateway"><a href="#API-Gateway" class="headerlink" title="API Gateway"></a>API Gateway</h2><p>I’ve <a href="https://cloudonaut.io/comparing-api-gateways-on-aws/">compared the different types of API Gateways that AWS is offering</a> already.</p><p>In my opinion, the <em>API Gateway REST API</em> is the best fit for the Enterprise because of the following reasons.</p><ul><li>High service maturity does not change quickly.</li><li>Wide range of authentication integrations, including the option to build a custom authorizer.</li><li>Supports tenant-based throttling and the WAF.</li><li>Supports private endpoints only accessible from the VPC.</li></ul><blockquote><p>In general, the API Gateway REST API is a good fit for Serverless in the Enterprise.</p></blockquote><h2 id="Distributed-System"><a href="#Distributed-System" class="headerlink" title="Distributed System"></a>Distributed System</h2><p>In the good old days, the application and database ran on one machine. Maybe on two different machines. With Serverless, you are entering the world of highly distributed systems. Provisioning and maintaining the Serverless application’s infrastructure is simple, but writing the code becomes much more complex.</p><ul><li>There is no guarantee that Lambda executes your code only once. The same is true for delivering messages.</li><li>Most often, there is no guarantee for delivering messages in the proper order.</li><li>Modifying data might be eventually consistent. Therefore, your application might read stale data.</li></ul><p>You need to plan for all of that. And it’s not always simple to do so.</p><blockquote><p>Add <strong>general understanding of distributed systems</strong> to your goals for training and hiring developers and architects.</p></blockquote><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>I see more and more large companies adopting Serverless architectures. The gap between a typical on-premises infrastructure and Serverless is huge. Therefore it will take time, energy, and money to cross the chasm. However, all projects that I’ve observed turned into a great success and were delivering value to the company. It is worth it!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Serverless Hybrid Cloud: Accessing an API Gateway via VPN or Direct Connect</title>
      <link>https://cloudonaut.io/serverless-hybrid-cloud-accessing-an-api-gateway-via-vpn-or-direct-connect/</link>
      <description>
        <![CDATA[<p>Recently, I’ve been coaching a team building a Serverless application. The extraordinary thing about it? We had to create a solution fitt]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/apigateway/">apigateway</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/alb/">alb</category>
      <guid isPermaLink="true">https://cloudonaut.io/serverless-hybrid-cloud-accessing-an-api-gateway-via-vpn-or-direct-connect/</guid>
      <pubDate>Wed, 17 Mar 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Recently, I’ve been coaching a team building a Serverless application. The extraordinary thing about it? We had to create a solution fitting into the hybrid cloud approach of the organization. An essential requirement was that the Serverless application is only accessible via the internal network. Sounds simple? It’s not because AWS designed their Serverless services with a focus on Internet-based applications.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/bridge@730w.webp 730w, /images/2021/03/bridge@730w2x.webp 1460w, /images/2021/03/bridge@610w.webp 610w, /images/2021/03/bridge@610w2x.webp 1220w, /images/2021/03/bridge@450w.webp 450w, /images/2021/03/bridge@450w2x.webp 900w, /images/2021/03/bridge@330w.webp 330w, /images/2021/03/bridge@330w2x.webp 660w, /images/2021/03/bridge@545w.webp 545w, /images/2021/03/bridge@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/bridge@730w.jpg 730w, /images/2021/03/bridge@730w2x.jpg 1460w, /images/2021/03/bridge@610w.jpg 610w, /images/2021/03/bridge@610w2x.jpg 1220w, /images/2021/03/bridge@450w.jpg 450w, /images/2021/03/bridge@450w2x.jpg 900w, /images/2021/03/bridge@330w.jpg 330w, /images/2021/03/bridge@330w2x.jpg 660w, /images/2021/03/bridge@545w.jpg 545w, /images/2021/03/bridge@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/bridge.jpg" alt="Serverless Hybrid Cloud: Accessing an API Gateway via VPN or Direct Connect" title="Serverless Hybrid Cloud: Accessing an API Gateway via VPN or Direct Connect"></picture></p><p>So how to build a Serverless application - backend and single-page application - accessible from the corporate network via VPN or Direct Connect? Read on to learn more! Terraform code snippets included.</p><h2 id="The-architecture"><a href="#The-architecture" class="headerlink" title="The architecture"></a>The architecture</h2><p>Use the following building blocks to assemble a Serverless architecture for the hybrid cloud scenario.</p><ul><li>A <em>VPN or Direct Connect connection</em> links the corporate network with the VPC (Virtual Private Cloud).</li><li>The <em>Application Load Balancer (ALB)</em> accepts HTTPS requests and forwards them to a VPC Endpoint.</li><li>The <em>VPC Endpoint</em> forwards traffic to the API Gateway.</li><li>The <em>API Gateway</em> processes the request and forwards them to Lambda (backend) or S3 (frontend).</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/serverlesss-hybrid-cloud_1@730w.webp 730w, /images/2021/03/serverlesss-hybrid-cloud_1@730w2x.webp 1460w, /images/2021/03/serverlesss-hybrid-cloud_1@610w.webp 610w, /images/2021/03/serverlesss-hybrid-cloud_1@610w2x.webp 1220w, /images/2021/03/serverlesss-hybrid-cloud_1@450w.webp 450w, /images/2021/03/serverlesss-hybrid-cloud_1@450w2x.webp 900w, /images/2021/03/serverlesss-hybrid-cloud_1@330w.webp 330w, /images/2021/03/serverlesss-hybrid-cloud_1@330w2x.webp 660w, /images/2021/03/serverlesss-hybrid-cloud_1@545w.webp 545w, /images/2021/03/serverlesss-hybrid-cloud_1@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/serverlesss-hybrid-cloud_1@730w.png 730w, /images/2021/03/serverlesss-hybrid-cloud_1@730w2x.png 1460w, /images/2021/03/serverlesss-hybrid-cloud_1@610w.png 610w, /images/2021/03/serverlesss-hybrid-cloud_1@610w2x.png 1220w, /images/2021/03/serverlesss-hybrid-cloud_1@450w.png 450w, /images/2021/03/serverlesss-hybrid-cloud_1@450w2x.png 900w, /images/2021/03/serverlesss-hybrid-cloud_1@330w.png 330w, /images/2021/03/serverlesss-hybrid-cloud_1@330w2x.png 660w, /images/2021/03/serverlesss-hybrid-cloud_1@545w.png 545w, /images/2021/03/serverlesss-hybrid-cloud_1@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/serverlesss-hybrid-cloud_1.png" alt="Serverless Hybdrid Cloud: VPN, ALB, VPC Endpoint, API Gateway, and Lambda" title="Serverless Hybdrid Cloud: VPN, ALB, VPC Endpoint, API Gateway, and Lambda"></picture></p><p>Let’s have a look at the details and some Terraform code next.</p><h3 id="API-Gateway"><a href="#API-Gateway" class="headerlink" title="API Gateway"></a>API Gateway</h3><p>When configuring the API Gateway, the following attributes are important.</p><ul><li>Use endpoint type <code>PRIVATE</code> to create an API Gateway only accessible from the VPC.</li><li>Attach a gateway policy that grants access from a certain VPC only.</li></ul><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_api_gateway_rest_api&quot; &quot;myapp&quot; &#123;</span><br><span class="line">  <span class="type">name</span> = &quot;myapp&quot;</span><br><span class="line"></span><br><span class="line">  endpoint_configuration &#123;</span><br><span class="line">    <span class="keyword">types</span>            = [&quot;PRIVATE&quot;]</span><br><span class="line">    vpc_endpoint_ids = [vpce<span class="number">-11111111111111111</span>]</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">policy</span> = &lt;&lt;<span class="keyword">POLICY</span></span><br><span class="line">&#123;</span><br><span class="line">  &quot;Version&quot;: &quot;2012-10-17&quot;,</span><br><span class="line">  &quot;Statement&quot;: [</span><br><span class="line">    &#123;</span><br><span class="line">      &quot;Effect&quot;: &quot;Allow&quot;,</span><br><span class="line">      &quot;Principal&quot;: &quot;*&quot;,</span><br><span class="line">      &quot;Action&quot;: &quot;execute-api:Invoke&quot;,</span><br><span class="line">      &quot;Resource&quot;: [</span><br><span class="line">        &quot;execute-api:/*&quot;</span><br><span class="line">      ],</span><br><span class="line">      &quot;Condition&quot;: &#123;</span><br><span class="line">        &quot;StringEquals&quot;: &#123;</span><br><span class="line">          &quot;aws:SourceVpce&quot;: &quot;vpc-11111111&quot;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">POLICY</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="VPC-Endpoint"><a href="#VPC-Endpoint" class="headerlink" title="VPC Endpoint"></a>VPC Endpoint</h3><p>The VPC Endpoint establishes the network connectivity between the VPC and the private API Gateway.</p><blockquote><p>Warning: When creating a VPC Endpoint for the API Gateway service with <em>Private DNS Enabled</em>, all requests from within the VPC to public API Gateways will fail. Check out Steffen’s blog post <a href="https://st-g.de/2019/07/be-careful-with-aws-private-api-gateway-endpoints" target="_blank" rel="noopener">Be careful with AWS Private API Gateway Endpoints</a> to learn more.</p></blockquote><figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_vpc_endpoint&quot;</span> <span class="string">&quot;apigw&quot;</span> &#123;</span><br><span class="line">  vpc_id            <span class="operator">=</span> <span class="string">&quot;vpc-11111111&quot;</span></span><br><span class="line">  service_name      <span class="operator">=</span> <span class="string">&quot;com.amazonaws.us-east-1.execute-api&quot;</span></span><br><span class="line">  vpc_endpoint_type <span class="operator">=</span> <span class="string">&quot;Interface&quot;</span></span><br><span class="line"></span><br><span class="line">  security_group_ids <span class="operator">=</span> [</span><br><span class="line">    aws_security_group.apigw.id</span><br><span class="line">  ]</span><br><span class="line"></span><br><span class="line">  private_dns_enabled <span class="operator">=</span> false</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_security_group&quot;</span> <span class="string">&quot;apigw&quot;</span> &#123;</span><br><span class="line">  name        <span class="operator">=</span> <span class="string">&quot;myapp-apigw&quot;</span></span><br><span class="line">  vpc_id      <span class="operator">=</span> <span class="string">&quot;vpc-11111111&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="ALB"><a href="#ALB" class="headerlink" title="ALB"></a>ALB</h3><p>Why on earth do we need an ALB for our Serverless architecture? The ALB allows us to define a custom domain name which is essential, especially when building a single-page application. Unfortunately, private API Gateways do not support custom domain names out-of-the-box.</p><p>Please note that the following Terraform code snippet creates a domain name intended to be used with a regional API gateway. By accident, it is possible to link the custom domain name with a private API Gateway.</p><figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_api_gateway_domain_name&quot;</span> <span class="string">&quot;myapp&quot;</span> &#123;</span><br><span class="line">  domain_name              <span class="operator">=</span> <span class="string">&quot;myapp.local.mycompany.com&quot;</span></span><br><span class="line">  regional_certificate_arn <span class="operator">=</span> <span class="string">&quot;arn:aws:acm:us-east-1:111111111111:certificate/...&quot;</span></span><br><span class="line">  security_policy          <span class="operator">=</span> <span class="string">&quot;TLS_1_2&quot;</span></span><br><span class="line"></span><br><span class="line">  endpoint_configuration &#123;</span><br><span class="line">    types <span class="operator">=</span> [<span class="string">&quot;REGIONAL&quot;</span>]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_api_gateway_base_path_mapping&quot;</span> <span class="string">&quot;myapp&quot;</span> &#123;</span><br><span class="line">  api_id      <span class="operator">=</span> aws_api_gateway_rest_api.myapp.id</span><br><span class="line">  stage_name  <span class="operator">=</span> aws_api_gateway_deployment.myapp.stage_name</span><br><span class="line">  domain_name <span class="operator">=</span> aws_api_gateway_domain_name.myapp.domain_name</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_api_gateway_deployment&quot;</span> <span class="string">&quot;myapp&quot;</span> &#123;</span><br><span class="line">  rest_api_id <span class="operator">=</span> aws_api_gateway_rest_api.myapp.id</span><br><span class="line">  stage_name  <span class="operator">=</span> <span class="string">&quot;v1&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>As mentioned already, the private API Gateway does not support custom domain names. Therefore, we are adding the ALB to the equation. The ALB accepts the requests to <code>myapp.local.mycompany.com</code> and forwards them to the private IP addresses of the VPC endpoint. Luckily, the ALB does not verify the TLS certificate used by the API Gateway, which is not valid for <code>myapp.local.mycompany.com</code> but <code>*.execute-api.us-east-1.amazonaws.com</code>.</p><figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_lb&quot;</span> <span class="string">&quot;myapp&quot;</span> &#123;</span><br><span class="line">  name               = <span class="string">&quot;myapp&quot;</span></span><br><span class="line">  load_balancer_type = <span class="string">&quot;application&quot;</span></span><br><span class="line">  security_groups    = <span class="selector-attr">[aws_security_group.alb.id]</span></span><br><span class="line">  subnets            = <span class="selector-attr">[<span class="string">&quot;subnet-aaaaaaaaaaaaaaaaa&quot;</span>, <span class="string">&quot;subnet-bbbbbbbbbbbbbbbbb&quot;</span>]</span></span><br><span class="line">  internal           = true</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_lb_listener&quot;</span> <span class="string">&quot;myapp_https&quot;</span> &#123;</span><br><span class="line">  load_balancer_arn  = aws_lb<span class="selector-class">.myapp</span><span class="selector-class">.arn</span></span><br><span class="line">  port               = <span class="string">&quot;443&quot;</span></span><br><span class="line">  protocol           = <span class="string">&quot;HTTPS&quot;</span></span><br><span class="line">  ssl_policy         = <span class="string">&quot;ELBSecurityPolicy-FS-1-2-2019-08&quot;</span></span><br><span class="line"></span><br><span class="line">  certificate_arn    = <span class="string">&quot;arn:aws:acm:us-east-1:111111111111:certificate/...&quot;</span></span><br><span class="line"></span><br><span class="line">  default_action &#123;</span><br><span class="line">    type             = <span class="string">&quot;forward&quot;</span></span><br><span class="line">    target_group_arn = aws_lb_target_group<span class="selector-class">.myapp</span><span class="selector-class">.arn</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Next, we register the private IP addresses of the VPC endpoint at the load balancer.</p><blockquote><p>I could not find a way to do so with Terraform automatically. Therefore, I’m using variables to configure the IP addresses here manually. Happy to learn a better way to do so from you!</p></blockquote><figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_lb_target_group&quot;</span> <span class="string">&quot;myapp&quot;</span> &#123;</span><br><span class="line">  name_prefix          <span class="operator">=</span> <span class="string">&quot;myapp-&quot;</span></span><br><span class="line">  port                 <span class="operator">=</span> <span class="number">443</span></span><br><span class="line">  protocol             <span class="operator">=</span> <span class="string">&quot;HTTPS&quot;</span></span><br><span class="line">  vpc_id               <span class="operator">=</span> <span class="string">&quot;vpc-11111111&quot;</span></span><br><span class="line">  deregistration_delay <span class="operator">=</span> <span class="number">60</span></span><br><span class="line">  target_type          <span class="operator">=</span> <span class="string">&quot;ip&quot;</span></span><br><span class="line"></span><br><span class="line">  health_check &#123;</span><br><span class="line">    interval            <span class="operator">=</span> <span class="number">10</span></span><br><span class="line">    path                <span class="operator">=</span> <span class="string">&quot;/&quot;</span></span><br><span class="line">    protocol            <span class="operator">=</span> <span class="string">&quot;HTTPS&quot;</span></span><br><span class="line">    timeout             <span class="operator">=</span> <span class="number">5</span></span><br><span class="line">    healthy_threshold   <span class="operator">=</span> <span class="number">2</span></span><br><span class="line">    unhealthy_threshold <span class="operator">=</span> <span class="number">2</span></span><br><span class="line">    matcher             <span class="operator">=</span> <span class="string">&quot;200-499&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_lb_target_group_attachment&quot;</span> <span class="string">&quot;myapp_1&quot;</span> &#123;</span><br><span class="line">  target_group_arn <span class="operator">=</span> aws_lb_target_group.myapp.arn</span><br><span class="line">  target_id        <span class="operator">=</span> var.vpce_ip_1</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_lb_target_group_attachment&quot;</span> <span class="string">&quot;myapp_2&quot;</span> &#123;</span><br><span class="line">  target_group_arn <span class="operator">=</span> aws_lb_target_group.api.arn</span><br><span class="line">  target_id        <span class="operator">=</span> var.vpce_ip_2</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Finally, point a DNS record the ALB.</p><figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_route53_record&quot;</span> <span class="string">&quot;myapp&quot;</span> &#123;</span><br><span class="line">  zone_id  <span class="operator">=</span> <span class="string">&quot;...&quot;</span></span><br><span class="line">  name     <span class="operator">=</span> <span class="string">&quot;myapp.local.mycompany.com&quot;</span></span><br><span class="line">  type     <span class="operator">=</span> <span class="string">&quot;A&quot;</span></span><br><span class="line"></span><br><span class="line">  alias &#123;</span><br><span class="line">    name                   <span class="operator">=</span> aws_lb.myapp.dns_name</span><br><span class="line">    zone_id                <span class="operator">=</span> aws_lb.myapp.zone_id</span><br><span class="line">    evaluate_target_health <span class="operator">=</span> false</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>So that’s one option to deploy a Serverless application with a custom domain name in a hybrid cloud scenario. Users access the frontend and backend via a custom domain name.</p><h2 id="An-alternative"><a href="#An-alternative" class="headerlink" title="An alternative"></a>An alternative</h2><p>Admittedly, the approach described so far is quite complicated. And not very efficient either since every request has to go through the ALB, the VPC endpoint, and the API gateway. This also results in unnecessary costs. In case the features of the API Gateway - AuthN&#x2F;AuthZ, Throttling, Request&#x2F;Response Mapping, and more - are not used, there is a more straightforward solution.</p><p>Configure the ALB to forward requests to a Lambda function. I’ve used this approach to build a simple API to send SMS, for example.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/serverlesss-hybrid-cloud_2@730w.webp 730w, /images/2021/03/serverlesss-hybrid-cloud_2@730w2x.webp 1460w, /images/2021/03/serverlesss-hybrid-cloud_2@610w.webp 610w, /images/2021/03/serverlesss-hybrid-cloud_2@610w2x.webp 1220w, /images/2021/03/serverlesss-hybrid-cloud_2@450w.webp 450w, /images/2021/03/serverlesss-hybrid-cloud_2@450w2x.webp 900w, /images/2021/03/serverlesss-hybrid-cloud_2@330w.webp 330w, /images/2021/03/serverlesss-hybrid-cloud_2@330w2x.webp 660w, /images/2021/03/serverlesss-hybrid-cloud_2@545w.webp 545w, /images/2021/03/serverlesss-hybrid-cloud_2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/serverlesss-hybrid-cloud_2@730w.png 730w, /images/2021/03/serverlesss-hybrid-cloud_2@730w2x.png 1460w, /images/2021/03/serverlesss-hybrid-cloud_2@610w.png 610w, /images/2021/03/serverlesss-hybrid-cloud_2@610w2x.png 1220w, /images/2021/03/serverlesss-hybrid-cloud_2@450w.png 450w, /images/2021/03/serverlesss-hybrid-cloud_2@450w2x.png 900w, /images/2021/03/serverlesss-hybrid-cloud_2@330w.png 330w, /images/2021/03/serverlesss-hybrid-cloud_2@330w2x.png 660w, /images/2021/03/serverlesss-hybrid-cloud_2@545w.png 545w, /images/2021/03/serverlesss-hybrid-cloud_2@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/serverlesss-hybrid-cloud_2.png" alt="Serverless Hybdrid Cloud: VPN, ALB, and Lambda" title="Serverless Hybdrid Cloud: VPN, ALB, and Lambda"></picture></p><p>This approach is less complex and more efficient.</p><p>Besides that, <a href="https://aws.amazon.com/blogs/aws/aws-privatelink-for-amazon-s3-now-available/" target="_blank" rel="noopener">AWS introduced interface VPC endpoint for S3 in February 2021</a>. I haven’t looked into this in detail, but this might be an option to access a single-page application’s static content in a hybrid cloud scenario.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Deploying a Serverless application only accessible via the private network of hybrid cloud architecture is tricky. Mainly because AWS is focusing on Internet-based applications. Therefore, I recommend carefully considering whether a serverless architecture is the right solution for a hybrid cloud environment.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Backup - Vault cannot be deleted? Use this script!</title>
      <link>https://cloudonaut.io/aws-backup-vault-cannot-be-deleted-use-this-script/</link>
      <description>
        <![CDATA[<p>With AWS Backup, it is simple to create snapshots of EBS, EFS, and more. A retention period defines the number of recovery points stored]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cli/">cli</category>
      <category domain="https://cloudonaut.io/tag/backup/">backup</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-backup-vault-cannot-be-deleted-use-this-script/</guid>
      <pubDate>Thu, 11 Mar 2021 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>With AWS Backup, it is simple to create snapshots of EBS, EFS, and more. A retention period defines the number of recovery points stored within a backup vault. When removing a backup vault, you need to delete all recovery points first. Doing so is a cumbersome process. Read on to learn how to automate that task.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/vacuumer@730w.webp 730w, /images/2021/03/vacuumer@730w2x.webp 1460w, /images/2021/03/vacuumer@610w.webp 610w, /images/2021/03/vacuumer@610w2x.webp 1220w, /images/2021/03/vacuumer@450w.webp 450w, /images/2021/03/vacuumer@450w2x.webp 900w, /images/2021/03/vacuumer@330w.webp 330w, /images/2021/03/vacuumer@330w2x.webp 660w, /images/2021/03/vacuumer@545w.webp 545w, /images/2021/03/vacuumer@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/vacuumer@730w.jpg 730w, /images/2021/03/vacuumer@730w2x.jpg 1460w, /images/2021/03/vacuumer@610w.jpg 610w, /images/2021/03/vacuumer@610w2x.jpg 1220w, /images/2021/03/vacuumer@450w.jpg 450w, /images/2021/03/vacuumer@450w2x.jpg 900w, /images/2021/03/vacuumer@330w.jpg 330w, /images/2021/03/vacuumer@330w2x.jpg 660w, /images/2021/03/vacuumer@545w.jpg 545w, /images/2021/03/vacuumer@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/vacuumer.jpg" alt="Vault cannot be deleted? Use this script!" title="Vault cannot be deleted? Use this script!"></picture></p><p>Tried to delete a backup vault and got the following error message?</p><figure class="highlight mipsasm"><table><tr><td class="code"><pre><span class="line"><span class="keyword">Backup </span>vault cannot <span class="keyword">be </span>deleted (contains <span class="number">99</span> recovery points).</span><br></pre></td></tr></table></figure><p>Check out the following script to avoid deleting recovery points manually.</p><blockquote><p>Make sure that to install the <a href="https://aws.amazon.com/cli/" target="_blank" rel="noopener">AWS CLI</a> on your machine before you proceed.</p></blockquote><p>The following script asks for the vault name you want to empty, fetches a list with the recovery points belonging to the backup vault, and deletes the recovery points.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Enter the name of the vault where all backups should be deleted.&quot;</span></span><br><span class="line"><span class="built_in">read</span> VAULT_NAME</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> ARN <span class="keyword">in</span> $(aws backup list-recovery-points-by-backup-vault --backup-vault-name <span class="string">&quot;<span class="variable">$&#123;VAULT_NAME&#125;</span>&quot;</span> --query <span class="string">&#x27;RecoveryPoints[].RecoveryPointArn&#x27;</span> --output text); <span class="keyword">do</span> </span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;deleting <span class="variable">$&#123;ARN&#125;</span> ...&quot;</span></span><br><span class="line">  aws backup delete-recovery-point --backup-vault-name <span class="string">&quot;<span class="variable">$&#123;VAULT_NAME&#125;</span>&quot;</span> --recovery-point-arn <span class="string">&quot;<span class="variable">$&#123;ARN&#125;</span>&quot;</span></span><br><span class="line"><span class="keyword">done</span></span><br></pre></td></tr></table></figure><p>The script saved me hundreds of manual steps. I hope you will enjoy it as well!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>cloudonaut plus - Behind the scenes</title>
      <link>https://cloudonaut.io/cloudonaut-plus-behind-the-scenes/</link>
      <description>
        <![CDATA[<p>Since November last year, we published more than 20 videos and online events with tips, pitfalls, code examples, and our independent opin]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/cloudonaut-plus-behind-the-scenes/</guid>
      <pubDate>Thu, 04 Mar 2021 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Since November last year, we published more than 20 videos and online events with tips, pitfalls, code examples, and our independent opinion. We covered <a href="/aws-sso-instead-of-iam-users/">AWS SSO</a>, <a href="/transition-to-imdsv2-on-ec2/">IMDSv2</a>, IAM policies, <a href="/all-you-need-to-know-about-encrypting-s3-buckets/">S3 encryption</a>, <a href="/unboxing-amazon-managed-service-for-prometheus-amp/">AMP</a>, and many more. The community of cloudonaut plus members has grown to a size where exciting discussions happen, and the community generates new topic ideas. It’s time for a behind the scenes blog post to share what we learned. Learn how we create exclusive videos and online events for AWS professionals.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/behind-the-scenes@730w.webp 730w, /images/2021/03/behind-the-scenes@730w2x.webp 1460w, /images/2021/03/behind-the-scenes@610w.webp 610w, /images/2021/03/behind-the-scenes@610w2x.webp 1220w, /images/2021/03/behind-the-scenes@450w.webp 450w, /images/2021/03/behind-the-scenes@450w2x.webp 900w, /images/2021/03/behind-the-scenes@330w.webp 330w, /images/2021/03/behind-the-scenes@330w2x.webp 660w, /images/2021/03/behind-the-scenes@545w.webp 545w, /images/2021/03/behind-the-scenes@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/behind-the-scenes@730w.jpg 730w, /images/2021/03/behind-the-scenes@730w2x.jpg 1460w, /images/2021/03/behind-the-scenes@610w.jpg 610w, /images/2021/03/behind-the-scenes@610w2x.jpg 1220w, /images/2021/03/behind-the-scenes@450w.jpg 450w, /images/2021/03/behind-the-scenes@450w2x.jpg 900w, /images/2021/03/behind-the-scenes@330w.jpg 330w, /images/2021/03/behind-the-scenes@330w2x.jpg 660w, /images/2021/03/behind-the-scenes@545w.jpg 545w, /images/2021/03/behind-the-scenes@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/behind-the-scenes.jpg" alt="Behind the scenes" title="Behind the scenes"></picture></p><h2 id="Producing-videos-is-not-easy"><a href="#Producing-videos-is-not-easy" class="headerlink" title="Producing videos is not easy"></a>Producing videos is not easy</h2><p>In August 2020, we had no idea what is needed to produce high-quality videos or streams. Luckily, Andreas shares his office with freelancers with diverse backgrounds. One of them—Moritz—is a professional filmmaker and photographer. We soon learned that an iPhone is not the best option to produce videos. Instead, we need a good scene, good light, good sound, good camera, suitable hardware, and software to process the video stream. The only problem: We had no idea what good meant for all those pieces. We hired Moritz to develop a visually appealing scene that fits into my home office and the list of parts that we needed to produce high-quality videos. After a short discussion, we realized that we need to set a budget too. The following video shows our studio &#x2F; my home office.</p><video controls preload="auto" class="video-js vjs-theme-forest vjs-16-9" poster="/media/cloudonaut/2021/000-behind-the-scenes/000-behind-the-scenes@730w.jpg">  <source src="/media/cloudonaut/2021/000-behind-the-scenes/000-behind-the-scenes.m3u8" type="application/x-mpegURL">  <source src="/media/cloudonaut/2021/000-behind-the-scenes/000-behind-the-scenes.mpd" type="application/dash+xml"></video><h2 id="Hardware-list"><a href="#Hardware-list" class="headerlink" title="Hardware list"></a>Hardware list</h2><ul><li>Headset: Beyerdynamic DT 297 PV (80 ohm)</li><li>Audio interface: Focusrite Scarlett 2i2</li><li>Camera: Sony Alpha 6400 body with Sony SEL50F18B lens</li><li>Capture Card: 2x Blackmagic DeckLink Mini Recorder</li><li>Main Lightning: ~150€ Ring Light from Amazon</li><li>Head Lightning: LED Panel Light from Neewer</li><li>Background Lightning: 5x Philips Hue White and Color Ambiance Play Lightbar and Philips Hue Bridge</li></ul><blockquote><p>Keep in mind that our lights are not the best available quality. The color temperature will change over time. If the device displays a number, it will be wrong. We used a color temperature meter to align the main and headlight.</p></blockquote><h2 id="Software-we-use"><a href="#Software-we-use" class="headerlink" title="Software we use"></a>Software we use</h2><p>We use <a href="https://obsproject.com/" target="_blank" rel="noopener">OBS</a> controlled by a <a href="https://www.elgato.com/en/gaming/stream-deck" target="_blank" rel="noopener">STREAM DECK</a> controller to record the raw video. We also use OBS for streaming. We run OBS on Windows 10 with an AMD Ryzen 5 3600X 6x3.80GHz processor (water-cooled).</p><blockquote><p>We tried OBS on macOS first. We wasted many hours and finally switched to Windows.</p></blockquote><p>For video editing, we use <a href="https://www.blackmagicdesign.com/de/products/davinciresolve/" target="_blank" rel="noopener">DaVinci Resolve</a>. It is much better compared to everything (Camtasia, iMovie) I used before. You can also find many tutorials on YouTube. We also hired Steffen to edit the videos for us at the beginning of the year, and it works great.</p><h2 id="Video-hosting"><a href="#Video-hosting" class="headerlink" title="Video hosting"></a>Video hosting</h2><p>We started to host the videos on YouTube. But we are not big fans of platforms. They dictate the rules and don’t pay taxes. Luckily, displaying videos on <a href="https://developer.mozilla.org/de/docs/Web/HTML/Element/video" target="_blank" rel="noopener">modern browsers</a> is not too hard. <a href="https://videojs.com/" target="_blank" rel="noopener">Video.js</a> adds “smooth quality change” to deliver the best quality for your bandwidth. We use <a href="https://aws.amazon.com/mediaconvert/" target="_blank" rel="noopener">AWS Elemental MediaConvert</a> to convert the edited video (MOV format) into small chunks that your browser can easily download while you watch. The CDN stays the same: Amazon CloudFront. We will write another blog post about the process of video hosting in the future.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>All you need to know about encrypting S3 buckets</title>
      <link>https://cloudonaut.io/all-you-need-to-know-about-encrypting-s3-buckets/</link>
      <description>
        <![CDATA[<p>Dance like nobody’s watching, encrypt like everyone is. Are you encrypting your data stored on Amazon Simple Storage Service (S3)? No, th]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/kms/">kms</category>
      <guid isPermaLink="true">https://cloudonaut.io/all-you-need-to-know-about-encrypting-s3-buckets/</guid>
      <pubDate>Wed, 03 Mar 2021 20:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Dance like nobody’s watching, encrypt like everyone is. Are you encrypting your data stored on Amazon Simple Storage Service (S3)? No, this video explains why and how to do so. Yes, this video helps you to avoid common pitfalls when doing so.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/03/encryption@730w.webp 730w, /images/2021/03/encryption@730w2x.webp 1460w, /images/2021/03/encryption@610w.webp 610w, /images/2021/03/encryption@610w2x.webp 1220w, /images/2021/03/encryption@450w.webp 450w, /images/2021/03/encryption@450w2x.webp 900w, /images/2021/03/encryption@330w.webp 330w, /images/2021/03/encryption@330w2x.webp 660w, /images/2021/03/encryption@545w.webp 545w, /images/2021/03/encryption@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/03/encryption@730w.jpg 730w, /images/2021/03/encryption@730w2x.jpg 1460w, /images/2021/03/encryption@610w.jpg 610w, /images/2021/03/encryption@610w2x.jpg 1220w, /images/2021/03/encryption@450w.jpg 450w, /images/2021/03/encryption@450w2x.jpg 900w, /images/2021/03/encryption@330w.jpg 330w, /images/2021/03/encryption@330w2x.jpg 660w, /images/2021/03/encryption@545w.jpg 545w, /images/2021/03/encryption@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/03/encryption.jpg" alt="All you need to know about encrypting S3 buckets" title="All you need to know about encrypting S3 buckets"></picture></p><p>After watching the video, you will be able to answer the following questions:</p><ol><li>How to enable default encryption for S3 buckets?</li><li>Which encryption options fit my needs? SSE-S3, SSE-KMS with AWS managed CMK, or SSE-KMS with Customer managed CMK.</li><li>How to make sure no one can lever out the default encryption?</li><li>How does turning on encryption for S3 lead to extensive costs, and what can I do about it?</li></ol><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=SHWcTPcp8As">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/SHWcTPcp8As" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p>The following snippet contains the CloudFormation template used in the video to create a bucket, a bucket policy, as well as key.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Bucket:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::S3::Bucket&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">BucketName:</span> <span class="string">&#x27;cloudonaut-sse-002&#x27;</span></span><br><span class="line">      <span class="attr">BucketEncryption:</span></span><br><span class="line">        <span class="attr">ServerSideEncryptionConfiguration:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">BucketKeyEnabled:</span> <span class="literal">true</span></span><br><span class="line">          <span class="attr">ServerSideEncryptionByDefault:</span></span><br><span class="line">            <span class="attr">SSEAlgorithm:</span> <span class="string">&#x27;aws:kms&#x27;</span></span><br><span class="line">            <span class="attr">KMSMasterKeyID:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Key.Arn&#x27;</span></span><br><span class="line">  <span class="attr">BucketPolicy:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::S3::BucketPolicy&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span> </span><br><span class="line">      <span class="attr">Bucket:</span> <span class="type">!Ref</span> <span class="string">Bucket</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Deny</span></span><br><span class="line">          <span class="attr">Principal:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">s3:PutObject</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:s3:::$&#123;Bucket&#125;/*&#x27;</span></span><br><span class="line">          <span class="attr">Condition:</span></span><br><span class="line">            <span class="attr">StringNotEquals:</span></span><br><span class="line">              <span class="attr">s3:x-amz-server-side-encryption:</span> <span class="string">&#x27;&#x27;</span></span><br><span class="line">              <span class="attr">s3:x-amz-server-side-encryption-aws-kms-key-id:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Key.Arn&#x27;</span></span><br><span class="line">  <span class="attr">Key:</span></span><br><span class="line">    <span class="attr">DeletionPolicy:</span> <span class="string">Retain</span></span><br><span class="line">    <span class="attr">UpdateReplacePolicy:</span> <span class="string">Retain</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::KMS::Key&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">EnableKeyRotation:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">KeyPolicy:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">AWS:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:iam::$&#123;AWS::AccountId&#125;:root&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;kms:*&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br></pre></td></tr></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>CloudFormation Modules</title>
      <link>https://cloudonaut.io/cloudformation-modules/</link>
      <description>
        <![CDATA[<p>Copying and pasting code or configuration comes with a lot of challenges. Fixing a bug requires you to find all the copies to fix them as]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/cloudformation-modules/</guid>
      <pubDate>Thu, 25 Feb 2021 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Copying and pasting code or configuration comes with a lot of challenges. Fixing a bug requires you to find all the copies to fix them as well. Adding new features to all the copies becomes difficult if copies diverge. That’s why many programming languages allow us to define functions and classes. In CloudFormation, you want to reuse templates as well. Think about a VPC template, database template, load-balanced Fargate container, and so on. Let me introduce CloudFormation Modules. A new feature to reuse CloudFormation templates.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/02/modules@730w.webp 730w, /images/2021/02/modules@730w2x.webp 1460w, /images/2021/02/modules@610w.webp 610w, /images/2021/02/modules@610w2x.webp 1220w, /images/2021/02/modules@450w.webp 450w, /images/2021/02/modules@450w2x.webp 900w, /images/2021/02/modules@330w.webp 330w, /images/2021/02/modules@330w2x.webp 660w, /images/2021/02/modules@545w.webp 545w, /images/2021/02/modules@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/02/modules@730w.jpg 730w, /images/2021/02/modules@730w2x.jpg 1460w, /images/2021/02/modules@610w.jpg 610w, /images/2021/02/modules@610w2x.jpg 1220w, /images/2021/02/modules@450w.jpg 450w, /images/2021/02/modules@450w2x.jpg 900w, /images/2021/02/modules@330w.jpg 330w, /images/2021/02/modules@330w2x.jpg 660w, /images/2021/02/modules@545w.jpg 545w, /images/2021/02/modules@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/02/modules.jpg" alt="CloudFormation Modules" title="CloudFormation Modules"></picture></p><p>CloudFormation Modules are available <a href="https://aws.amazon.com/blogs/mt/introducing-aws-cloudformation-modules/" target="_blank" rel="noopener">since November 2020</a>. A module is a plain CloudFormation template (referred to as a fragment in the docs). To use a module, you have to upload it to the CloudFormation Registry. The <a href="https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/what-is-cloudformation-cli.html" target="_blank" rel="noopener">CloudFormation CLI</a> helps you with this process. You can define the resource type of your module, but you have to follow the schema <code>&lt;Organization&gt;::&lt;Service&gt;::&lt;Name&gt;::MODULE</code>. Modules can be nested up to 3 levels deep.</p><p>I created a VPC module that I can use like this:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Vpc:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;cloudonaut::VPC::TwoZones::MODULE&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">ClassB:</span> <span class="string">&#x27;42&#x27;</span></span><br></pre></td></tr></table></figure><p>With a concise template, I can create a lot of AWS resources. The template for the VPC module looks like this:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">ClassB:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;Class B of VPC (10.XXX.0.0/16)&#x27;</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">Number</span></span><br><span class="line">    <span class="attr">Default:</span> <span class="number">0</span></span><br><span class="line">    <span class="attr">ConstraintDescription:</span> <span class="string">&#x27;Must be in the range [0-255]&#x27;</span></span><br><span class="line">    <span class="attr">MinValue:</span> <span class="number">0</span></span><br><span class="line">    <span class="attr">MaxValue:</span> <span class="number">255</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Vpc:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::VPC&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">CidrBlock:</span> <span class="type">!Sub</span> <span class="string">&#x27;10.$&#123;ClassB&#125;.0.0/16&#x27;</span></span><br><span class="line">      <span class="attr">EnableDnsSupport:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">EnableDnsHostnames:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">InstanceTenancy:</span> <span class="string">default</span></span><br><span class="line">  <span class="attr">InternetGateway:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::InternetGateway&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">VpcGatewayAttachment:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::VPCGatewayAttachment&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">VpcId:</span> <span class="type">!Ref</span> <span class="string">Vpc</span></span><br><span class="line">      <span class="attr">InternetGatewayId:</span> <span class="type">!Ref</span> <span class="string">InternetGateway</span></span><br><span class="line">  <span class="attr">SubnetAPublic:</span></span><br><span class="line">    <span class="attr">DependsOn:</span> [<span class="string">VpcGatewayAttachment</span>]</span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;cloudonaut::VPC::Subnet::MODULE&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">VpcId:</span> <span class="type">!Ref</span> <span class="string">Vpc</span></span><br><span class="line">      <span class="attr">VpcCidrBlock:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Vpc.CidrBlock&#x27;</span></span><br><span class="line">      <span class="attr">InternetGatewayId:</span> <span class="type">!Ref</span> <span class="string">InternetGateway</span></span><br><span class="line">      <span class="attr">Reach:</span> <span class="string">public</span></span><br><span class="line">      <span class="attr">AZIndex:</span> <span class="string">&#x27;0&#x27;</span></span><br><span class="line">      <span class="attr">AZChar:</span> <span class="string">A</span></span><br><span class="line">      <span class="attr">SubnetIndex:</span> <span class="string">&#x27;0&#x27;</span></span><br><span class="line">      <span class="attr">SubnetCount:</span> <span class="number">4</span></span><br><span class="line">  <span class="attr">SubnetAPrivate:</span></span><br><span class="line">    <span class="attr">DependsOn:</span> [<span class="string">VpcGatewayAttachment</span>]</span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;cloudonaut::VPC::Subnet::MODULE&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="comment"># [...]</span></span><br><span class="line">  <span class="attr">SubnetBPublic:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line">  <span class="attr">SubnetBPrivate:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">Id:</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">Vpc</span></span><br><span class="line">  <span class="attr">CidrBlock:</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Vpc.CidrBlock&#x27;</span></span><br><span class="line">  <span class="attr">InternetGatewayId:</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">InternetGateway</span></span><br></pre></td></tr></table></figure><p>The VPC module includes the subnet module, which demonstrates nested modules.</p><p>Unfortunately, there are a ton of limitations. I was struggling to create something useful with CloudFormation Modules. Learn from my experience in the following video.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=PM_f-LWdQgc">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/PM_f-LWdQgc" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p>Source Code: <a href="/download/files/cloudformation-modules.zip">cloudformation-modules.zip</a></p><h2 id="Alternatives"><a href="#Alternatives" class="headerlink" title="Alternatives"></a>Alternatives</h2><ul><li>You can reuse templates by exporting outputs and using them via <code>Fn::ImportValue</code>: Example: <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">https://github.com/widdix/aws-cf-templates</a></li><li>Nested stacks can also be helpful. Example: <a href="https://github.com/cfn-modules" target="_blank" rel="noopener">https://github.com/cfn-modules</a></li><li>Last but not least, preprocessors for CloudFormation such as the <a href="https://aws.amazon.com/cdk/" target="_blank" rel="noopener">AWS CDK</a> or <a href="https://github.com/cloudtools/troposphere" target="_blank" rel="noopener">troposphere</a>.</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>Does your VPC endpoint allow access to half of the Internet?</title>
      <link>https://cloudonaut.io/does-your-vpc-endpoint-allow-access-to-half-of-the-internet/</link>
      <description>
        <![CDATA[<p>Are you using VPC endpoints to enable private connections between your VPC and AWS services? Drop everything and check the policy attache]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <guid isPermaLink="true">https://cloudonaut.io/does-your-vpc-endpoint-allow-access-to-half-of-the-internet/</guid>
      <pubDate>Mon, 22 Feb 2021 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Are you using VPC endpoints to enable private connections between your VPC and AWS services? Drop everything and check the policy attached to your VPC Endpoint for S3. You might have allowed access to half of the Internet - assuming that half of the Internet is hosted on S3 - from your private network accidentally.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/02/lock@730w.webp 730w, /images/2021/02/lock@730w2x.webp 1460w, /images/2021/02/lock@610w.webp 610w, /images/2021/02/lock@610w2x.webp 1220w, /images/2021/02/lock@450w.webp 450w, /images/2021/02/lock@450w2x.webp 900w, /images/2021/02/lock@330w.webp 330w, /images/2021/02/lock@330w2x.webp 660w, /images/2021/02/lock@545w.webp 545w, /images/2021/02/lock@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/02/lock@730w.jpg 730w, /images/2021/02/lock@730w2x.jpg 1460w, /images/2021/02/lock@610w.jpg 610w, /images/2021/02/lock@610w2x.jpg 1220w, /images/2021/02/lock@450w.jpg 450w, /images/2021/02/lock@450w2x.jpg 900w, /images/2021/02/lock@330w.jpg 330w, /images/2021/02/lock@330w2x.jpg 660w, /images/2021/02/lock@545w.jpg 545w, /images/2021/02/lock@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/02/lock.jpg" alt="Does your VPC endpoint allow access to half of the Internet?" title="Does your VPC endpoint allow access to half of the Internet?"></picture></p><h2 id="The-Problem"><a href="#The-Problem" class="headerlink" title="The Problem"></a>The Problem</h2><p>By default, a VPC endpoint for S3 allows access to all S3 buckets. So an EC2 instance running in your VPC has access to all S3 buckets worldwide. Not only the S3 buckets you own but also the S3 buckets owned by other AWS customers. For example, anyone with access to your VPC could copy data from your systems to S3 buckets owned by someone else. Of course, the other way round - downloading data from S3 aka. half of the Internet - is possible as well.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/02/vpc-endpoint@730w.webp 730w, /images/2021/02/vpc-endpoint@730w2x.webp 1460w, /images/2021/02/vpc-endpoint@610w.webp 610w, /images/2021/02/vpc-endpoint@610w2x.webp 1220w, /images/2021/02/vpc-endpoint@450w.webp 450w, /images/2021/02/vpc-endpoint@450w2x.webp 900w, /images/2021/02/vpc-endpoint@330w.webp 330w, /images/2021/02/vpc-endpoint@330w2x.webp 660w, /images/2021/02/vpc-endpoint@545w.webp 545w, /images/2021/02/vpc-endpoint@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/02/vpc-endpoint@730w.png 730w, /images/2021/02/vpc-endpoint@730w2x.png 1460w, /images/2021/02/vpc-endpoint@610w.png 610w, /images/2021/02/vpc-endpoint@610w2x.png 1220w, /images/2021/02/vpc-endpoint@450w.png 450w, /images/2021/02/vpc-endpoint@450w2x.png 900w, /images/2021/02/vpc-endpoint@330w.png 330w, /images/2021/02/vpc-endpoint@330w2x.png 660w, /images/2021/02/vpc-endpoint@545w.png 545w, /images/2021/02/vpc-endpoint@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/02/vpc-endpoint.png" alt="A VPC endpoint allows access to any S3 bucket by default." title="A VPC endpoint allows access to any S3 bucket by default."></picture></p><h2 id="The-Solution"><a href="#The-Solution" class="headerlink" title="The Solution"></a>The Solution</h2><p>By attaching an <a href="https://docs.aws.amazon.com/vpc/latest/userguide/vpc-endpoints-s3.html#vpc-endpoints-policies-s3" target="_blank" rel="noopener">endpoint policy</a> to your VPC endpoint for S3, you can restrict the usage of the endpoint to certain S3 buckets. I came up with the following endpoint policy recently, which allows access to all S3 buckets owned by your AWS account. In my opinion, a good tradeoff limiting access and configuration overhead.</p><p>Make sure to replace <code>111111111111</code> with your AWS account ID.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2008-10-17&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Statement&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Principal&quot;</span>: <span class="string">&quot;*&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Action&quot;</span>: <span class="string">&quot;s3:*&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;*&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Condition&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;StringEquals&quot;</span>: &#123;</span><br><span class="line">          <span class="string">&quot;s3:ResourceAccount&quot;</span>: <span class="string">&quot;111111111111&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>Warning: In case you need to access S3 buckets from your VPC that you do not own, you need to extend the endpoint policy. That’s example the case for S3 buckets offered by AWS.</p></blockquote><p>That’s it. Don’t forget, details are important!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Client VPN: Connected with the Cloud</title>
      <link>https://cloudonaut.io/aws-client-vpn-connected-with-the-cloud/</link>
      <description>
        <![CDATA[<p>Everybody talks about remote work those days. A fundamental prerequisite is to provide secure connectivity to your infrastructure. No mat]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-client-vpn-connected-with-the-cloud/</guid>
      <pubDate>Thu, 18 Feb 2021 21:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Everybody talks about remote work those days. A fundamental prerequisite is to provide secure connectivity to your infrastructure. No matter if only a few engineers or users need to access resources within a private network. About two years ago, AWS announced AWS Client VPN, a managed client-based VPN service that allows you to connect users with your VPCs. Over the past two years, the service matured and is worth a look.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/02/remotework@730w.webp 730w, /images/2021/02/remotework@730w2x.webp 1460w, /images/2021/02/remotework@610w.webp 610w, /images/2021/02/remotework@610w2x.webp 1220w, /images/2021/02/remotework@450w.webp 450w, /images/2021/02/remotework@450w2x.webp 900w, /images/2021/02/remotework@330w.webp 330w, /images/2021/02/remotework@330w2x.webp 660w, /images/2021/02/remotework@545w.webp 545w, /images/2021/02/remotework@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/02/remotework@730w.jpg 730w, /images/2021/02/remotework@730w2x.jpg 1460w, /images/2021/02/remotework@610w.jpg 610w, /images/2021/02/remotework@610w2x.jpg 1220w, /images/2021/02/remotework@450w.jpg 450w, /images/2021/02/remotework@450w2x.jpg 900w, /images/2021/02/remotework@330w.jpg 330w, /images/2021/02/remotework@330w2x.jpg 660w, /images/2021/02/remotework@545w.jpg 545w, /images/2021/02/remotework@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/02/remotework.jpg" alt="AWS Client VPN: Connected with the Cloud" title="AWS Client VPN: Connected with the Cloud"></picture></p><p>What does AWS Client VPN offer?</p><ul><li>Managed VPN service based on OpenVPN</li><li>Secure client-to-site connections (TLS)</li><li>Multi-AZ + Scales automatically</li><li>Active Directory, federated authentication (SAML), and certificate-based authentication</li></ul><p>By the way, AWS Client VPN starts at USD 150 per month.</p><p>What surprised me the most? AWS Client VPN supports SAML for federated authentication. For example, it is possible to integrate with Azure Active Directory to re-use existing users and groups for authentication and authorization into your VPCs via VPN.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/02/clientvpn@730w.webp 730w, /images/2021/02/clientvpn@730w2x.webp 1460w, /images/2021/02/clientvpn@610w.webp 610w, /images/2021/02/clientvpn@610w2x.webp 1220w, /images/2021/02/clientvpn@450w.webp 450w, /images/2021/02/clientvpn@450w2x.webp 900w, /images/2021/02/clientvpn@330w.webp 330w, /images/2021/02/clientvpn@330w2x.webp 660w, /images/2021/02/clientvpn@545w.webp 545w, /images/2021/02/clientvpn@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/02/clientvpn@730w.png 730w, /images/2021/02/clientvpn@730w2x.png 1460w, /images/2021/02/clientvpn@610w.png 610w, /images/2021/02/clientvpn@610w2x.png 1220w, /images/2021/02/clientvpn@450w.png 450w, /images/2021/02/clientvpn@450w2x.png 900w, /images/2021/02/clientvpn@330w.png 330w, /images/2021/02/clientvpn@330w2x.png 660w, /images/2021/02/clientvpn@545w.png 545w, /images/2021/02/clientvpn@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/02/clientvpn.png" alt="AWS Client VPN with Azure Active Directory for authN and authZ" title="AWS Client VPN with Azure Active Directory for authN and authZ"></picture></p><h2 id="Watch-the-video"><a href="#Watch-the-video" class="headerlink" title="Watch the video"></a>Watch the video</h2><p>Want to learn more about AWS Client VPN? The following video includes a demo of how to configure AWS Client VPN and Azure Active Directory, among other things.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=rq3UXjGBZdE">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/rq3UXjGBZdE" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p>Check out <a href="https://aws.amazon.com/blogs/apn/how-to-integrate-aws-client-vpn-with-azure-active-directory/" target="_blank" rel="noopener">How to Integrate AWS Client VPN with Azure Active Directory</a> for detailed instructions.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>It is about time to replace handcrafted EC2 instances running VPN servers with AWS Client VPN. Also, AWS Client VPN is the perfect way to connect your remote workers with the Cloud.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>5 good reasons not to get AWS certified</title>
      <link>https://cloudonaut.io/5-good-reasons-not-to-get-aws-certified/</link>
      <description>
        <![CDATA[<p>I completed my first AWS certification in 2014: AWS Solutions Architect Associate. During the following years, I accomplished all five as]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/career/">career</category>
      <guid isPermaLink="true">https://cloudonaut.io/5-good-reasons-not-to-get-aws-certified/</guid>
      <pubDate>Wed, 17 Feb 2021 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>I completed my first AWS certification in 2014: AWS Solutions Architect Associate. During the following years, I accomplished all five associate and professional certificates. However, Michael and I decided not to renew our AWS certifications about a year ago. In the following, I will share five good reasons not to get AWS certified!</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/02/unbelievable@730w.webp 730w, /images/2021/02/unbelievable@730w2x.webp 1460w, /images/2021/02/unbelievable@610w.webp 610w, /images/2021/02/unbelievable@610w2x.webp 1220w, /images/2021/02/unbelievable@450w.webp 450w, /images/2021/02/unbelievable@450w2x.webp 900w, /images/2021/02/unbelievable@330w.webp 330w, /images/2021/02/unbelievable@330w2x.webp 660w, /images/2021/02/unbelievable@545w.webp 545w, /images/2021/02/unbelievable@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/02/unbelievable@730w.jpg 730w, /images/2021/02/unbelievable@730w2x.jpg 1460w, /images/2021/02/unbelievable@610w.jpg 610w, /images/2021/02/unbelievable@610w2x.jpg 1220w, /images/2021/02/unbelievable@450w.jpg 450w, /images/2021/02/unbelievable@450w2x.jpg 900w, /images/2021/02/unbelievable@330w.jpg 330w, /images/2021/02/unbelievable@330w2x.jpg 660w, /images/2021/02/unbelievable@545w.jpg 545w, /images/2021/02/unbelievable@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/02/unbelievable.jpg" alt="5 good reasons not to get AWS certified" title="5 good reasons not to get AWS certified"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/38-5-good-reasons-not-to-get-aws-certified/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="Stay-away-from-the-race-to-the-bottom"><a href="#Stay-away-from-the-race-to-the-bottom" class="headerlink" title="Stay away from the race to the bottom"></a>Stay away from the race to the bottom</h2><p>More and more IT professionals are getting AWS certified. Recently, Amazon announced <a href="https://www.aboutamazon.com/news/workplace/amazon-to-help-29-million-people-around-the-world-grow-their-tech-skills-with-free-cloud-computing-skills-training-by-2025" target="_blank" rel="noopener">to help 29 million people around the world grow their tech skills with free cloud computing skills training by 2025</a>. It is a race to the bottom. In the end, an AWS certification will not distinguish you from your peers anymore.</p><p>Find a way to escape the race to the bottom. You will find some inspiration in the following.</p><h2 id="Showcase-your-skills-with-your-work-instead-of-a-certificate"><a href="#Showcase-your-skills-with-your-work-instead-of-a-certificate" class="headerlink" title="Showcase your skills with your work instead of a certificate"></a>Showcase your skills with your work instead of a certificate</h2><p>Whenever I’m involved in hiring a freelancer or employee, I look for a showcase of the applicant’s work. It is not that hard to fake an AWS certification. Having a look into the results of an applicant’s work is much more revealing.</p><p>Therefore, start your journey into the cloud and showcase the skills you acquire along the way. Start a blog, create a video channel, maintain an open-source repository, speak at the local user group, or find any other way to share your learnings and expertise.</p><h2 id="Be-unique-define-your-own-curriculum"><a href="#Be-unique-define-your-own-curriculum" class="headerlink" title="Be unique, define your own curriculum"></a>Be unique, define your own curriculum</h2><p>When aiming for an AWS certification, you need to follow the learning path defined by Amazon. In the end, your skillset is not unique but similar to millions of other IT professionals. Also, you will learn a lot of things that you are not interested in.</p><p>Pick your focus areas instead. Start with Serverless, Machine Learning, Networking, Databases, whatever sounds most interesting to you. It will be a lot more fun and allows you to differentiate your skillset.</p><h2 id="Learning-by-doing-is-much-more-effective-than-memorizing-answers"><a href="#Learning-by-doing-is-much-more-effective-than-memorizing-answers" class="headerlink" title="Learning by doing is much more effective than memorizing answers"></a>Learning by doing is much more effective than memorizing answers</h2><p>Let’s face it. How do 99% prepare for AWS certification? By memorizing questions and answers. That’s not a very effective way of learning.</p><p>I highly recommend learning by doing. It is harder to do so, but in the end, you know how to solve problems instead of answering multiple-choice questions. Migrate a website to AWS, implement a simple Serverless application, backup your data on S3, or implement an IoT use case.</p><h2 id="Avoid-marketing-nonsense-by-learning-from-independents"><a href="#Avoid-marketing-nonsense-by-learning-from-independents" class="headerlink" title="Avoid marketing nonsense by learning from independents"></a>Avoid marketing nonsense by learning from independents</h2><p>AWS invests a lot into learning materials, which are used by their partners as well. Very seldomly you will read about limitations and pitfalls in those learning materials. To me, a lot of the content feels more like marketing content than technical materials. The problem with that is that you will not learn about the rough edges of the cloud. But that’s the expertise that distinguishes between success and failure in the real world.</p><p>Find independents to learn from and avoid AWS and their partners.</p><ul><li><a href="https://www.manning.com/books/serverless-architectures-on-aws-second-edition" target="_blank" rel="noopener">AppSync Masterclass</a> by Yan Cui.</li><li><a href="https://www.dynamodbbook.com/" target="_blank" rel="noopener">The DynamoDB Book</a> by Alex DeBrie.</li><li><a href="https://gumroad.com/l/aws-good-parts" target="_blank" rel="noopener">The Good Parts of AWS</a> by Daniel Vassallo.</li></ul><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>For sure, there are good reasons to get AWS certified. But, there are good reasons against doing so as well. The most important one, getting AWS certified, is a race to the bottom. Go on your own journey!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Unboxing Amazon Managed Service for Prometheus (AMP)</title>
      <link>https://cloudonaut.io/unboxing-amazon-managed-service-for-prometheus-amp/</link>
      <description>
        <![CDATA[<p>AWS announced Amazon Managed Service for Prometheus (AMP) in December 2020 during re:Invent. Prometheus is a monitoring system as well as]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/amp/">amp</category>
      <guid isPermaLink="true">https://cloudonaut.io/unboxing-amazon-managed-service-for-prometheus-amp/</guid>
      <pubDate>Fri, 12 Feb 2021 14:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>AWS announced Amazon Managed Service for Prometheus (AMP) in December 2020 during re:Invent. Prometheus is a monitoring system as well as a time-series database. Let’s take a first look at the managed service that is currently in public preview.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/02/meter@730w.webp 730w, /images/2021/02/meter@730w2x.webp 1460w, /images/2021/02/meter@610w.webp 610w, /images/2021/02/meter@610w2x.webp 1220w, /images/2021/02/meter@450w.webp 450w, /images/2021/02/meter@450w2x.webp 900w, /images/2021/02/meter@330w.webp 330w, /images/2021/02/meter@330w2x.webp 660w, /images/2021/02/meter@545w.webp 545w, /images/2021/02/meter@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/02/meter@730w.jpg 730w, /images/2021/02/meter@730w2x.jpg 1460w, /images/2021/02/meter@610w.jpg 610w, /images/2021/02/meter@610w2x.jpg 1220w, /images/2021/02/meter@450w.jpg 450w, /images/2021/02/meter@450w2x.jpg 900w, /images/2021/02/meter@330w.jpg 330w, /images/2021/02/meter@330w2x.jpg 660w, /images/2021/02/meter@545w.jpg 545w, /images/2021/02/meter@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/02/meter.jpg" alt="Unboxing Amazon Managed Service for Prometheus (AMP)" title="Unboxing Amazon Managed Service for Prometheus (AMP)"></picture></p><h2 id="What-is-Prometheus"><a href="#What-is-Prometheus" class="headerlink" title="What is Prometheus?"></a>What is Prometheus?</h2><ul><li><strong>Monitoring System</strong> designed machine-centric monitoring as well as monitoring of highly dynamic service-oriented architectures.</li><li><strong>Time Series Database</strong> storing data in memory and on local disk in an efficient custom format. Scaling is achieved by functional sharding and federation.</li><li><strong>Open Source project</strong> built initially at SoundCloud. Prometheus is a standalone open-source project and maintained independently of any company which joined the Cloud Native Computing Foundation in 2016.</li></ul><p>On AWS, AMP competes with CloudWatch, the built-in, and proprietary monitoring solution.</p><h2 id="Key-facts"><a href="#Key-facts" class="headerlink" title="Key facts"></a>Key facts</h2><p>The key facts about AMP in brief:</p><ul><li>AMP is in public preview and not yet production-ready.</li><li>AMP is available in five regions and multi-AZ by default.</li><li>AMP scales ingestion, storage, and querying automatically.</li><li>AMP does not support pulling data - an essential feature for smooth integration with K8s. Currently, only pushing data is supported.</li><li>AMP does not support the alerting manager yet.</li><li>AMP does not allow to upload metric samples older than 1 hour and deletes data after 150 days.</li><li>AMP comes with encryption-at-rest based on KMS and uses HTTPS for all communication - even internal traffic between AZs.</li></ul><h2 id="Watch-the-video"><a href="#Watch-the-video" class="headerlink" title="Watch the video"></a>Watch the video</h2><p>Want to learn more? Watch my video, including a demo about how to get metrics from your EKS (managed Kubernetes) cluster to AMP.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=MHrlp6wjvbY">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/MHrlp6wjvbY" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p>I’ve used the following guides to setup my EKS + AMP demo.</p><ul><li><a href="https://docs.aws.amazon.com/prometheus/latest/userguide/AMP-onboard-ingest-metrics-existing-Prometheus.html" target="_blank" rel="noopener">Set up ingestion from an existing Prometheus server in Kubernetes</a></li><li><a href="https://docs.aws.amazon.com/prometheus/latest/userguide/AMP-onboard-query-grafana-7.3.html" target="_blank" rel="noopener">Query using Grafana running in an Amazon EKS cluster</a></li></ul><p>The <a href="https://docs.google.com/spreadsheets/d/1TMa6exofo5a9bgpZzdqAeUPAqLwkm2WoBUuV3GGyCsE/" target="_blank" rel="noopener">spreadsheet</a> allows you to compare the monthly costs of AMP and CloudWatch custom metrics.</p><h2 id="Comparing-costs-between-AMP-and-CloudWatch"><a href="#Comparing-costs-between-AMP-and-CloudWatch" class="headerlink" title="Comparing costs between AMP and CloudWatch"></a>Comparing costs between AMP and CloudWatch</h2><p>The pricing model of AMP and CloudWatch are very different. That’s why CloudWatch could be much more cost-effective than AMP or the other way round, depending on your workload.</p><p>For example, in the following scenario with more writes than reads, CloudWatch metrics are more expensive than AMP.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/02/costs_amp_cloudwatch_01@730w.webp 730w, /images/2021/02/costs_amp_cloudwatch_01@730w2x.webp 1460w, /images/2021/02/costs_amp_cloudwatch_01@610w.webp 610w, /images/2021/02/costs_amp_cloudwatch_01@610w2x.webp 1220w, /images/2021/02/costs_amp_cloudwatch_01@450w.webp 450w, /images/2021/02/costs_amp_cloudwatch_01@450w2x.webp 900w, /images/2021/02/costs_amp_cloudwatch_01@330w.webp 330w, /images/2021/02/costs_amp_cloudwatch_01@330w2x.webp 660w, /images/2021/02/costs_amp_cloudwatch_01@545w.webp 545w, /images/2021/02/costs_amp_cloudwatch_01@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/02/costs_amp_cloudwatch_01@730w.png 730w, /images/2021/02/costs_amp_cloudwatch_01@730w2x.png 1460w, /images/2021/02/costs_amp_cloudwatch_01@610w.png 610w, /images/2021/02/costs_amp_cloudwatch_01@610w2x.png 1220w, /images/2021/02/costs_amp_cloudwatch_01@450w.png 450w, /images/2021/02/costs_amp_cloudwatch_01@450w2x.png 900w, /images/2021/02/costs_amp_cloudwatch_01@330w.png 330w, /images/2021/02/costs_amp_cloudwatch_01@330w2x.png 660w, /images/2021/02/costs_amp_cloudwatch_01@545w.png 545w, /images/2021/02/costs_amp_cloudwatch_01@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/02/costs_amp_cloudwatch_01.png" alt="Comparing costs between AMP and CloudWatch (1)" title="Comparing costs between AMP and CloudWatch (1)"></picture></p><p>However, when you query your metrics frequently, AMP gets much more expensive than CloudWatch metrics.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/02/costs_amp_cloudwatch_02@730w.webp 730w, /images/2021/02/costs_amp_cloudwatch_02@730w2x.webp 1460w, /images/2021/02/costs_amp_cloudwatch_02@610w.webp 610w, /images/2021/02/costs_amp_cloudwatch_02@610w2x.webp 1220w, /images/2021/02/costs_amp_cloudwatch_02@450w.webp 450w, /images/2021/02/costs_amp_cloudwatch_02@450w2x.webp 900w, /images/2021/02/costs_amp_cloudwatch_02@330w.webp 330w, /images/2021/02/costs_amp_cloudwatch_02@330w2x.webp 660w, /images/2021/02/costs_amp_cloudwatch_02@545w.webp 545w, /images/2021/02/costs_amp_cloudwatch_02@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/02/costs_amp_cloudwatch_02@730w.png 730w, /images/2021/02/costs_amp_cloudwatch_02@730w2x.png 1460w, /images/2021/02/costs_amp_cloudwatch_02@610w.png 610w, /images/2021/02/costs_amp_cloudwatch_02@610w2x.png 1220w, /images/2021/02/costs_amp_cloudwatch_02@450w.png 450w, /images/2021/02/costs_amp_cloudwatch_02@450w2x.png 900w, /images/2021/02/costs_amp_cloudwatch_02@330w.png 330w, /images/2021/02/costs_amp_cloudwatch_02@330w2x.png 660w, /images/2021/02/costs_amp_cloudwatch_02@545w.png 545w, /images/2021/02/costs_amp_cloudwatch_02@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/02/costs_amp_cloudwatch_02.png" alt="Comparing costs between AMP and CloudWatch (1)" title="Comparing costs between AMP and CloudWatch (1)"></picture></p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>AWS is going in an interesting direction with AMP. Form a customer perspective; it is a good thing that AMP competes with CloudWatch. However, AMP is far away from being a production-ready and straightforward to use service. I’m looking forward to the releases during the following months and years.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Guest authors wanted!</title>
      <link>https://cloudonaut.io/guest-authors-wanted/</link>
      <description>
        <![CDATA[<p>Have you ever dreamed of publishing an article on cloudonaut? Are you a freelancer or employee who wants to attract attention to boost yo]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/guest-authors-wanted/</guid>
      <pubDate>Wed, 10 Feb 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Have you ever dreamed of publishing an article on cloudonaut? Are you a freelancer or employee who wants to attract attention to boost your career? This is your chance! We are looking for guest authors who are interested in publishing an article on cloudonaut in 2021.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/02/write@730w.webp 730w, /images/2021/02/write@730w2x.webp 1460w, /images/2021/02/write@610w.webp 610w, /images/2021/02/write@610w2x.webp 1220w, /images/2021/02/write@450w.webp 450w, /images/2021/02/write@450w2x.webp 900w, /images/2021/02/write@330w.webp 330w, /images/2021/02/write@330w2x.webp 660w, /images/2021/02/write@545w.webp 545w, /images/2021/02/write@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/02/write@730w.jpg 730w, /images/2021/02/write@730w2x.jpg 1460w, /images/2021/02/write@610w.jpg 610w, /images/2021/02/write@610w2x.jpg 1220w, /images/2021/02/write@450w.jpg 450w, /images/2021/02/write@450w2x.jpg 900w, /images/2021/02/write@330w.jpg 330w, /images/2021/02/write@330w2x.jpg 660w, /images/2021/02/write@545w.jpg 545w, /images/2021/02/write@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/02/write.jpg" alt="Guest authors wanted!" title="Guest authors wanted!"></picture></p><p>Here are our conditions:</p><ul><li>Profit from our reach and put your article in front of a large readership consisting of AWS professionals.</li><li>We expect an article discussing a technical aspect of AWS with ~1,000 words and 3-4 illustrations, tables, or code examples.</li><li>We will publish the article along with a profile picture, bio, and links to your personal presence on the Internet.</li><li>Michael and I will peer-review your article to avoid technical faults.</li><li>We hire and pay for a professional proofreading service to correct spelling and grammar mistakes.</li><li>Your article must be unique and not published on other platforms.</li><li>We do not accept any form of advertising for products, services, or open-source projects.</li><li>Optionally, we invite you to the cloudonaut podcast to discuss your article.</li></ul><p><strong><a href="https://forms.gle/P6ychp2dGijxYw7v5" target="_blank" rel="noopener">Apply now!</a></strong></p><p><em>Do you have any questions? Let us know!</em></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>What's new about AWS SDK for JavaScript v3?</title>
      <link>https://cloudonaut.io/whats-new-about-aws-sdk-for-javascript-v3/</link>
      <description>
        <![CDATA[<p>The AWS SDK for JavaScript v3 is generally available since December 2020. This challenge unites all AWS users: is this new version worth]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/sdk/">sdk</category>
      <guid isPermaLink="true">https://cloudonaut.io/whats-new-about-aws-sdk-for-javascript-v3/</guid>
      <pubDate>Wed, 03 Feb 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The AWS SDK for JavaScript v3 is generally available since December 2020. This challenge unites all AWS users: is this new version worth investing your precious time?</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/02/refactoring@730w.webp 730w, /images/2021/02/refactoring@730w2x.webp 1460w, /images/2021/02/refactoring@610w.webp 610w, /images/2021/02/refactoring@610w2x.webp 1220w, /images/2021/02/refactoring@450w.webp 450w, /images/2021/02/refactoring@450w2x.webp 900w, /images/2021/02/refactoring@330w.webp 330w, /images/2021/02/refactoring@330w2x.webp 660w, /images/2021/02/refactoring@545w.webp 545w, /images/2021/02/refactoring@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/02/refactoring@730w.jpg 730w, /images/2021/02/refactoring@730w2x.jpg 1460w, /images/2021/02/refactoring@610w.jpg 610w, /images/2021/02/refactoring@610w2x.jpg 1220w, /images/2021/02/refactoring@450w.jpg 450w, /images/2021/02/refactoring@450w2x.jpg 900w, /images/2021/02/refactoring@330w.jpg 330w, /images/2021/02/refactoring@330w2x.jpg 660w, /images/2021/02/refactoring@545w.jpg 545w, /images/2021/02/refactoring@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/02/refactoring.jpg" alt="Migrating to AWS SDK for JavaScript v3" title="Migrating to AWS SDK for JavaScript v3"></picture></p><p>In this blog post, I show you the new capabilities and use cases where they help you most, no matter if you use JavaScript on the frontend or the backend (Node.js). Let’s get started.</p><h2 id="Pagination"><a href="#Pagination" class="headerlink" title="Pagination"></a>Pagination</h2><p>Many AWS APIs could potentially return a long list of data (e.g., list all S3 objects in a bucket). All list APIs offer a paging mechanism to retrieve one batch at a time. Each batch contains a token to retrieve the next batch or indicate that you reached the list’s end. Good old v2 code with callbacks looked like this:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> dynamodb = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">DynamoDB</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-08-10&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fetchPage</span>(<span class="params">lastEvaluatedKey, cb</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> params = &#123;</span><br><span class="line">    <span class="title class_">TableName</span>: <span class="string">&#x27;users&#x27;</span>,</span><br><span class="line">    <span class="title class_">Limit</span>: <span class="number">100</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">if</span> (lastEvaluatedKey) &#123;</span><br><span class="line">    params.<span class="property">ExclusiveStartKey</span> = lastEvaluatedKey;</span><br><span class="line">  &#125;</span><br><span class="line">  dynamodb.<span class="title function_">scan</span>(params, cb);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fetchAll</span>(<span class="params">cb</span>) &#123;</span><br><span class="line">  <span class="title function_">fetchPage</span>(<span class="literal">null</span>, <span class="keyword">function</span>(<span class="params">err, data</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) &#123;</span><br><span class="line">      <span class="title function_">cb</span>(err);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      data.<span class="property">Items</span>.<span class="title function_">forEach</span>(<span class="variable language_">console</span>.<span class="property">log</span>);</span><br><span class="line">      <span class="keyword">if</span> (data.<span class="property">LastEvaluatedKey</span>) &#123;</span><br><span class="line">        process.<span class="title function_">nextTick</span>(<span class="function">() =&gt;</span> <span class="title function_">fetchPage</span>(data.<span class="property">LastEvaluatedKey</span>, cb));</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="title function_">cb</span>();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">fetchAll</span>(<span class="keyword">function</span> (<span class="params">err</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) &#123;</span><br><span class="line">    <span class="comment">// panic</span></span><br><span class="line">    process.<span class="title function_">exit</span>(<span class="number">1</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;done&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>Luckily, the JavaScript language evolved. We now have Promises, async&#x2F;await, and <a href="https://hacks.mozilla.org/2015/05/es6-in-depth-generators/" target="_blank" rel="noopener">generators</a>. That’s why we can now write the following using the v3 SDK:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">DynamoDBClient</span>, paginateScan &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-sdk/client-dynamodb&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> dynamodb = <span class="keyword">new</span> <span class="title class_">DynamoDBClient</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-08-10&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">fetchAll</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> config = &#123;</span><br><span class="line">    <span class="attr">client</span>: dynamodb,</span><br><span class="line">    <span class="attr">pageSize</span>: <span class="number">100</span></span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">const</span> input = &#123;</span><br><span class="line">    <span class="title class_">TableName</span>: <span class="string">&#x27;users&#x27;</span></span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">const</span> paginator = <span class="title function_">paginateScan</span>(config, input);</span><br><span class="line">  <span class="keyword">for</span> <span class="title function_">await</span> (<span class="keyword">const</span> page <span class="keyword">of</span> paginator) &#123;</span><br><span class="line">    page.<span class="property">Items</span>.<span class="title function_">forEach</span>(<span class="variable language_">console</span>.<span class="property">log</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">fetchAll</span>()</span><br><span class="line">  .<span class="title function_">then</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;done&#x27;</span>))</span><br><span class="line">  .<span class="title function_">catch</span>(<span class="function">() =&gt;</span> process.<span class="title function_">exit</span>(<span class="number">1</span>));</span><br></pre></td></tr></table></figure><p>I’m happy that I don’t have to write pagination code anymore. Keep in mind that in almost all user-facing scenarios, you should avoid getting all items at once. It will take a long time, and you might run out of memory! I believe that backend developers benefit most from this feature when they write “batch job” code.</p><h2 id="Promises"><a href="#Promises" class="headerlink" title="Promises"></a>Promises</h2><p>Many programmers prefer <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" target="_blank" rel="noopener">promises</a> over the callback approach. A callback approach using the v2 SDK looks like this:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> dynamodb = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">DynamoDB</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-08-10&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line">dynamodb.<span class="title function_">getItem</span>(&#123;<span class="comment">/*...*/</span>&#125;, <span class="keyword">function</span>(<span class="params">err, data</span>) &#123;</span><br><span class="line">  <span class="comment">// this code is invoked as soon as the result is available</span></span><br><span class="line">  dynamodb.<span class="title function_">updateItem</span>(&#123;<span class="comment">/*...*/</span>&#125;, <span class="keyword">function</span>(<span class="params">err, data</span>) &#123;</span><br><span class="line">    <span class="comment">// if you nest many callback you might end up in &quot;callback hell&quot;</span></span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>The promise approach combined with async&#x2F;await in v2 looks like this:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> dynamodb = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">DynamoDB</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-08-10&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">demo</span>(<span class="params"></span>) &#123;&#125;</span><br><span class="line">  <span class="keyword">const</span> data = <span class="keyword">await</span> dynamodb.<span class="title function_">getItem</span>(&#123;<span class="comment">/*...*/</span>&#125;).<span class="title function_">promise</span>();</span><br><span class="line">  <span class="keyword">await</span> dynamodb.<span class="title function_">updateItem</span>(&#123;<span class="comment">/*...*/</span>&#125;).<span class="title function_">promise</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">demo</span>()</span><br><span class="line">  .<span class="title function_">then</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;done&#x27;</span>))</span><br><span class="line">  .<span class="title function_">catch</span>(<span class="function">() =&gt;</span> process.<span class="title function_">exit</span>(<span class="number">1</span>));</span><br></pre></td></tr></table></figure><p>With the new v3 SDK, you can now write:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">DynamoDB</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-sdk/client-dynamodb&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> dynamodb = <span class="keyword">new</span> <span class="title class_">DynamoDB</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-08-10&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">demo</span>(<span class="params"></span>) &#123;&#125;</span><br><span class="line">  <span class="keyword">const</span> data = <span class="keyword">await</span> dynamodb.<span class="title function_">getItem</span>(&#123;<span class="comment">/*...*/</span>&#125;); <span class="comment">// no .promise() needed anymore!</span></span><br><span class="line">  <span class="keyword">await</span> dynamodb.<span class="title function_">updateItem</span>(&#123;<span class="comment">/*...*/</span>&#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">demo</span>()</span><br><span class="line">  .<span class="title function_">then</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;done&#x27;</span>))</span><br><span class="line">  .<span class="title function_">catch</span>(<span class="function">() =&gt;</span> process.<span class="title function_">exit</span>(<span class="number">1</span>));</span><br></pre></td></tr></table></figure><p>This new feature is great for both frontend and backend developers.</p><h2 id="Modularization-Tree-Shaking"><a href="#Modularization-Tree-Shaking" class="headerlink" title="Modularization &amp; Tree Shaking"></a>Modularization &amp; Tree Shaking</h2><p>The v2 SDK packaged all AWS services. Over time, the <code>aws-sdk</code> module grew in size. And that’s a hard challenge if you use the SDK on the frontend. It was just too big and slowed down your website. Two features of the new v3 SDK will help:</p><ul><li>Modularization</li><li>Tree shaking</li></ul><p>Let me dive into both features.</p><p>Each AWS service is now packaged as its own npm module. You want to use DynamoDB, install the <code>@aws-sdk/client-dynamodb</code> package. Need some S3? Install <code>@aws-sdk/client-s3</code> and so on.</p><p>JavaScript nowadays supports ES6 modules. Combined with tools like <a href="https://esbuild.github.io/" target="_blank" rel="noopener">esbuild</a> or <a href="https://webpack.js.org/" target="_blank" rel="noopener">webpack</a>, we can eliminate the dead JS code that is “not used”. This is called tree shaking. If you want to benefit from tree shaking, don’t use the approach that is compatible with the v2 SDK:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">DynamoDB</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-sdk/client-dynamodb&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> dynamodb = <span class="keyword">new</span> <span class="title class_">DynamoDB</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-08-10&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">await</span> dynamodb.<span class="title function_">getItem</span>(<span class="comment">/*...*/</span>);</span><br></pre></td></tr></table></figure><p>Instead, use:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">DynamoDBClient</span>, <span class="title class_">GetItemCommand</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-sdk/client-dynamodb&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> dynamodb = <span class="keyword">new</span> <span class="title class_">DynamoDBClient</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-08-10&#x27;</span>&#125;);</span><br><span class="line"><span class="keyword">await</span> client.<span class="title function_">send</span>(<span class="keyword">new</span> <span class="title class_">GetItemCommand</span>(<span class="comment">/*...*/</span>));</span><br></pre></td></tr></table></figure><p>Now, tree shaking can kick in and remove all the commands that you never use! Frontend developers will love it!</p><blockquote><p>The Node.js Lambda runtime does not include the v3 SDK. If you want to keep your Lambda function code as small as possible, you should still use the v2 SDK because you don’t have to include it at all in your ZIP file.</p></blockquote><h2 id="The-bad-parts"><a href="#The-bad-parts" class="headerlink" title="The bad parts"></a>The bad parts</h2><ul><li>X-Ray support missing (tracked <a href="https://github.com/aws/aws-sdk-js-v3/issues/1795" target="_blank" rel="noopener">here</a> and <a href="https://github.com/aws/aws-xray-sdk-node/issues/294" target="_blank" rel="noopener">here</a></li><li>As I said, the v3 SDK is not part of the Node.js AWS Lambda runtime. If you want to use it, you have to add it to your ZIP.</li><li>The DynamoDB <a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html" target="_blank" rel="noopener">DocumentClient</a> is missing. Instead, you might want to use <a href="https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/dynamodb-example-dynamodb-utilities.html" target="_blank" rel="noopener">those utility functions</a>. I never used the DocumentClient because I prefer the explicit over the implicit.</li><li>Client Side Monitoring (CSM) is not supported, which makes <a href="https://cloudonaut.io/record-aws-api-calls-to-improve-iam-policies/">debugging IAM issues harder&#x2F;impossible</a>.</li></ul><h2 id="Additional-features"><a href="#Additional-features" class="headerlink" title="Additional features"></a>Additional features</h2><ul><li>If you want to write plugins for the AWS SDK (maybe because you add X-Ray support), you might be interested in the new <a href="https://aws.amazon.com/blogs/developer/middleware-stack-modular-aws-sdk-js/" target="_blank" rel="noopener">middleware</a>.</li><li>v3 is implemented in TypeScript. We also have types for the v2 SDK. Therefore, not a big change for us, the AWS customers.</li></ul><h2 id="AWS-SDK-for-JavaScript-v3-in-Action"><a href="#AWS-SDK-for-JavaScript-v3-in-Action" class="headerlink" title="AWS SDK for JavaScript v3 in Action"></a>AWS SDK for JavaScript v3 in Action</h2><p>Watch the following video to learn how we use the new SDK in our <a href="https://marbot.io/" target="_blank" rel="noopener">Chatbot for AWS Monitoring</a>.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=YqfD0kcId10">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/YqfD0kcId10" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Frontend developers should start using the v3 SDK today. Use command objects to tree shake away all of the unused code! For backend developers, I don’t see much benefit unless your code is paging a lot :) if your backend runs on Lambda, keep in mind that the v3 SDK is not part of the Lambda runtime! If you plan to migrate, check out the <a href="https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/migrating-to-v3.html" target="_blank" rel="noopener">official migration guide</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Debugging a VPC: Security Groups, Network ACLs, and Routing Tables</title>
      <link>https://cloudonaut.io/debugging-a-vpc-security-groups-network-acl-routing-table/</link>
      <description>
        <![CDATA[<p>Configuring a VPC (Virtual Private Cloud) and firewalls (Security Groups and Network ACLs) is tricky. What to do when you cannot find the]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <guid isPermaLink="true">https://cloudonaut.io/debugging-a-vpc-security-groups-network-acl-routing-table/</guid>
      <pubDate>Wed, 27 Jan 2021 14:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Configuring a VPC (Virtual Private Cloud) and firewalls (Security Groups and Network ACLs) is tricky. What to do when you cannot find the cause for a <code>connection refused</code> or <code>connection timed out</code> error when connecting to an EC2 instance, RDS database instance, or ALB load balancer?</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/01/debug@730w.webp 730w, /images/2021/01/debug@730w2x.webp 1460w, /images/2021/01/debug@610w.webp 610w, /images/2021/01/debug@610w2x.webp 1220w, /images/2021/01/debug@450w.webp 450w, /images/2021/01/debug@450w2x.webp 900w, /images/2021/01/debug@330w.webp 330w, /images/2021/01/debug@330w2x.webp 660w, /images/2021/01/debug@545w.webp 545w, /images/2021/01/debug@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/01/debug@730w.jpg 730w, /images/2021/01/debug@730w2x.jpg 1460w, /images/2021/01/debug@610w.jpg 610w, /images/2021/01/debug@610w2x.jpg 1220w, /images/2021/01/debug@450w.jpg 450w, /images/2021/01/debug@450w2x.jpg 900w, /images/2021/01/debug@330w.jpg 330w, /images/2021/01/debug@330w2x.jpg 660w, /images/2021/01/debug@545w.jpg 545w, /images/2021/01/debug@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/01/debug.jpg" alt="Debugging a VPC" title="Debugging a VPC"></picture></p><p>The following checklist helps to solve the connectivity issue.</p><ol><li>Check the security group’s inbound rules of the target.</li><li>Check the security group’s outbound rules of the source.</li><li>Inspect the inbound and outbound rules of the Network ACLs.</li><li>Verify that there is an entry in the routing table for the source and target.</li></ol><p>Sometimes, it is hard to find the problem - even with that checklist.</p><p>But there is hope. AWS provides two tools allowing you to debug connectivity issues with ease.</p><h2 id="VPC-Flow-Logs"><a href="#VPC-Flow-Logs" class="headerlink" title="VPC Flow Logs"></a>VPC Flow Logs</h2><p>By enabling flow logs for a VPC, you get access to information about the IP traffic going to and from network interfaces. Analyzing flow logs to debug connectivity issues works best with CloudWatch Insights.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">2</span> <span class="number">123456789010</span> eni-<span class="number">1235</span>b8ca123456789 <span class="number">172.31.16.139</span> <span class="number">172.31.16.21</span> <span class="number">20641</span> <span class="number">22</span> <span class="number">6</span> <span class="number">20</span> <span class="number">4249</span> <span class="number">1418530010</span> <span class="number">1418530070</span> ACCEPT OK</span><br><span class="line"><span class="attribute">2</span> <span class="number">123456789010</span> eni-<span class="number">1235</span>b8ca123456789 <span class="number">172.31.9.69</span> <span class="number">172.31.9.12</span> <span class="number">49761</span> <span class="number">3389</span> <span class="number">6</span> <span class="number">20</span> <span class="number">4249</span> <span class="number">1418530010</span> <span class="number">1418530070</span> REJECT OK</span><br></pre></td></tr></table></figure><h2 id="VPC-Reachability-Analyzer"><a href="#VPC-Reachability-Analyzer" class="headerlink" title="VPC Reachability Analyzer"></a>VPC Reachability Analyzer</h2><p>AWS announced the VPC Reachability Analyzer in December 2020. You specify the source and target of a connection, and the VPC Reachability Analyzer checks your network for any misconfigurations. The tool even provides hints on how to solve the problem.</p><h2 id="Learn-more"><a href="#Learn-more" class="headerlink" title="Learn more"></a>Learn more</h2><p>Watch the following video to learn more.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=6mVTGvBHrWM">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/6mVTGvBHrWM" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Cheap, Durable, Fast. How to choose an EBS volume type?</title>
      <link>https://cloudonaut.io/cheap-durable-fast-how-to-choose-an-ebs-volume-type/</link>
      <description>
        <![CDATA[<p>Elastic Block Storage (EBS) provides solid state drives (SSD) and hard disk drives (HDD) for EC2 instances. The virtual machine accesses]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/ebs/">ebs</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/cheap-durable-fast-how-to-choose-an-ebs-volume-type/</guid>
      <pubDate>Tue, 19 Jan 2021 20:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Elastic Block Storage (EBS) provides solid state drives (SSD) and hard disk drives (HDD) for EC2 instances. The virtual machine accesses the persistent storage via the network. In December 2020, AWS announced another volume type called <code>General Purpose SSD (gp3)</code>. So now there are three volume types based on SSDs. With this blog post, I compare <code>gp2</code>, <code>gp3</code>, and <code>io2</code> volumes and guide how to choose the volume type that fits best a specific scenario.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/01/storage@730w.webp 730w, /images/2021/01/storage@730w2x.webp 1460w, /images/2021/01/storage@610w.webp 610w, /images/2021/01/storage@610w2x.webp 1220w, /images/2021/01/storage@450w.webp 450w, /images/2021/01/storage@450w2x.webp 900w, /images/2021/01/storage@330w.webp 330w, /images/2021/01/storage@330w2x.webp 660w, /images/2021/01/storage@545w.webp 545w, /images/2021/01/storage@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/01/storage@730w.jpg 730w, /images/2021/01/storage@730w2x.jpg 1460w, /images/2021/01/storage@610w.jpg 610w, /images/2021/01/storage@610w2x.jpg 1220w, /images/2021/01/storage@450w.jpg 450w, /images/2021/01/storage@450w2x.jpg 900w, /images/2021/01/storage@330w.jpg 330w, /images/2021/01/storage@330w2x.jpg 660w, /images/2021/01/storage@545w.jpg 545w, /images/2021/01/storage@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/01/storage.jpg" alt="Cheap, Durable, Fast. How to choose an EBS volume type?" title="Cheap, Durable, Fast. How to choose an EBS volume type?"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/37-cheap-durable-fast-how-to-choose-an-ebs-volume-type/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="gp2-General-Purpose-SSD"><a href="#gp2-General-Purpose-SSD" class="headerlink" title="gp2 (General Purpose SSD)"></a>gp2 (General Purpose SSD)</h2><p>At first sight, the <code>gp2</code> volume type is easy to use. The volume size determines the price as well as the baseline throughput (IOPS and bandwidth).</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">Baseline</span> Throughput (IOPS) = MIN( Volume Size (GiB) * <span class="number">3</span>, <span class="number">3000</span> )</span><br></pre></td></tr></table></figure><p>Volumes smaller than 1,000 GiB can burst up to 3,000 IOPS for a short period per day. I’ve seen many infrastructures fail because the performance was fine during testing but degraded significantly an hour after the go-live in production.</p><h2 id="gp3-General-Purpose-SSD"><a href="#gp3-General-Purpose-SSD" class="headerlink" title="gp3 (General Purpose SSD)"></a>gp3 (General Purpose SSD)</h2><p>The latest generation of General Purpose SSD volumes is different. Every volume comes with a baseline performance of 3,000 IOPS and 125 MB&#x2F;s regardless of the size. So the baseline performance of a <code>gp3</code> volume is the same as the burst capacity of a <code>gp2</code> volume.</p><blockquote><p>Be careful; a <code>gp2</code> volume larger than 333 GiB provides a maximum bandwidth of 250 MiB&#x2F;s. The <code>gp3</code> volume comes with only 125 MB&#x2F;s by default.</p></blockquote><p>Surprisingly, it is possible to increase the maximum throughput of a <code>gp3</code> volume by provisioning additional IOPS and bandwidth. Provisioned throughput was the unique selling point of <code>io2</code> volumes, which I present next.</p><p>An <code>gp3</code> volume supports up to 16,000 IOPS and 1,000 MiB&#x2F;s. I want to highlight the maximum bandwidth of 1,000 MiB&#x2F;s, four times as much as gp2.</p><p>AWS charges by size, IOPS, and bandwidth. 3,000 IOPS and 125 MB&#x2F;s are included for free for every volume.</p><blockquote><p>While AWS says gp3 delivers up to 20% lower price-point over gp2, in reality the price advantage is somewhere between 7 and 20% depending on IOPS and throughput required in addition to baseline.</p></blockquote><h2 id="io2-Provisioned-IOPS-SSD"><a href="#io2-Provisioned-IOPS-SSD" class="headerlink" title="io2 (Provisioned IOPS SSD)"></a>io2 (Provisioned IOPS SSD)</h2><p>The <code>io2</code> volume type works similar to <code>gp3</code>. When creating an <code>io2</code> volume, you specify the size as well as the provisioned IOPS. Again, AWS charges by size and provisioned IOPS.</p><blockquote><p>By the way, AWS announced <code>io2</code> in August 2020. The Relational Database Service (RDS) does not support <code>io2</code> volumes yet. The service still runs on  <code>io1</code>.</p></blockquote><p>However, an <code>io2</code> volume is much more expensive than a <code>gp3</code> volume, as shown in the following example.</p><table class="table table-striped table-responsive"><thead><tr><th style="text-align:right">Volume Size</th><th style="text-align:right">IOPS</th><th style="text-align:right">Bandwith (MB/s)</th><th style="text-align:right">gp3 (USD/month)</th><th style="text-align:right">io2 (USD/month)</th></tr></thead><tbody><tr><td style="text-align:right">1000</td><td style="text-align:right">3000</td><td style="text-align:right">125</td><td style="text-align:right">80</td><td style="text-align:right">320</td></tr><tr><td style="text-align:right">2000</td><td style="text-align:right">6000</td><td style="text-align:right">250</td><td style="text-align:right">180</td><td style="text-align:right">640</td></tr><tr><td style="text-align:right">2000</td><td style="text-align:right">12000</td><td style="text-align:right">500</td><td style="text-align:right">220</td><td style="text-align:right">1030</td></tr></tbody></table><p>As a rule of thumb, an <code>io2</code> volume costs 3 to 4 times as much as a <code>gp3</code> volume.</p><p>So what is the big difference between <code>io2</code> and <code>gp3</code>?</p><ol><li><strong>Durability</strong>: the annual failure rate of an <code>io2</code> volume is 0.001%. That’s a huge difference compared to <code>gp3</code> with an annual failure rate of 0.2%. In other words, 1 of 500 <code>gp3</code> volumes fails every year. But, only 1 of 100,0000 <code>io2</code> volumes fail every year.</li><li><strong>SLA on throughput</strong>: an <code>io2</code> volume promises to deliver the provisioned performance 99.9 percent of the time. There is no such guarantee for <code>gp3</code> volumes.</li><li><strong>Maximum throughput</strong>: an <code>io2</code> volume supports up to 64,000 IOPS and 1,000 MiB&#x2F;s. That’s four times the maximum IOPS of a <code>gp3</code> volume. However, both volume types do not provide more than 1,000 MiB&#x2F;s bandwidth.</li></ol><blockquote><p>I decided not to discuss the previous generation <code>io1</code> in this blog post. In summary, the previous generation is more expensive, less durable, and comes with lower maximum bandwidth. Check out <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html" target="_blank" rel="noopener">Amazon EBS volume types</a> to learn more.</p></blockquote><h2 id="I-O-Benchmark"><a href="#I-O-Benchmark" class="headerlink" title="I&#x2F;O Benchmark"></a>I&#x2F;O Benchmark</h2><p>Some AWS customers are complaining about degraded performance when switching from <code>gp2</code> to <code>gp3</code>. For example, Silas has written down his experiences with <a href="https://silashansen.medium.com/looking-into-the-new-ebs-gp3-volumes-8eaaa8aff33e" target="_blank" rel="noopener">gp3 and an Elasticsearch cluster</a>. That’s why I decided to benchmark the three different volume types.</p><p>I did my test on Jan 11, 2021, with the following setup.</p><ul><li>Region: <code>eu-west-1</code></li><li>EC2 Instance:  <code>m5.8xlarge</code></li><li><code>gp2</code> Volume: 1000 GiB</li><li><code>gp3</code> Volume: 1000 GiB, 3000 IOPS, 125 MB&#x2F;s</li><li><code>io2</code> Vokume: 1000 GiB, 3000 IOPS</li><li>File System: <code>xfs</code></li><li>Duration: 120 min</li></ul><p>I’ve used <a href="https://fio.readthedocs.io/en/latest/fio_doc.html" target="_blank" rel="noopener">fio</a> to measure the I&#x2F;O performance with the following commands.</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">fio <span class="attribute">--directory</span>=/mnt/gp3 --name gp3 <span class="attribute">--direct</span>=1 <span class="attribute">--rw</span>=randrw <span class="attribute">--bs</span>=16k <span class="attribute">--size</span>=1G <span class="attribute">--numjobs</span>=16 --time_based <span class="attribute">--runtime</span>=7200 --group_reporting --norandommap</span><br><span class="line">fio <span class="attribute">--directory</span>=/mnt/gp2 --name gp2 <span class="attribute">--direct</span>=1 <span class="attribute">--rw</span>=randrw <span class="attribute">--bs</span>=16k <span class="attribute">--size</span>=1G <span class="attribute">--numjobs</span>=16 --time_based <span class="attribute">--runtime</span>=7200 --group_reporting --norandommap</span><br><span class="line">fio <span class="attribute">--directory</span>=/mnt/io2 --name io2 <span class="attribute">--direct</span>=1 <span class="attribute">--rw</span>=randrw <span class="attribute">--bs</span>=16k <span class="attribute">--size</span>=1G <span class="attribute">--numjobs</span>=16 --time_based <span class="attribute">--runtime</span>=7200 --group_reporting --norandommap</span><br></pre></td></tr></table></figure><p>The following table shows the results.</p><table class="table table-striped table-responsive"><thead><tr><th>Read</th><th style="text-align:right">io2</th><th style="text-align:right">gp3</th><th style="text-align:right">gp2</th></tr></thead><tbody><tr><td>bw (KB/s)</td><td style="text-align:right">23980</td><td style="text-align:right">23991</td><td style="text-align:right">23991</td></tr><tr><td>iops</td><td style="text-align:right">1498</td><td style="text-align:right">1499</td><td style="text-align:right">1499</td></tr><tr><td>clat avg (usec)</td><td style="text-align:right">5293</td><td style="text-align:right">5302</td><td style="text-align:right">5473</td></tr><tr><td>clat stdev (usec)</td><td style="text-align:right">261</td><td style="text-align:right">524</td><td style="text-align:right">2702</td></tr><tr><td>clat 90.00p (usec)</td><td style="text-align:right">5536</td><td style="text-align:right">5664</td><td style="text-align:right">5984</td></tr><tr><td>clat 95.00p (usec)</td><td style="text-align:right">5664</td><td style="text-align:right">5792</td><td style="text-align:right">7264</td></tr><tr><td>clat 99.00p (usec)</td><td style="text-align:right">5856</td><td style="text-align:right">5984</td><td style="text-align:right">16768</td></tr><tr><td>clat 99.90p (usec)</td><td style="text-align:right">6688</td><td style="text-align:right">6880</td><td style="text-align:right">40192</td></tr><tr><td>clat 99.99p (usec)</td><td style="text-align:right">8768</td><td style="text-align:right">9664</td><td style="text-align:right">69120</td></tr></tbody></table><table class="table table-striped table-responsive"><thead><tr><th>Write</th><th style="text-align:right">io2</th><th style="text-align:right">gp3</th><th style="text-align:right">gp2</th></tr></thead><tbody><tr><td>bw (KB/s)</td><td style="text-align:right">23989</td><td style="text-align:right">24000</td><td style="text-align:right">24000</td></tr><tr><td>iops</td><td style="text-align:right">1499</td><td style="text-align:right">1499</td><td style="text-align:right">1499</td></tr><tr><td>clat avg (usec)</td><td style="text-align:right">5379</td><td style="text-align:right">5365</td><td style="text-align:right">5194</td></tr><tr><td>clat stdev (usec)</td><td style="text-align:right">229</td><td style="text-align:right">554</td><td style="text-align:right">1041</td></tr><tr><td>clat 90.00p (usec)</td><td style="text-align:right">5600</td><td style="text-align:right">5728</td><td style="text-align:right">5600</td></tr><tr><td>clat 95.00p (usec)</td><td style="text-align:right">5728</td><td style="text-align:right">5856</td><td style="text-align:right">5728</td></tr><tr><td>clat 99.00p (usec)</td><td style="text-align:right">5920</td><td style="text-align:right">6496</td><td style="text-align:right">8384</td></tr><tr><td>clat 99.90p (usec)</td><td style="text-align:right">7520</td><td style="text-align:right">11456</td><td style="text-align:right">16512</td></tr><tr><td>clat 99.99p (usec)</td><td style="text-align:right">9280</td><td style="text-align:right">17536</td><td style="text-align:right">23936</td></tr></tbody></table><p>As expected, all three volume types delivered 3,000 IOPS (1,500 read IOPS and 1,500 write IOPS) over 2 hours.</p><blockquote><p>Please note, I’ve been using a block size of 16 KB for my I&#x2F;O benchmark. That’s whe the volumes are not reaching their maximum bandwith. </p></blockquote><p>However, there are some differences in the completion latency (<code>clat</code>) - the time from submission to completion of the I&#x2F;O pieces. The latency is much more stable (see <code>stdev</code> and percentiles) for <code>io2</code> volumes. A <code>gp3</code> volume is somewhere in the middle between an <code>io2</code> volume and a <code>gp2</code> volume from a latency predictability point of view.</p><blockquote><p>In summary, I could not find any hints on why switching from <code>gp2</code> to <code>gp3</code> should slow down your workloads.</p></blockquote><h2 id="Guidance"><a href="#Guidance" class="headerlink" title="Guidance"></a>Guidance</h2><ul><li>The volume type <code>gp2</code> is outdated. A <code>gp3</code> volume is more cost-effective and predictable as it does not come with burstable performance.</li><li>The volume type <code>io1</code> is outdated. Choose <code>io2</code> whenever available in your region.</li><li>The volume type <code>io2</code> is expensive but much more durable. On top of that, an <code>io2</code> volume provides a SLA on the provisioned throughput. Therefore, I recommend <code>io2</code> for production-critical database workloads.</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to Become an AWS Certified Solutions Architect</title>
      <link>https://cloudonaut.io/how-to-become-an-aws-certified-solutions-architect/</link>
      <description>
        <![CDATA[<p>In 2012, I created my first AWS account. Back then, I worked as a software engineer and was looking for a way to deploy an online trading]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/career/">career</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-become-an-aws-certified-solutions-architect/</guid>
      <pubDate>Thu, 07 Jan 2021 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>In 2012, I created my first AWS account. Back then, I worked as a software engineer and was looking for a way to deploy an online trading platform. Two years later, I attended re:Invent — the yearly conference organized by AWS — in Las Vegas for the first time. I was lucky to pass the Certified Solutions Architect certification without any preparation due to my hands-on experience.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/01/learning@730w.webp 730w, /images/2021/01/learning@730w2x.webp 1460w, /images/2021/01/learning@610w.webp 610w, /images/2021/01/learning@610w2x.webp 1220w, /images/2021/01/learning@450w.webp 450w, /images/2021/01/learning@450w2x.webp 900w, /images/2021/01/learning@330w.webp 330w, /images/2021/01/learning@330w2x.webp 660w, /images/2021/01/learning@545w.webp 545w, /images/2021/01/learning@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/01/learning@730w.jpg 730w, /images/2021/01/learning@730w2x.jpg 1460w, /images/2021/01/learning@610w.jpg 610w, /images/2021/01/learning@610w2x.jpg 1220w, /images/2021/01/learning@450w.jpg 450w, /images/2021/01/learning@450w2x.jpg 900w, /images/2021/01/learning@330w.jpg 330w, /images/2021/01/learning@330w2x.jpg 660w, /images/2021/01/learning@545w.jpg 545w, /images/2021/01/learning@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/01/learning.jpg" alt="How to Become an AWS Certified Solutions Architect" title="How to Become an AWS Certified Solutions Architect"></picture></p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><p>Are you aiming to pass the <a href="https://aws.amazon.com/certification/certified-solutions-architect-associate/" target="_blank" rel="noopener">Solutions Architect Associate (SAA-C02)</a> exam? In this blog, you’ll find my advice on how to prepare for the certification exam. My approach is not the fastest way to get certified but a sustainable and joyful way to learn about AWS that will pay off in the long term.</p><h2 id="The-learning-map"><a href="#The-learning-map" class="headerlink" title="The learning map"></a>The learning map</h2><p>AWS is a vast country with its own language. Therefore, I’ve created a learning map to navigate AWS’s most important parts to pass the exam. Think of your exam preparations as a journey of discovery through this country.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2021/01/aws-certified-learning-map@730w.webp 730w, /images/2021/01/aws-certified-learning-map@730w2x.webp 1460w, /images/2021/01/aws-certified-learning-map@610w.webp 610w, /images/2021/01/aws-certified-learning-map@610w2x.webp 1220w, /images/2021/01/aws-certified-learning-map@450w.webp 450w, /images/2021/01/aws-certified-learning-map@450w2x.webp 900w, /images/2021/01/aws-certified-learning-map@330w.webp 330w, /images/2021/01/aws-certified-learning-map@330w2x.webp 660w, /images/2021/01/aws-certified-learning-map@545w.webp 545w, /images/2021/01/aws-certified-learning-map@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2021/01/aws-certified-learning-map@730w.png 730w, /images/2021/01/aws-certified-learning-map@730w2x.png 1460w, /images/2021/01/aws-certified-learning-map@610w.png 610w, /images/2021/01/aws-certified-learning-map@610w2x.png 1220w, /images/2021/01/aws-certified-learning-map@450w.png 450w, /images/2021/01/aws-certified-learning-map@450w2x.png 900w, /images/2021/01/aws-certified-learning-map@330w.png 330w, /images/2021/01/aws-certified-learning-map@330w2x.png 660w, /images/2021/01/aws-certified-learning-map@545w.png 545w, /images/2021/01/aws-certified-learning-map@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2021/01/aws-certified-learning-map.png" alt="AWS Certified Learning Map" title="AWS Certified Learning Map"></picture></p><p>Use the following checklist to track your learning progress.</p><ul><li>EC2</li><li>VPC</li><li>RDS</li><li>ElastiCache</li><li>EBS</li><li>EFS</li><li>IAM</li><li>S3</li><li>CloudWatch</li><li>Auto Scaling</li><li>Route 53</li><li>CloudFront</li><li>API Gateway</li><li>Lambda</li><li>DynamoDB</li><li>Redshift</li><li>KMS</li><li>CloudFormation</li></ul><p>The official <a href="https://d1.awsstatic.com/training-and-certification/docs-sa-assoc/AWS-Certified-Solutions-Architect-Associate_Exam-Guide.pdf" target="_blank" rel="noopener">exam guide</a> describes the requirements to get certified in more detail.</p><h2 id="What’s-your-learning-style"><a href="#What’s-your-learning-style" class="headerlink" title="What’s your learning style?"></a>What’s your learning style?</h2><p>I prefer learning about new topics by reading a book. I do recommend the following two books to get started with AWS.</p><ul><li><a href="https://www.manning.com/books/amazon-web-services-in-action" target="_blank" rel="noopener">Amazon Web Services in Action</a></li><li><a href="https://www.manning.com/books/learn-amazon-web-services-in-a-month-of-lunches" target="_blank" rel="noopener">Learn Amazon Web Services in a Month of Lunches</a></li></ul><p>On top of that, I suggest reading through the <a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/welcome.html" target="_blank" rel="noopener">AWS Well-Architected Framework</a>. The framework summarizes best practices on how to build secure, reliable and efficient systems on AWS. You will find a lot of questions about those principles within the exam.</p><p>Do you prefer watching videos instead of reading? The place to go is A Cloud Guru with their <a href="https://acloud.guru/overview/aws-certified-solutions-architect-associate" target="_blank" rel="noopener">AWS Certified Solutions Architect Associate SAA-C02 course</a> consisting of more than 34 hours of videos. Besides that, Adrian Cantrill — an independent instructor — puts a lot of effort into his video courses. Of course, Adrian offers an <a href="https://learn.cantrill.io/p/aws-certified-solutions-architect-associate-saa-c02" target="_blank" rel="noopener">AWS Certified Solutions Architect – Associate (SAA-C02) course</a> as well.</p><p>Participating in instructor-led classroom training is another way of getting started with AWS. An onsite training breaks the daily routine and provides a place to concentrate. AWS and its partners offer training all around the world. Until last year, I was an instructor for official AWS training in Germany. Based on that experience, I do recommend the <a href="https://www.aws.training/SessionSearch?pageNumber=1&courseId=10002" target="_blank" rel="noopener">3-day Architecting on AWS</a> training.</p><h2 id="Learning-by-doing"><a href="#Learning-by-doing" class="headerlink" title="Learning by doing"></a>Learning by doing</h2><p>Reading books, watching videos and participating in classroom training is an excellent way to get familiar with AWS. However, I observe that learning by doing is even more critical. By the way, AWS recommends one year of hands-on experience (see <a href="https://d1.awsstatic.com/training-and-certification/docs-sa-assoc/AWS-Certified-Solutions-Architect-Associate_Exam-Guide.pdf" target="_blank" rel="noopener">exam guide</a>).</p><p>In my opinion, a side project is a perfect way to learn about AWS. Get inspired by the following examples.</p><ul><li>Deploy a personal blog based on WordPress. Start with a single EC2 instance and expand to a highly available infrastructure with Route 53, ALB, EC2, Auto Scaling, EFS, and RDS.</li><li>Back up data from your local machine or network to AWS. Start by using a basic S3 bucket and proceed with lifecycle policies to decrease costs, encryption-at-rest with a customer-managed KMS key, and scheduled backups with the help of AWS DataSync.</li><li>Install your a VPN server — for example, OpenVPN — on an EC2 instance to tunnel your traffic through unsecured networks or to another country. Expand by accessing additional resources like an EFS file system over the VPN connection.</li><li>When working on a side project to deepen your knowledge about AWS, you will most likely struggle from time to time. Therefore, we recommend joining a community of AWS users.</li></ul><h2 id="Join-a-community"><a href="#Join-a-community" class="headerlink" title="Join a community"></a>Join a community</h2><p>There are <a href="https://aws.amazon.com/developer/community/usergroups/" target="_blank" rel="noopener">AWS user groups</a> all around the world. For example, I’m the co-organizer of the AWS user group in Stuttgart. And of course, there are online communities as well. For example, <a href="https://www.100daysofcloud.com/" target="_blank" rel="noopener">#100DaysOfCloud</a> is a community of AWS&#x2F;GCP&#x2F;Azure beginners. Get involved, for example, with one of the following activities.</p><ul><li>Ask for help when you are struggling and need some guidance to get back on track.</li><li>Watch talks where experts share their knowledge and experience.</li><li>Share your side project to get motivating feedback.</li><li>Help others as early as possible. Explaining a topic to a beginner is very helpful to deepen your understanding.</li></ul><p>I highly recommend joining a community of AWS users!</p><h2 id="Broaden-your-knowledge"><a href="#Broaden-your-knowledge" class="headerlink" title="Broaden your knowledge"></a>Broaden your knowledge</h2><p>After gaining some hands-on AWS experience, I encourage you to broaden your knowledge about AWS in two dimensions:</p><ul><li>Vertical: Pick 2-3 services in which you want to become an expert. Take a close look at every detail. I highly recommend testing the limits. For example, what’s the maximum network bandwidth of an EC2 instance? Or, how fast does an ALB scale?</li><li>Horizontal: Add 2-3 services to your learning agenda. Pick services you are most interested in — for example, machine learning, analytics, or IoT.<br>Doing so helps you to see the bigger picture and to connect the dots.</li></ul><h2 id="Take-a-practice-exam"><a href="#Take-a-practice-exam" class="headerlink" title="Take a practice exam"></a>Take a practice exam</h2><p>So you learned a lot about AWS and gained some hands-on experience? Time for the last steps before the exam.</p><p>First, I recommend the <a href="https://www.aws.training/Details/Curriculum?id=20685" target="_blank" rel="noopener">exam readiness course from AWS</a>. The free course summarizes important information and contains example questions. Next, I recommend taking the <a href="https://www.aws.training/certification?src=exam-prep" target="_blank" rel="noopener">practice exam</a> to build trust in your knowledge and sleep well before the final exam.</p><h2 id="Go-go-go"><a href="#Go-go-go" class="headerlink" title="Go, go, go!"></a>Go, go, go!</h2><p>Did you pass the practice exam? Schedule a certification exam right away. Good luck, and don’t forget to share your success in the comments.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>2020 in Review</title>
      <link>https://cloudonaut.io/2020-in-review/</link>
      <description>
        <![CDATA[<p>The year is coming to an end. We want to say thank you to our clients, partners, supporters, and readers. We are fortunate that our busin]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/retrospective/">retrospective</category>
      <guid isPermaLink="true">https://cloudonaut.io/2020-in-review/</guid>
      <pubDate>Tue, 29 Dec 2020 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The year is coming to an end. We want to say thank you to our clients, partners, supporters, and readers. We are fortunate that our business is growing steadily even during the pandemic. In this blog post, we look back on the past year and dare a look at the next year.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/12/happy-new-year@730w.webp 730w, /images/2020/12/happy-new-year@730w2x.webp 1460w, /images/2020/12/happy-new-year@610w.webp 610w, /images/2020/12/happy-new-year@610w2x.webp 1220w, /images/2020/12/happy-new-year@450w.webp 450w, /images/2020/12/happy-new-year@450w2x.webp 900w, /images/2020/12/happy-new-year@330w.webp 330w, /images/2020/12/happy-new-year@330w2x.webp 660w, /images/2020/12/happy-new-year@545w.webp 545w, /images/2020/12/happy-new-year@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/12/happy-new-year@730w.jpg 730w, /images/2020/12/happy-new-year@730w2x.jpg 1460w, /images/2020/12/happy-new-year@610w.jpg 610w, /images/2020/12/happy-new-year@610w2x.jpg 1220w, /images/2020/12/happy-new-year@450w.jpg 450w, /images/2020/12/happy-new-year@450w2x.jpg 900w, /images/2020/12/happy-new-year@330w.jpg 330w, /images/2020/12/happy-new-year@330w2x.jpg 660w, /images/2020/12/happy-new-year@545w.jpg 545w, /images/2020/12/happy-new-year@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/12/happy-new-year.jpg" alt="Happy New Year" title="Happy New Year"></picture></p><h2 id="Content"><a href="#Content" class="headerlink" title="Content"></a>Content</h2><p>We love blogging and published 61 articles on <a href="https://cloudonaut.io/">cloudonaut.io</a> this year. Those articles have been read more than 1,200,000 times in 2020.<br>We also dropped 26 <a href="https://podcast.cloudonaut.io/" target="_blank" rel="noopener">podcast episodes</a> with ~30,000 downloads &amp; streams.<br>Besides that, we launched cloudonaut plus in November, where we publish weekly video content for AWS professionals. Our archive already contains 17 videos.</p><p>The following articles attracted the most attention in 2020:</p><ul><li><a href="/loosing-trust-in-aws-sns-broken-for-24-days/">I’m losing trust in AWS. SNS is broken for 65 days.</a></li><li><a href="/ecs-vs-fargate-whats-the-difference/">ECS vs. Fargate: What’s the difference?</a></li><li><a href="/cloudformation-vs-terraform/">CloudFormation vs. Terraform</a></li><li><a href="/aws-sso-instead-of-iam-users/">Have you replaced IAM Users with AWS SSO yet?</a></li><li><a href="/eks-vs-ecs-orchestrating-containers-on-aws/">EKS vs. ECS: orchestrating containers on AWS</a></li><li><a href="/review-api-gateway-http-apis/">Review: API Gateway HTTP APIs - Cheaper and Faster REST APIs?</a></li><li><a href="/advanved-aws-networking-pitfalls-that-you-should-avoid/">Advanced AWS Networking: Pitfalls That You Should Avoid</a></li><li><a href="/6-unknown-cloudformation-features-you-should-know-about/">6 unknown CloudFormation features you should know about</a></li></ul><h2 id="Open-Source"><a href="#Open-Source" class="headerlink" title="Open Source"></a>Open Source</h2><p>Andreas and Michael made more than 3,000 contributions in the last year (according to GitHub). That’s ~13 contributions per working day! Our most popular repositories are:</p><ul><li><a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">aws-cf-templates</a> with 2,100 stars</li><li><a href="https://github.com/cfn-modules" target="_blank" rel="noopener">cfn-modules</a></li><li><a href="https://github.com/widdix/complete-aws-iam-reference" target="_blank" rel="noopener">complete-aws-iam-reference</a></li><li><a href="https://github.com/widdix/learn-cloudformation" target="_blank" rel="noopener">learn-cloudformation</a></li></ul><p>Our open source projects do not contribute directly to revenue.</p><h2 id="Consulting-business"><a href="#Consulting-business" class="headerlink" title="Consulting business"></a>Consulting business</h2><p>We served 15 different consulting clients in 2020, including startups, SMEs, and large enterprises. All projects have one thing in common: We support teams during their transition to AWS.<br>We made ~85% of our revenue in 2020 from our consulting business.</p><h2 id="Product-SaaS-business"><a href="#Product-SaaS-business" class="headerlink" title="Product &amp; SaaS business"></a>Product &amp; SaaS business</h2><p>We made significant investments in our product &amp; SaaS businesses this year.</p><ol><li>We integrated our <a href="https://marbot.io/" target="_blank" rel="noopener">chatbot for AWS Monitoring</a> with Microsoft Teams in  August (besides our Slack integration). We see more than twice the number of new teams joining marbot every week since then—a huge success.</li><li>Our product <a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV - Antivirus for Amazon S3</a> is used by hundreds of clients worldwide to protect their S3 buckets from malware, trjoans, viruses and other threats. We continue to receive tremendous feedback from our customers, which we use to enhance the product further.</li><li>We launched the Rapid Docker on AWS Video Course in January and are very happy with the results. Many customers enjoy both the ebook and the video course.</li></ol><p>In total, 15% of our revenue was generated by our product business in 2020.</p><h2 id="Outlook"><a href="#Outlook" class="headerlink" title="Outlook"></a>Outlook</h2><p>We will focus on our subscription offering cloudonaut plus in 2021. We will allocate 45% of our time to produce high-quality content. This includes videos, podcasts, and articles. This will be the most significant financial bet we ever made since we founded our company more than 5 years ago. </p><p>We aim to increase the revenue from our product business compared to our consulting business. Our goal is to make 35% of our revenue from our product business in 2021.</p><p>Thanks a lot for your support! We wish you a Happy New Year.</p><p>Andreas &amp; Michael</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>What's the CO² footprint of your architecture?</title>
      <link>https://cloudonaut.io/whats-the-co2-footprint-of-your-architecture/</link>
      <description>
        <![CDATA[<p>Fighting climate change is one of the biggest challenges of our days. When designing an architecture, there are many important factors to]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/whats-the-co2-footprint-of-your-architecture/</guid>
      <pubDate>Wed, 23 Dec 2020 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Fighting climate change is one of the biggest challenges of our days. When designing an architecture, there are many important factors to consider: security, reliability, performance and costs. I’d like to add another factor to that list: the CO² footprint.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/12/co2@730w.webp 730w, /images/2020/12/co2@730w2x.webp 1460w, /images/2020/12/co2@610w.webp 610w, /images/2020/12/co2@610w2x.webp 1220w, /images/2020/12/co2@450w.webp 450w, /images/2020/12/co2@450w2x.webp 900w, /images/2020/12/co2@330w.webp 330w, /images/2020/12/co2@330w2x.webp 660w, /images/2020/12/co2@545w.webp 545w, /images/2020/12/co2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/12/co2@730w.jpg 730w, /images/2020/12/co2@730w2x.jpg 1460w, /images/2020/12/co2@610w.jpg 610w, /images/2020/12/co2@610w2x.jpg 1220w, /images/2020/12/co2@450w.jpg 450w, /images/2020/12/co2@450w2x.jpg 900w, /images/2020/12/co2@330w.jpg 330w, /images/2020/12/co2@330w2x.jpg 660w, /images/2020/12/co2@545w.jpg 545w, /images/2020/12/co2@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/12/co2.jpg" alt="What's the CO² footprint of your architecture?" title="What's the CO² footprint of your architecture?"></picture></p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><p>Besides that fact that <a href="https://aws.amazon.com/about-aws/sustainability/" target="_blank" rel="noopener">AWS aims to achieve 100% renewable energy</a> for its data centers, there is also a lot to do for AWS customers to reduce energy consumption at all. Read on to learn about how you can take part and minimize the CO² footprint of your AWS architecture.</p><p>Luckily, in the world of cloud computing, reducing the CO² footprint will reduce costs in most scenarios. Therefore, it should be easy for you to develop a business case for a greener architecture.</p><h2 id="Shut-down-unused-resources"><a href="#Shut-down-unused-resources" class="headerlink" title="Shut down unused resources"></a>Shut down unused resources</h2><p>Shutting down unused resources has a significant impact on the CO² footprint as well as on costs. When building a greenfield application or migrating a legacy application, you should always think about whether there are times where you could shut down the whole application. For example, when an internal application is only used during business hours from within a single timezone.</p><p>There are two simple approaches to shut down unused resources automatically. When using an Auto Scaling Group to orchestrate EC2 instances, it is simple to define <a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/schedule_time.html" target="_blank" rel="noopener">scheduled actions</a> to increase and decrease running instances. For example, you could set the desired capacity of the Auto Scaling Group to zero at 05:00 pm and back to three at 09:00 am each working day.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/12/shut-down-unused-resources@730w.webp 730w, /images/2020/12/shut-down-unused-resources@730w2x.webp 1460w, /images/2020/12/shut-down-unused-resources@610w.webp 610w, /images/2020/12/shut-down-unused-resources@610w2x.webp 1220w, /images/2020/12/shut-down-unused-resources@450w.webp 450w, /images/2020/12/shut-down-unused-resources@450w2x.webp 900w, /images/2020/12/shut-down-unused-resources@330w.webp 330w, /images/2020/12/shut-down-unused-resources@330w2x.webp 660w, /images/2020/12/shut-down-unused-resources@545w.webp 545w, /images/2020/12/shut-down-unused-resources@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/12/shut-down-unused-resources@730w.png 730w, /images/2020/12/shut-down-unused-resources@730w2x.png 1460w, /images/2020/12/shut-down-unused-resources@610w.png 610w, /images/2020/12/shut-down-unused-resources@610w2x.png 1220w, /images/2020/12/shut-down-unused-resources@450w.png 450w, /images/2020/12/shut-down-unused-resources@450w2x.png 900w, /images/2020/12/shut-down-unused-resources@330w.png 330w, /images/2020/12/shut-down-unused-resources@330w2x.png 660w, /images/2020/12/shut-down-unused-resources@545w.png 545w, /images/2020/12/shut-down-unused-resources@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/12/shut-down-unused-resources.png" alt="Shut down unused resources" title="Shut down unused resources"></picture></p><p>The <a href="https://aws.amazon.com/solutions/implementations/instance-scheduler/" target="_blank" rel="noopener">AWS Instance Scheduler</a> - a solution based on Lambda and DynamoDB maintained by AWS - is a good alternative for EC2 instances not managed by an Auto Scaling Group. The tool covers RDS database instances as well.</p><h2 id="Right-size-your-resources"><a href="#Right-size-your-resources" class="headerlink" title="Right size your resources"></a>Right size your resources</h2><p>As a consultant, I often review cloud architectures. About 80% of the infrastructures I see are wasting resources by oversizing their cloud components. There are two reasons for that. One, we are not used to the fact that increasing or decreasing capacity is something we can do with the click of a button, and two - no one wants to take the risk of performance degradation because of picking a smaller size.</p><p>However, to reduce the CO² footprint of your cloud infrastructure, you should be careful when provisioning your cloud resources. Here are a few tips:</p><ul><li>Start with small instance types for EC2 and RDS when setting up the infrastructure for a greenfield or lift &amp; shift project. It is no big deal to increase the instance size later. But downsizing an instance later is a hard decision because it could potentially cause service degradation.</li><li>Do not extensively overprovision storage capacity when using EBS. It is possible to <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-modify-volume.html" target="_blank" rel="noopener">increase the size of an EBS volume</a> on the fly later. But it is not possible to downsize the storage capacity of an EBS volume.</li><li>Same for provisioning I&#x2F;O throughput of EBS, EFS, RDS, DynamoDB, and similar services. Start with the smallest possible configuration and increase I&#x2F;O throughput later.</li></ul><p>By the way, right-sizing your infrastructure does not only reduce the CO² footprint but reduces costs dramatically as well.</p><h2 id="Reserved-Capacity-vs-Flexible-Resources"><a href="#Reserved-Capacity-vs-Flexible-Resources" class="headerlink" title="Reserved Capacity vs. Flexible Resources"></a>Reserved Capacity vs. Flexible Resources</h2><p>When choosing the building blocks for your architecture, you should consider whether a service requires reserved capacity or offers flexible resources. By using services without the need to provision a specific capacity, you avoid idle times.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/12/reserved-capacity-vs-flexible-resources@730w.webp 730w, /images/2020/12/reserved-capacity-vs-flexible-resources@730w2x.webp 1460w, /images/2020/12/reserved-capacity-vs-flexible-resources@610w.webp 610w, /images/2020/12/reserved-capacity-vs-flexible-resources@610w2x.webp 1220w, /images/2020/12/reserved-capacity-vs-flexible-resources@450w.webp 450w, /images/2020/12/reserved-capacity-vs-flexible-resources@450w2x.webp 900w, /images/2020/12/reserved-capacity-vs-flexible-resources@330w.webp 330w, /images/2020/12/reserved-capacity-vs-flexible-resources@330w2x.webp 660w, /images/2020/12/reserved-capacity-vs-flexible-resources@545w.webp 545w, /images/2020/12/reserved-capacity-vs-flexible-resources@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/12/reserved-capacity-vs-flexible-resources@730w.png 730w, /images/2020/12/reserved-capacity-vs-flexible-resources@730w2x.png 1460w, /images/2020/12/reserved-capacity-vs-flexible-resources@610w.png 610w, /images/2020/12/reserved-capacity-vs-flexible-resources@610w2x.png 1220w, /images/2020/12/reserved-capacity-vs-flexible-resources@450w.png 450w, /images/2020/12/reserved-capacity-vs-flexible-resources@450w2x.png 900w, /images/2020/12/reserved-capacity-vs-flexible-resources@330w.png 330w, /images/2020/12/reserved-capacity-vs-flexible-resources@330w2x.png 660w, /images/2020/12/reserved-capacity-vs-flexible-resources@545w.png 545w, /images/2020/12/reserved-capacity-vs-flexible-resources@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/12/reserved-capacity-vs-flexible-resources.png" alt="Reserved Capacity vs. Flexible Resources" title="Reserved Capacity vs. Flexible Resources"></picture></p><p>A comparison of different options:</p><table class="table table-striped table-responsive"><thead><tr><th>Reserved Capacity</th><th>Flexible Resources</th></tr></thead><tbody><tr><td><strong>EBS</strong> requires you to provision the storage capacity upfront; the same is true for the I/O throughput.</td><td><strong>S3</strong> provides unlimited storage and bills per read/write request.</td></tr><tr><td><strong>EC2</strong> launches an instance with a predefined amount of CPU, memory, and network resources. As long as the instance runs, the resources are occupied, no matter if the virtual machine is idling or under high load.</td><td><strong>Lambda</strong> spins up execution environments on-demand. When no requests are coming in, Lambda will not occupy any compute resources.</td></tr><tr><td><strong>RDS</strong> launches virtual machines under the hood. Those virtual machines are running 24/7 to provide access to the data stored in the relational database. When dealing with spiky workloads, the database needs to be provisioned for the maximum throughput required. Doing so wastes a lot of resources during times with low demand.</td><td><strong>DynamoDB</strong> provides unlimited storage that grows on demand. On top of that, you can use DynamoDB On-Demand, which means you will pay per request. That also means that you will reduce idle resources to a minimum.</td></tr><tr><td><strong>ECS/EKS with EC2</strong> requires a fleet of EC2 instances to run your container workload. Typically, that results in wasted resources due to fragmentation.</td><td><strong>ECS/EKS with Fargate</strong> launches containers on an infrastructure managed by AWS. Doing so allows AWS to reduce waste by optimizing the underlying infrastructure.</td></tr></tbody></table><h2 id="AWS-regions-with-renewable-energy"><a href="#AWS-regions-with-renewable-energy" class="headerlink" title="AWS regions with renewable energy"></a>AWS regions with renewable energy</h2><p>One of the first steps when thinking about an AWS architecture is to choose the AWS region. At this point, AWS provides more than 20 regions worldwide thoughout US East (N. Virginia), Asia Pacific (Tokyo) and Europe (Ireland).</p><p>You should take into consideration the following criteria when deciding which regions fit best for your scenario:</p><ul><li>The network latency, a factor to consider when choosing the region that is closest to the majority of users.</li><li>Legal requirements like data protection laws.</li><li>How cost effective each region is as the pricing for most services differs between regions.</li><li>Not all AWS services are available in all regions and new services are typically announced in a certain order for the different regions.</li><li>Some regions are known for high stability and availability of resources (e.g., virtual machines with GPU cores).</li></ul><p>To reduce the CO² footprint of your architecture, you should consider another criterion: “AWS purchases and retires environmental attributes, like Renewable Energy Credits and Guarantees of Origin, to cover the non-renewable energy we use in these regions.” (Source <a href="https://aws.amazon.com/about-aws/sustainability/" target="_blank" rel="noopener">AWS &amp; Sustainability</a>)</p><ul><li>US West (Oregon)</li><li>Europe (Frankfurt)</li><li>Europe (Ireland)</li><li>GovCloud (US-West)</li><li>Canada (Central)</li></ul><p>By spinning up your workload in one of these regions, you are dramatically reducing the CO² footprint of your architecture.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/12/whats-the-co2-footprint-of-your-architecture@730w.webp 730w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@730w2x.webp 1460w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@610w.webp 610w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@610w2x.webp 1220w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@450w.webp 450w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@450w2x.webp 900w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@330w.webp 330w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@330w2x.webp 660w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@545w.webp 545w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/12/whats-the-co2-footprint-of-your-architecture@730w.png 730w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@730w2x.png 1460w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@610w.png 610w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@610w2x.png 1220w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@450w.png 450w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@450w2x.png 900w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@330w.png 330w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@330w2x.png 660w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@545w.png 545w, /images/2020/12/whats-the-co2-footprint-of-your-architecture@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/12/whats-the-co2-footprint-of-your-architecture.png" alt="What's the CO² footprint of your architecture?" title="What's the CO² footprint of your architecture?"></picture></p><p>Keeping the CO² footprint to a minimum is your duty when designing an AWS architecture. Wasting cloud resources increases energy consumption as well as hardware usage. The good news is that you can kill two birds with one stone: reducing your CO² footprint does also reduce costs.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Unboxing AWS DataSync</title>
      <link>https://cloudonaut.io/unboxing-aws-datasync/</link>
      <description>
        <![CDATA[<p>Your toolbox should contain AWS DataSync, a service to synchronize data between all kinds of locations. Copy data between S3, EFS, and FS]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/efs/">efs</category>
      <category domain="https://cloudonaut.io/tag/datasync/">datasync</category>
      <guid isPermaLink="true">https://cloudonaut.io/unboxing-aws-datasync/</guid>
      <pubDate>Wed, 23 Dec 2020 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Your toolbox should contain AWS DataSync, a service to synchronize data between all kinds of locations. Copy data between S3, EFS, and FSx. On top of that, DataSync works with on-premises locations like NFS, SMB, and more. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/12/move@730w.webp 730w, /images/2020/12/move@730w2x.webp 1460w, /images/2020/12/move@610w.webp 610w, /images/2020/12/move@610w2x.webp 1220w, /images/2020/12/move@450w.webp 450w, /images/2020/12/move@450w2x.webp 900w, /images/2020/12/move@330w.webp 330w, /images/2020/12/move@330w2x.webp 660w, /images/2020/12/move@545w.webp 545w, /images/2020/12/move@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/12/move@730w.jpg 730w, /images/2020/12/move@730w2x.jpg 1460w, /images/2020/12/move@610w.jpg 610w, /images/2020/12/move@610w2x.jpg 1220w, /images/2020/12/move@450w.jpg 450w, /images/2020/12/move@450w2x.jpg 900w, /images/2020/12/move@330w.jpg 330w, /images/2020/12/move@330w2x.jpg 660w, /images/2020/12/move@545w.jpg 545w, /images/2020/12/move@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/12/move.jpg" alt="Unboxing AWS DataSync" title="Unboxing AWS DataSync"></picture></p><p>In this week’s video Andreas unboxes AWS DataSync for you, explains the core concepts, and demos how to copy data between S3 and EFS.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=-ru39l6qljk">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/-ru39l6qljk" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p>Here is the Terraform configuration code, that we used for our example to synchronize data between S3 and EFS. The Terraform configuration creates the following resources:</p><ul><li>S3 bucket</li><li>EFS file system</li><li>DataSync configuration</li><li>EC2 instance allowing you to access the EFS file system</li><li>Security Groups for DataSync and EC2</li><li>IAM roles for Data Sync and EC2</li></ul><p>Use the AWS Systems Manager Session Manager to connect with the EC2 instance to mount and inspect the EFS file system.</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">terraform &#123;</span><br><span class="line">  required_providers &#123;</span><br><span class="line">    aws = &#123;</span><br><span class="line">      source  = &quot;hashicorp/aws&quot;</span><br><span class="line">      version = &quot;~&gt; 3.0&quot;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">provider &quot;aws&quot; &#123;</span><br><span class="line">  region = &quot;eu-west-1&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">data &quot;aws_vpc&quot; &quot;default&quot; &#123;</span><br><span class="line">  default = true</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">data &quot;aws_subnet_ids&quot; &quot;public&quot; &#123;</span><br><span class="line">  vpc_id = data.aws_vpc.default.id</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">data &quot;aws_subnet&quot; &quot;selected&quot; &#123;</span><br><span class="line">  vpc_id = data.aws_vpc.default.id</span><br><span class="line">  id = sort(data.aws_subnet_ids.public.ids)[0]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">data &quot;aws_ami&quot; &quot;amzn2&quot; &#123;</span><br><span class="line">  most_recent = true</span><br><span class="line"></span><br><span class="line">  filter &#123;</span><br><span class="line">    name   = &quot;name&quot;</span><br><span class="line">    values = [&quot;amzn2-ami-hvm-2.0.*-x86_64-gp2&quot;]</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  filter &#123;</span><br><span class="line">    name   = &quot;virtualization-type&quot;</span><br><span class="line">    values = [&quot;hvm&quot;]</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  owners = [&quot;137112412989&quot;]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_datasync_location_s3&quot; &quot;demo&quot; &#123;</span><br><span class="line">  s3_bucket_arn = aws_s3_bucket.demo.arn</span><br><span class="line">  subdirectory = &quot;/&quot;</span><br><span class="line"></span><br><span class="line">  s3_config &#123;</span><br><span class="line">    bucket_access_role_arn = aws_iam_role.datasync.arn</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_datasync_location_efs&quot; &quot;demo&quot; &#123;</span><br><span class="line">  efs_file_system_arn = aws_efs_file_system.demo.arn</span><br><span class="line"></span><br><span class="line">  ec2_config &#123;</span><br><span class="line">    security_group_arns = [ aws_security_group.datasync.arn ]</span><br><span class="line">    subnet_arn = data.aws_subnet.selected.arn</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_datasync_task&quot; &quot;demo&quot; &#123;</span><br><span class="line">  name = &quot;demo-s3-to-efs&quot;</span><br><span class="line">  source_location_arn = aws_datasync_location_s3.demo.arn</span><br><span class="line">  destination_location_arn = aws_datasync_location_efs.demo.arn</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">resource &quot;aws_s3_bucket&quot; &quot;demo&quot; &#123;</span><br><span class="line">  bucket_prefix = &quot;demo-&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_efs_file_system&quot; &quot;demo&quot; &#123;</span><br><span class="line">  tags = &#123;</span><br><span class="line">    Name = &quot;Demo&quot;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_iam_role&quot; &quot;datasync&quot; &#123;</span><br><span class="line">  name = &quot;demo-datasync&quot;</span><br><span class="line"></span><br><span class="line">  assume_role_policy = jsonencode(&#123;</span><br><span class="line">    Version = &quot;2012-10-17&quot;</span><br><span class="line">    Statement = [</span><br><span class="line">      &#123;</span><br><span class="line">        Effect = &quot;Allow&quot;</span><br><span class="line">        Principal = &#123;</span><br><span class="line">            Service = &quot;datasync.amazonaws.com&quot;</span><br><span class="line">        &#125;</span><br><span class="line">        Action = &quot;sts:AssumeRole&quot;</span><br><span class="line">      &#125;,</span><br><span class="line">    ]</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_iam_role_policy&quot; &quot;datasync&quot; &#123;</span><br><span class="line">  role = aws_iam_role.datasync.name</span><br><span class="line"></span><br><span class="line">  policy = jsonencode(&#123;</span><br><span class="line">    Version = &quot;2012-10-17&quot;</span><br><span class="line">    Statement = [</span><br><span class="line">      &#123;</span><br><span class="line">        Effect = &quot;Allow&quot;</span><br><span class="line">        Action = [</span><br><span class="line">          &quot;s3:GetBucketLocation&quot;,</span><br><span class="line">          &quot;s3:ListBucket&quot;,</span><br><span class="line">          &quot;s3:ListBucketMultipartUploads&quot;,</span><br><span class="line">          &quot;s3:HeadBucket&quot;</span><br><span class="line">        ]</span><br><span class="line">        Resource = aws_s3_bucket.demo.arn</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        Effect = &quot;Allow&quot;</span><br><span class="line">        Action = [</span><br><span class="line">          &quot;s3:AbortMultipartUpload&quot;,</span><br><span class="line">          &quot;s3:DeleteObject&quot;,</span><br><span class="line">          &quot;s3:GetObject&quot;,</span><br><span class="line">          &quot;s3:ListMultipartUploadParts&quot;,</span><br><span class="line">          &quot;s3:GetObjectTagging&quot;,</span><br><span class="line">          &quot;s3:PutObjectTagging&quot;,</span><br><span class="line">          &quot;s3:PutObject&quot;</span><br><span class="line">        ]</span><br><span class="line">        Resource = &quot;$&#123;aws_s3_bucket.demo.arn&#125;/*&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_efs_mount_target&quot; &quot;demo&quot; &#123;</span><br><span class="line">  for_each      = data.aws_subnet_ids.public.ids</span><br><span class="line">  file_system_id = aws_efs_file_system.demo.id</span><br><span class="line">  subnet_id      = each.value</span><br><span class="line">  security_groups = [ aws_security_group.efs.id ]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_security_group&quot; &quot;efs&quot; &#123;</span><br><span class="line">    name_prefix = &quot;demo-efs-&quot;</span><br><span class="line">    vpc_id = data.aws_vpc.default.id</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_security_group_rule&quot; &quot;efs_datasync&quot; &#123;</span><br><span class="line">  type              = &quot;ingress&quot;</span><br><span class="line">  from_port         = 2049</span><br><span class="line">  to_port           = 2049</span><br><span class="line">  protocol          = &quot;tcp&quot;</span><br><span class="line">  security_group_id = aws_security_group.efs.id</span><br><span class="line">  source_security_group_id = aws_security_group.datasync.id</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_security_group_rule&quot; &quot;efs_ec2&quot; &#123;</span><br><span class="line">  type              = &quot;ingress&quot;</span><br><span class="line">  from_port         = 2049</span><br><span class="line">  to_port           = 2049</span><br><span class="line">  protocol          = &quot;tcp&quot;</span><br><span class="line">  security_group_id = aws_security_group.efs.id</span><br><span class="line">  source_security_group_id = aws_security_group.ec2.id</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_security_group&quot; &quot;datasync&quot; &#123;</span><br><span class="line">    name_prefix = &quot;demo-datasync-&quot;</span><br><span class="line">    vpc_id = data.aws_vpc.default.id</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_security_group_rule&quot; &quot;datasync&quot; &#123;</span><br><span class="line">  type              = &quot;egress&quot;</span><br><span class="line">  from_port         = 2049</span><br><span class="line">  to_port           = 2049</span><br><span class="line">  protocol          = &quot;tcp&quot;</span><br><span class="line">  security_group_id = aws_security_group.datasync.id</span><br><span class="line">  source_security_group_id = aws_security_group.efs.id</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_instance&quot; &quot;demo&quot; &#123;</span><br><span class="line">  ami           = data.aws_ami.amzn2.id</span><br><span class="line">  instance_type = &quot;t3.micro&quot;</span><br><span class="line">  vpc_security_group_ids = [ aws_security_group.ec2.id ]</span><br><span class="line">  subnet_id = data.aws_subnet.selected.id</span><br><span class="line">  iam_instance_profile = aws_iam_instance_profile.ec2.name</span><br><span class="line"></span><br><span class="line">  tags = &#123;</span><br><span class="line">    Name = &quot;demo-datasync&quot;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_security_group&quot; &quot;ec2&quot; &#123;</span><br><span class="line">    name_prefix = &quot;demo-ec2-&quot;</span><br><span class="line">    vpc_id = data.aws_vpc.default.id</span><br><span class="line"></span><br><span class="line">    egress &#123;</span><br><span class="line">      from_port   = 0</span><br><span class="line">      to_port     = 0</span><br><span class="line">      protocol    = &quot;-1&quot;</span><br><span class="line">      cidr_blocks = [&quot;0.0.0.0/0&quot;]</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_iam_role&quot; &quot;ec2&quot; &#123;</span><br><span class="line">  name = &quot;demo-ec2&quot;</span><br><span class="line"></span><br><span class="line">  assume_role_policy = jsonencode(&#123;</span><br><span class="line">    Version = &quot;2012-10-17&quot;</span><br><span class="line">    Statement = [</span><br><span class="line">      &#123;</span><br><span class="line">        Effect = &quot;Allow&quot;</span><br><span class="line">        Principal = &#123;</span><br><span class="line">          Service = &quot;ec2.amazonaws.com&quot;</span><br><span class="line">        &#125;</span><br><span class="line">        Action = &quot;sts:AssumeRole&quot;</span><br><span class="line">      &#125;,</span><br><span class="line">    ]</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_iam_instance_profile&quot; &quot;ec2&quot; &#123;</span><br><span class="line">  name = &quot;demo-ec2&quot;</span><br><span class="line">  role = aws_iam_role.ec2.name</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_iam_role_policy&quot; &quot;ec2&quot; &#123;</span><br><span class="line">  role = aws_iam_role.ec2.name</span><br><span class="line"></span><br><span class="line">  policy = jsonencode(&#123;</span><br><span class="line">    Version = &quot;2012-10-17&quot;</span><br><span class="line">    Statement = [</span><br><span class="line">      &#123;</span><br><span class="line">        Effect = &quot;Allow&quot;</span><br><span class="line">        Action = [</span><br><span class="line">          &quot;ec2messages:*&quot;,</span><br><span class="line">          &quot;ssmmessages:*&quot;,</span><br><span class="line">          &quot;ssm:UpdateInstanceInformation&quot;,</span><br><span class="line">          &quot;ssm:GetDeployablePatchSnapshotForInstance&quot;,</span><br><span class="line">          &quot;ssm:ListAssociations&quot;</span><br><span class="line">        ]</span><br><span class="line">        Resource = &quot;*&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
      </content:encoded>
    </item>
    <item>
      <title>re:Invent 2020: Recap of Werner Vogels's Keynote</title>
      <link>https://cloudonaut.io/reinvent-2020-recap-werner-vogels-keynote/</link>
      <description>
        <![CDATA[<p>Werner Vogels’s keynote was a blast and definitely the highlight of re:Invent 2020. Michael and I are going through the announced feature]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/reinvent/">reinvent</category>
      <guid isPermaLink="true">https://cloudonaut.io/reinvent-2020-recap-werner-vogels-keynote/</guid>
      <pubDate>Wed, 16 Dec 2020 15:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Werner Vogels’s keynote was a blast and definitely the highlight of re:Invent 2020. Michael and I are going through the announced features and services. As usual, we also take a look at the technical details.</p><ul><li><a href="https://aws.amazon.com/blogs/aws/aws-cloudshell-command-line-access-to-aws-resources/" target="_blank" rel="noopener">CloudShell</a></li><li><a href="https://aws.amazon.com/blogs/aws/new-vpc-insights-analyzes-reachability-and-visibility-in-vpcs/" target="_blank" rel="noopener">VPC Reachability Analyzer</a></li><li><a href="https://aws.amazon.com/fis/" target="_blank" rel="noopener">Fault Injection Simulator</a></li><li><a href="https://aws.amazon.com/blogs/aws/join-the-preview-amazon-managed-service-for-prometheus-amp/" target="_blank" rel="noopener">Amazon Managed Service for Prometheus (AMP)</a></li><li><a href="https://aws.amazon.com/blogs/aws/announcing-amazon-managed-grafana-service-in-preview/" target="_blank" rel="noopener">Amazon Managed Service for Grafana</a></li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/12/lasvegas@730w.webp 730w, /images/2020/12/lasvegas@730w2x.webp 1460w, /images/2020/12/lasvegas@610w.webp 610w, /images/2020/12/lasvegas@610w2x.webp 1220w, /images/2020/12/lasvegas@450w.webp 450w, /images/2020/12/lasvegas@450w2x.webp 900w, /images/2020/12/lasvegas@330w.webp 330w, /images/2020/12/lasvegas@330w2x.webp 660w, /images/2020/12/lasvegas@545w.webp 545w, /images/2020/12/lasvegas@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/12/lasvegas@730w.jpg 730w, /images/2020/12/lasvegas@730w2x.jpg 1460w, /images/2020/12/lasvegas@610w.jpg 610w, /images/2020/12/lasvegas@610w2x.jpg 1220w, /images/2020/12/lasvegas@450w.jpg 450w, /images/2020/12/lasvegas@450w2x.jpg 900w, /images/2020/12/lasvegas@330w.jpg 330w, /images/2020/12/lasvegas@330w2x.jpg 660w, /images/2020/12/lasvegas@545w.jpg 545w, /images/2020/12/lasvegas@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/12/lasvegas.jpg" alt="re:Invent 2020" title="re:Invent 2020"></picture></p><p>Enjoy listening or watching!</p><h2 id="Video"><a href="#Video" class="headerlink" title="Video"></a>Video</h2><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=T4tBt80idyE">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/T4tBt80idyE" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="Podcast"><a href="#Podcast" class="headerlink" title="Podcast"></a>Podcast</h2><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/36-reinvent-2020-recap-werner-vogels-keynote/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>Please, share this with your friends and coworkers.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Next Generation Load Balancing: ALB with gRPC and HTTP/2</title>
      <link>https://cloudonaut.io/next-generation-load-balancing-alb-http2-grpc/</link>
      <description>
        <![CDATA[<p>AWS announced an important update for the Application Load Balancer (ALB) in November 2020: Support for gRPC and HTTP&#x2F;2.</p>
<p>HTTP]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/elb/">elb</category>
      <category domain="https://cloudonaut.io/tag/alb/">alb</category>
      <guid isPermaLink="true">https://cloudonaut.io/next-generation-load-balancing-alb-http2-grpc/</guid>
      <pubDate>Wed, 09 Dec 2020 20:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>AWS announced an important update for the Application Load Balancer (ALB) in November 2020: Support for gRPC and HTTP&#x2F;2.</p><p>HTTP&#x2F;2 comes with request multiplexing over a single TCP connection, header compression (HPACK) which reduces network utilization, and a<br>binary protocol which reduces overhead in parsing data.</p><p>gRPC is a high-performance RPC framework with full-duplex streaming. It is designed to connect mircoservices efficiently and uses HTTP&#x2F;2 under the covers.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/12/modern@730w.webp 730w, /images/2020/12/modern@730w2x.webp 1460w, /images/2020/12/modern@610w.webp 610w, /images/2020/12/modern@610w2x.webp 1220w, /images/2020/12/modern@450w.webp 450w, /images/2020/12/modern@450w2x.webp 900w, /images/2020/12/modern@330w.webp 330w, /images/2020/12/modern@330w2x.webp 660w, /images/2020/12/modern@545w.webp 545w, /images/2020/12/modern@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/12/modern@730w.jpg 730w, /images/2020/12/modern@730w2x.jpg 1460w, /images/2020/12/modern@610w.jpg 610w, /images/2020/12/modern@610w2x.jpg 1220w, /images/2020/12/modern@450w.jpg 450w, /images/2020/12/modern@450w2x.jpg 900w, /images/2020/12/modern@330w.jpg 330w, /images/2020/12/modern@330w2x.jpg 660w, /images/2020/12/modern@545w.jpg 545w, /images/2020/12/modern@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/12/modern.jpg" alt="Next Generation Load Balancing" title="Next Generation Load Balancing"></picture></p><p>This video includes a demo of a gRPC service running behind an ALB.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=4zVyRHXEWgM">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/4zVyRHXEWgM" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>3½ ways to workaround missing CloudFormation support</title>
      <link>https://cloudonaut.io/three-and-a-half-ways-to-workaround-missing-cloudformation-support/</link>
      <description>
        <![CDATA[<p>Are you following the Infrastructure as Code approach using CloudFormation? If so, I bet you encountered a situation where CloudFormation]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/three-and-a-half-ways-to-workaround-missing-cloudformation-support/</guid>
      <pubDate>Wed, 09 Dec 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Are you following the Infrastructure as Code approach using CloudFormation? If so, I bet you encountered a situation where CloudFormation <a href="https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues" target="_blank" rel="noopener">misses support for a service’s latest features</a>. I run into those issues weekly! So what can we do about it?</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/12/construction-work@730w.webp 730w, /images/2020/12/construction-work@730w2x.webp 1460w, /images/2020/12/construction-work@610w.webp 610w, /images/2020/12/construction-work@610w2x.webp 1220w, /images/2020/12/construction-work@450w.webp 450w, /images/2020/12/construction-work@450w2x.webp 900w, /images/2020/12/construction-work@330w.webp 330w, /images/2020/12/construction-work@330w2x.webp 660w, /images/2020/12/construction-work@545w.webp 545w, /images/2020/12/construction-work@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/12/construction-work@730w.jpg 730w, /images/2020/12/construction-work@730w2x.jpg 1460w, /images/2020/12/construction-work@610w.jpg 610w, /images/2020/12/construction-work@610w2x.jpg 1220w, /images/2020/12/construction-work@450w.jpg 450w, /images/2020/12/construction-work@450w2x.jpg 900w, /images/2020/12/construction-work@330w.jpg 330w, /images/2020/12/construction-work@330w2x.jpg 660w, /images/2020/12/construction-work@545w.jpg 545w, /images/2020/12/construction-work@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/12/construction-work.jpg" alt="3½ ways to workaround missing CloudFormation support" title="3½ ways to workaround missing CloudFormation support"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/35-three-and-a-half-ways-to-workaround-missing-cloudformation-support/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>For quite some time, a Custom Resource backed by a Lambda function was the way to go. A couple of weeks ago, I learned about new creative ways from <a href="https://x.com/s0enke" target="_blank" rel="noopener">Soenke</a> (co-author of this blog post and co-founder of <a href="https://superluminar.io/" target="_blank" rel="noopener">superluminar</a>). Before we present you with the workarounds, let me issue one warning: If you start using workarounds, you will face issues when switching to native CloudFormation in the future. If you can not replace your workaround resource without downtime (e.g., datastores, network config), I strongly advise against using any of the workarounds. Better wait for native CloudFormation support or go with Terraform.</p><h2 id="Custom-Resource"><a href="#Custom-Resource" class="headerlink" title="Custom Resource"></a>Custom Resource</h2><p>A <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html" target="_blank" rel="noopener">custom resource</a> has to support three actions: <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requesttypes-create.html" target="_blank" rel="noopener">create</a>, <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requesttypes-update.html" target="_blank" rel="noopener">update</a>, <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requesttypes-delete.html" target="_blank" rel="noopener">delete</a>. The steps work like this:</p><ul><li>The create event contains the <code>ResourceProperties</code> defined in your CloudFormation template. Create the resource and return at least a <code>PhysicalResourceId</code> to identify the resource later.</li><li>The update event contains the <code>PhysicalResourceId</code> from the create step, the <code>OldResourceProperties</code>, and the new <code>ResourceProperties</code> defined in your template. If possible, update the existing resource (identified by <code>PhysicalResourceId</code>) using the new <code>ResourceProperties</code>. If the resource can not be updated, create a new resource, and return a new <code>PhysicalResourceId</code>. In the case of a replacement, you receive a delete event during the update cleanup phase for the old <code>PhysicalResourceId</code>.</li><li>Finally, the delete event contains the <code>PhysicalResourceId</code> and <code>ResourceProperties</code> defined in your template. Delete the resource identified by the <code>PhysicalResourceId</code>.</li></ul><p>You can either implement the custom resource using a Lambda function or use an SNS topic with a subscriber of your choice. If you are interested in the details, check out this <a href="https://github.com/widdix/aws-cf-templates/blob/master/security/account-password-policy.yaml#L149" target="_blank" rel="noopener">Custom Resource to manage the IAM Password Policy</a>.</p><h2 id="Resource-Providers-CloudFormation-Registry"><a href="#Resource-Providers-CloudFormation-Registry" class="headerlink" title="Resource Providers &amp; CloudFormation Registry"></a>Resource Providers &amp; CloudFormation Registry</h2><p>Resource Providers are the evolution of custom resources. The benefits are:</p><ul><li>A <a href="https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html" target="_blank" rel="noopener">well-defined schema of the resource</a> that is machine-readable</li><li>You can share them with other AWS accounts</li><li>All features of CloudFormation are supported, such as drift detection and resource import </li><li>Resource Provider code execution is managed by AWS as well as IAM credentials</li></ul><p>Sharing Resource Providers with other accounts is more cumbersome than it should be at the moment. Development effort is higher compared to a custom resource. The best examples are the <a href="https://github.com/aws-cloudformation?q=aws-cloudformation-resource-provider&type=&language=" target="_blank" rel="noopener">official CloudFormation resource types</a>.</p><h2 id="SSM-Automation-documents"><a href="#SSM-Automation-documents" class="headerlink" title="SSM Automation documents"></a>SSM Automation documents</h2><p>Systems Manager Automation provides a way to <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-action-executeAwsApi.html" target="_blank" rel="noopener">call arbitrary AWS APIs</a> with a YAML&#x2F;JSON language. One advantage of this approach is that one does not need to maintain or update language runtimes like Javascript or Python.</p><p>SSM Automation also provides basic features as conditions and a <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-actions.html" target="_blank" rel="noopener">library of standard operating procedures</a> such as <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-action-createstack.html" target="_blank" rel="noopener">creating a CloudFormation stack</a> or <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-action-executeAutomation.html" target="_blank" rel="noopener">executing</a> other <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-documents-reference-details.html" target="_blank" rel="noopener">AWS-provided SSM documents</a>. </p><p>CloudFormation cannot trigger SSM Automation documents directly, but EventBridge events can trigger them. A <a href="https://github.com/superwerker/superwerker/blob/1bae89d520938575bbc2e9e5065b513676513883/components/guardduty.yaml#L7-L120" target="_blank" rel="noopener">practical example is setting up GuardDuty Delegated Administrator</a> - a feature that has no CloudFormation support at the time of writing this.</p><p>It’s also possible to execute automation documents across AWS accounts and regions. This comes in handy when administering medium-sized or bigger multi-region&#x2F;account setups. For example, <a href="https://github.com/superwerker/superwerker/blob/1bae89d520938575bbc2e9e5065b513676513883/components/guardduty.yaml#L87-L107" target="_blank" rel="noopener">enabling GuardDuty for existing accounts</a>.</p><p>Important detail: The underlying AWS Client SDK is only updated periodically, so it’s impossible to use newer features and releases of AWS APIs. </p><h2 id="CloudWatch-Synthetics"><a href="#CloudWatch-Synthetics" class="headerlink" title="CloudWatch Synthetics"></a>CloudWatch Synthetics</h2><p>Some AWS services or features don’t provide APIs at all. Currently, examples are:</p><ul><li>Billing features like setting the VAT ID, tax inheritance, or currency</li><li>AWS Control Tower</li><li>Partially Amazon Connect and AWS Chatbot</li></ul><p>Automating these features is nearly impossible, but as a last resort, it can be done with headless browser frameworks like Puppeteer&#x2F;Selenium.</p><p>AWS provides a service called CloudWatch Synthetics to monitor websites. It uses Puppeteer&#x2F;Selenium under the hood, so it can be used to automatically login into the AWS Management Console and automate anything.<br>A big advantage is that synthetics canaries are completely managed by AWS, so one does not need a process to install or keep runtimes and libraries up to date. </p><p>One example is <a href="https://github.com/superwerker/superwerker/blob/1bae89d520938575bbc2e9e5065b513676513883/components/control-tower.yaml#L37-L139" target="_blank" rel="noopener">activating Control Tower</a> (which provides no API currently) via Cloudformation. The canary is started right away after creation since <code>StartCanaryAfterCreation</code> is set to <code>true</code>. </p><p>There are some limitations as well:</p><ul><li>The AWS Management Console is no stable API. If something gets changed in the UI, e.g., new design or HTML&#x2F;CSS changes, the automation could break.</li><li>It’s not easy to get the result of the automation since the API call does not wait until the canary is finished. This could be circumvented by using a custom resource Lambda function with Puppeteer&#x2F;Selenium installed instead of Synthetics.</li><li>If more control over the start conditions of the canary was necessary, it would need a CloudFormation custom resource as a shim to trigger the StartCanary API (see <a href="https://github.com/superwerker/superwerker/blob/1bae89d520938575bbc2e9e5065b513676513883/components/control-tower.yaml#L263-L264" target="_blank" rel="noopener">example</a>).</li></ul><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Let me end with a summary. If there is no API, CloudWatch Synthetics is your only option. If there is an API, you can choose between SSM Automation, Custom Resource, and Resource Registry. Custom resources and SSM provide a fast track. Resource Providers are the way to implement a resource type in CloudFormation properly.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to configure SAML for AWS SSO?</title>
      <link>https://cloudonaut.io/how-to-configure-saml-for-aws-sso/</link>
      <description>
        <![CDATA[<p>AWS SSO is a great way to grant engineers access to AWS accounts. By default, AWS SSO comes with a built in user database. However, it is]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <category domain="https://cloudonaut.io/tag/sso/">sso</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-configure-saml-for-aws-sso/</guid>
      <pubDate>Thu, 03 Dec 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>AWS SSO is a great way to grant engineers access to AWS accounts. By default, AWS SSO comes with a built in user database. However, it is also possible to configure identity federation via SAML. Doing so allows you to re-use existing users and groups. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/12/lock@730w.webp 730w, /images/2020/12/lock@730w2x.webp 1460w, /images/2020/12/lock@610w.webp 610w, /images/2020/12/lock@610w2x.webp 1220w, /images/2020/12/lock@450w.webp 450w, /images/2020/12/lock@450w2x.webp 900w, /images/2020/12/lock@330w.webp 330w, /images/2020/12/lock@330w2x.webp 660w, /images/2020/12/lock@545w.webp 545w, /images/2020/12/lock@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/12/lock@730w.jpg 730w, /images/2020/12/lock@730w2x.jpg 1460w, /images/2020/12/lock@610w.jpg 610w, /images/2020/12/lock@610w2x.jpg 1220w, /images/2020/12/lock@450w.jpg 450w, /images/2020/12/lock@450w2x.jpg 900w, /images/2020/12/lock@330w.jpg 330w, /images/2020/12/lock@330w2x.jpg 660w, /images/2020/12/lock@545w.jpg 545w, /images/2020/12/lock@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/12/lock.jpg" alt="CodeBuild Report Groups 101" title="CodeBuild Report Groups 101"></picture></p><p>In this video you will learn how to configure identity federation for AWS Single Sign On (SSO) with SAML. The video includes demos for Google Workspace and Azure AD.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=SyXWcPxIf7Y">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/SyXWcPxIf7Y" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>A recap of the re:Invent 2020 Keynote with Andy Jassy</title>
      <link>https://cloudonaut.io/a-recap-of-the-reinvent-2020-keynote-with-andy-jassy/</link>
      <description>
        <![CDATA[<p>We are discussing Andy Jassy’s keynote from re:Invent 2020. The focus is on the newly announced services and features: ECS Anywhere, EBS]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/reinvent/">reinvent</category>
      <guid isPermaLink="true">https://cloudonaut.io/a-recap-of-the-reinvent-2020-keynote-with-andy-jassy/</guid>
      <pubDate>Wed, 02 Dec 2020 23:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>We are discussing Andy Jassy’s keynote from re:Invent 2020. The focus is on the newly announced services and features: ECS Anywhere, EBS volumes (gp3), Aurora Serverless v3, Lambda Container Support, and many more.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/12/lasvegas@730w.webp 730w, /images/2020/12/lasvegas@730w2x.webp 1460w, /images/2020/12/lasvegas@610w.webp 610w, /images/2020/12/lasvegas@610w2x.webp 1220w, /images/2020/12/lasvegas@450w.webp 450w, /images/2020/12/lasvegas@450w2x.webp 900w, /images/2020/12/lasvegas@330w.webp 330w, /images/2020/12/lasvegas@330w2x.webp 660w, /images/2020/12/lasvegas@545w.webp 545w, /images/2020/12/lasvegas@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/12/lasvegas@730w.jpg 730w, /images/2020/12/lasvegas@730w2x.jpg 1460w, /images/2020/12/lasvegas@610w.jpg 610w, /images/2020/12/lasvegas@610w2x.jpg 1220w, /images/2020/12/lasvegas@450w.jpg 450w, /images/2020/12/lasvegas@450w2x.jpg 900w, /images/2020/12/lasvegas@330w.jpg 330w, /images/2020/12/lasvegas@330w2x.jpg 660w, /images/2020/12/lasvegas@545w.jpg 545w, /images/2020/12/lasvegas@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/12/lasvegas.jpg" alt="re:Invent 2020" title="re:Invent 2020"></picture></p><p>Enjoy listening or watching!</p><h2 id="Video"><a href="#Video" class="headerlink" title="Video"></a>Video</h2><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=_27TCsg3qds">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/_27TCsg3qds" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="Podcast"><a href="#Podcast" class="headerlink" title="Podcast"></a>Podcast</h2><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/34-a-recap-of-the-reinvent-2020-keynote-with-andy-jassy/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>Please, share this with your friends and coworkers.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Unusual AWS Architectures</title>
      <link>https://cloudonaut.io/unusual-aws-architectures/</link>
      <description>
        <![CDATA[<p>AWS provides many building blocks. As architects, we have to choose the right building blocks to construct our systems. But sometimes, th]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/rds/">rds</category>
      <category domain="https://cloudonaut.io/tag/ebs/">ebs</category>
      <category domain="https://cloudonaut.io/tag/alb/">alb</category>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/unusual-aws-architectures/</guid>
      <pubDate>Thu, 26 Nov 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>AWS provides many building blocks. As architects, we have to choose the right building blocks to construct our systems. But sometimes, the proper building block is not available, and we have to make compromises. In this blog post, I show four unusual AWS architectures that deal with AWS’s limitations in creative ways.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/11/unusual@730w.webp 730w, /images/2020/11/unusual@730w2x.webp 1460w, /images/2020/11/unusual@610w.webp 610w, /images/2020/11/unusual@610w2x.webp 1220w, /images/2020/11/unusual@450w.webp 450w, /images/2020/11/unusual@450w2x.webp 900w, /images/2020/11/unusual@330w.webp 330w, /images/2020/11/unusual@330w2x.webp 660w, /images/2020/11/unusual@545w.webp 545w, /images/2020/11/unusual@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/11/unusual@730w.jpg 730w, /images/2020/11/unusual@730w2x.jpg 1460w, /images/2020/11/unusual@610w.jpg 610w, /images/2020/11/unusual@610w2x.jpg 1220w, /images/2020/11/unusual@450w.jpg 450w, /images/2020/11/unusual@450w2x.jpg 900w, /images/2020/11/unusual@330w.jpg 330w, /images/2020/11/unusual@330w2x.jpg 660w, /images/2020/11/unusual@545w.jpg 545w, /images/2020/11/unusual@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/11/unusual.jpg" alt="Unusual AWS Architectures" title="Unusual AWS Architectures"></picture></p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><h2 id="Active-Standby"><a href="#Active-Standby" class="headerlink" title="Active-Standby"></a>Active-Standby</h2><p>Not all applications support active-active deployments where multiple instances of an application server run simultaneously and serve traffic. Sometimes, only one application server is allowed to receive user traffic at a time. For example, if the application stores session state in memory. But you still want to prepare for failure. In this scenario, an active-standby deployment can help as the following figure — drawn with <a href="http://www.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft</a> — shows.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/11/active-standby@730w.webp 730w, /images/2020/11/active-standby@730w2x.webp 1460w, /images/2020/11/active-standby@610w.webp 610w, /images/2020/11/active-standby@610w2x.webp 1220w, /images/2020/11/active-standby@450w.webp 450w, /images/2020/11/active-standby@450w2x.webp 900w, /images/2020/11/active-standby@330w.webp 330w, /images/2020/11/active-standby@330w2x.webp 660w, /images/2020/11/active-standby@545w.webp 545w, /images/2020/11/active-standby@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/11/active-standby@730w.png 730w, /images/2020/11/active-standby@730w2x.png 1460w, /images/2020/11/active-standby@610w.png 610w, /images/2020/11/active-standby@610w2x.png 1220w, /images/2020/11/active-standby@450w.png 450w, /images/2020/11/active-standby@450w2x.png 900w, /images/2020/11/active-standby@330w.png 330w, /images/2020/11/active-standby@330w2x.png 660w, /images/2020/11/active-standby@545w.png 545w, /images/2020/11/active-standby@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/11/active-standby.png" alt="Active-Standby" title="Active-Standby"></picture></p><p>You launch and install two EC2 instances in two different Availability Zones (AZs). The Application Load Balancer (ALB) has only a single target, the active EC2 instance. The ALB will send a health check to the active instance at a regular interval.  Ensure to enable cross AZ traffic support on your ALB. On top of that, a Lambda function is configured to poll the ALB API every minute. If the target status changes to unhealthy, the Lambda function deregisters the unhealthy target and registers the standby as a target. Within 90 seconds, user traffic can continue. </p><p>This architecture also allows you to perform blue-green deployments. You can install the new release on the standby instance. Once the installation is complete, switch the active with the standby instance on the load balancer. If the release causes issues, you can easily switch back. If the release works as expected, you can install it on the previously active instance.</p><h2 id="Three-tier-architecture-with-AZ-preference"><a href="#Three-tier-architecture-with-AZ-preference" class="headerlink" title="Three-tier architecture with AZ preference"></a>Three-tier architecture with AZ preference</h2><p>A typical three-tier-architecture consists of web servers, application servers, and database servers. The typical AWS architecture looks like this:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/11/az-preference-1@730w.webp 730w, /images/2020/11/az-preference-1@730w2x.webp 1460w, /images/2020/11/az-preference-1@610w.webp 610w, /images/2020/11/az-preference-1@610w2x.webp 1220w, /images/2020/11/az-preference-1@450w.webp 450w, /images/2020/11/az-preference-1@450w2x.webp 900w, /images/2020/11/az-preference-1@330w.webp 330w, /images/2020/11/az-preference-1@330w2x.webp 660w, /images/2020/11/az-preference-1@545w.webp 545w, /images/2020/11/az-preference-1@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/11/az-preference-1@730w.png 730w, /images/2020/11/az-preference-1@730w2x.png 1460w, /images/2020/11/az-preference-1@610w.png 610w, /images/2020/11/az-preference-1@610w2x.png 1220w, /images/2020/11/az-preference-1@450w.png 450w, /images/2020/11/az-preference-1@450w2x.png 900w, /images/2020/11/az-preference-1@330w.png 330w, /images/2020/11/az-preference-1@330w2x.png 660w, /images/2020/11/az-preference-1@545w.png 545w, /images/2020/11/az-preference-1@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/11/az-preference-1.png" alt="Three-tier architecture with AZ preference (1)" title="Three-tier architecture with AZ preference (1)"></picture></p><p>The EC2 instances of the web and app tier run in multiple AZs and are fronted by a load balancer (e.g., Application Load Balancer). The RDS database is deployed in Multi-AZ mode, but all reads&#x2F;writes happen on the primary database instance. This architecture will cause a lot of cross AZ traffic. E.g., if an app server from AZ B sends a query to the database in AZ A.</p><p>You might ask yourself: What’s the problem with cross AT traffic? First, you pay $0.01 per GB of cross AZ traffic while traffic in the same AZ is free. Second, you will experience additional latency (about 1ms). For some applications, we need to reduce cross AZ traffic to the minimum. The following figure shows the evolution of the previous architecture.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/11/az-preference-2@730w.webp 730w, /images/2020/11/az-preference-2@730w2x.webp 1460w, /images/2020/11/az-preference-2@610w.webp 610w, /images/2020/11/az-preference-2@610w2x.webp 1220w, /images/2020/11/az-preference-2@450w.webp 450w, /images/2020/11/az-preference-2@450w2x.webp 900w, /images/2020/11/az-preference-2@330w.webp 330w, /images/2020/11/az-preference-2@330w2x.webp 660w, /images/2020/11/az-preference-2@545w.webp 545w, /images/2020/11/az-preference-2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/11/az-preference-2@730w.png 730w, /images/2020/11/az-preference-2@730w2x.png 1460w, /images/2020/11/az-preference-2@610w.png 610w, /images/2020/11/az-preference-2@610w2x.png 1220w, /images/2020/11/az-preference-2@450w.png 450w, /images/2020/11/az-preference-2@450w2x.png 900w, /images/2020/11/az-preference-2@330w.png 330w, /images/2020/11/az-preference-2@330w2x.png 660w, /images/2020/11/az-preference-2@545w.png 545w, /images/2020/11/az-preference-2@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/11/az-preference-2.png" alt="Three-tier architecture with AZ preference (2)" title="Three-tier architecture with AZ preference (2)"></picture></p><p>I want to highlight two changes. Once the traffic arrives at a web server, it stays in the same AZ and is forwarded to an application server in the same AZ. If possible, you could try to get rid of the load balancer between your web and app tier.</p><p>The second change applies to the database tier. To avoid cross AZ traffic for reads, we added a read replica. The application server has to send writes to another database endpoint than reads to make this work! The write traffic will still cross AZ boundaries.</p><h2 id="EBS-replication-among-two-AZs"><a href="#EBS-replication-among-two-AZs" class="headerlink" title="EBS replication among two AZs"></a>EBS replication among two AZs</h2><p>EBS volumes are bound to a single AZ. During an AZ outage, the data will not be available. The good news is that the data is not lost. As soon as the AZ recovers, you can re-access your data. But what if you need to access the data earlier? What if you need to guarantee to recover within a specific time?</p><p>One option is to create EBS snapshots. If you run snapshots every day, you can lose up to 24 hours of data. If you run snapshots every hour, you can lose up to 1 hour of data. Unfortunately, you can not reduce your Recovery Point Objective (RPO) significantly lower than 60 minutes with snapshots. Additionally, If you restore an EBS volume from a snapshot, the data is copied to the volume asynchronously on first access. Therefore, your EBS volume is initially very slow, which is likely not tolerable if your RTO is lower than the time it takes to restore the volume from the snapshot.</p><p>So what’s the solution to the problem? On Linux, we can use a DRBD (Distributed Replicated Block Device) to replicate all changes from one volume to another volume, as the following figure shows.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/11/ebs-replication@730w.webp 730w, /images/2020/11/ebs-replication@730w2x.webp 1460w, /images/2020/11/ebs-replication@610w.webp 610w, /images/2020/11/ebs-replication@610w2x.webp 1220w, /images/2020/11/ebs-replication@450w.webp 450w, /images/2020/11/ebs-replication@450w2x.webp 900w, /images/2020/11/ebs-replication@330w.webp 330w, /images/2020/11/ebs-replication@330w2x.webp 660w, /images/2020/11/ebs-replication@545w.webp 545w, /images/2020/11/ebs-replication@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/11/ebs-replication@730w.png 730w, /images/2020/11/ebs-replication@730w2x.png 1460w, /images/2020/11/ebs-replication@610w.png 610w, /images/2020/11/ebs-replication@610w2x.png 1220w, /images/2020/11/ebs-replication@450w.png 450w, /images/2020/11/ebs-replication@450w2x.png 900w, /images/2020/11/ebs-replication@330w.png 330w, /images/2020/11/ebs-replication@330w2x.png 660w, /images/2020/11/ebs-replication@545w.png 545w, /images/2020/11/ebs-replication@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/11/ebs-replication.png" alt="EBS replication with DRBD" title="EBS replication with DRBD"></picture></p><p>DRBD requires that you have two EC2 instances running. The instance in AZ A mounts the volume in AZ A. The instance in AZ B mounts the volume in AZ B. The DRBD software replicates all changes from one side to the other. This way, you always have a hot copy of your EBS volume. DRBD allows you to replicate synchronously (slows down performance but provides a minimal RPO) or asynchronously (better performance, but you risk losing a tiny slice of data in case of a failure).</p><p>PS: If you are lucky, you find a native data replication mechanism provided out of the box by the software application you try to run!</p><h2 id="Oracle-APEX-on-Fargate-and-RDS"><a href="#Oracle-APEX-on-Fargate-and-RDS" class="headerlink" title="Oracle APEX on Fargate and RDS"></a>Oracle APEX on Fargate and RDS</h2><p>Oracle Application Express (APEX) is a way to create CRUD applications without code that interact with an Oracle database. We see many enterprise applications that are based on APEX. We asked ourselves: What’s the most modern way to run this? It turns out that you can run an APEX application in a Docker container on Fargate.</p><p>This architecture minimized the operational effort to a minimum. It was never easier to run enterprise applications based on APEX in the cloud! No rewrite required.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>AWS covers most use cases already. But sometimes, you discover that your requirements can not be satisfied. Now it’s time to be creative. Can you use an AWS service in an unusual way? Is there a feature that you can use slightly differently to achieve your goal? Can you combine AWS with software from 3rd parties (open source or commercial) to achieve our goal? So far, I always found a way to implement a solution. And of course, always challenge the requirements!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>CodeBuild Report Groups 101</title>
      <link>https://cloudonaut.io/codebuild-report-groups-101/</link>
      <description>
        <![CDATA[<p>AWS CodeBuild provides a fully managed, Docker-based build environment. You can use it in standalone mode or together with CodePipeline.]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/codebuild/">codebuild</category>
      <guid isPermaLink="true">https://cloudonaut.io/codebuild-report-groups-101/</guid>
      <pubDate>Tue, 24 Nov 2020 07:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>AWS CodeBuild provides a fully managed, Docker-based build environment. You can use it in standalone mode or together with CodePipeline. Report Groups help you to make unit test results and code coverage visible because only the visible can be improved with confidence.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/11/spotlight@730w.webp 730w, /images/2020/11/spotlight@730w2x.webp 1460w, /images/2020/11/spotlight@610w.webp 610w, /images/2020/11/spotlight@610w2x.webp 1220w, /images/2020/11/spotlight@450w.webp 450w, /images/2020/11/spotlight@450w2x.webp 900w, /images/2020/11/spotlight@330w.webp 330w, /images/2020/11/spotlight@330w2x.webp 660w, /images/2020/11/spotlight@545w.webp 545w, /images/2020/11/spotlight@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/11/spotlight@730w.jpg 730w, /images/2020/11/spotlight@730w2x.jpg 1460w, /images/2020/11/spotlight@610w.jpg 610w, /images/2020/11/spotlight@610w2x.jpg 1220w, /images/2020/11/spotlight@450w.jpg 450w, /images/2020/11/spotlight@450w2x.jpg 900w, /images/2020/11/spotlight@330w.jpg 330w, /images/2020/11/spotlight@330w2x.jpg 660w, /images/2020/11/spotlight@545w.jpg 545w, /images/2020/11/spotlight@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/11/spotlight.jpg" alt="CodeBuild Report Groups 101" title="CodeBuild Report Groups 101"></picture></p><p>In this video, you learn to capture test and coverage results in CodeBuild. Making unit test results and code coverage visible helps your team to write high-quality code. And I promise CodeBuild Report Groups are not hard to integrate!</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=axaYOwocWmc">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/axaYOwocWmc" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Amazon EventBridge versus Amazon SNS: What's the difference?</title>
      <link>https://cloudonaut.io/eventbridge-vs-sns/</link>
      <description>
        <![CDATA[<p><a href="https://aws.amazon.com/eventbridge/" target="_blank" rel="noopener">Amazon EventBridge</a> (formerly CloudWatch Events) and <a h]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/sns/">sns</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <category domain="https://cloudonaut.io/tag/eventbridge/">eventbridge</category>
      <guid isPermaLink="true">https://cloudonaut.io/eventbridge-vs-sns/</guid>
      <pubDate>Wed, 18 Nov 2020 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><a href="https://aws.amazon.com/eventbridge/" target="_blank" rel="noopener">Amazon EventBridge</a> (formerly CloudWatch Events) and <a href="https://aws.amazon.com/sns/" target="_blank" rel="noopener">Amazon SNS</a> provide a way to send events to multiple subscribers. From a high-level perspective, both services are similar. This leads to the question: But how are they different? What advantages do the services have? Should you choose SNS or EventBridge?</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/11/versus@730w.webp 730w, /images/2020/11/versus@730w2x.webp 1460w, /images/2020/11/versus@610w.webp 610w, /images/2020/11/versus@610w2x.webp 1220w, /images/2020/11/versus@450w.webp 450w, /images/2020/11/versus@450w2x.webp 900w, /images/2020/11/versus@330w.webp 330w, /images/2020/11/versus@330w2x.webp 660w, /images/2020/11/versus@545w.webp 545w, /images/2020/11/versus@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/11/versus@730w.jpg 730w, /images/2020/11/versus@730w2x.jpg 1460w, /images/2020/11/versus@610w.jpg 610w, /images/2020/11/versus@610w2x.jpg 1220w, /images/2020/11/versus@450w.jpg 450w, /images/2020/11/versus@450w2x.jpg 900w, /images/2020/11/versus@330w.jpg 330w, /images/2020/11/versus@330w2x.jpg 660w, /images/2020/11/versus@545w.jpg 545w, /images/2020/11/versus@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/11/versus.jpg" alt="EventBridge versus SNS" title="EventBridge versus SNS"></picture></p><p>From time to time, I scroll through major service announcements by AWS. One thing I noticed: EventBridge received four new major features (content filtering, schema registry, DLQs, replay &amp; archival) this year already. SNS received one new feature (FIFO topics) in 2020 so far. On top of that, a nasty <a href="/loosing-trust-in-aws-sns-broken-for-24-days/">SNS bug</a> forced me to look for alternatives. In this post, I present you the options and help you to choose the service for your use case.</p><h2 id="SNS-Standard"><a href="#SNS-Standard" class="headerlink" title="SNS Standard"></a>SNS Standard</h2><p>First of all, <a href="https://aws.amazon.com/sns/" target="_blank" rel="noopener">Amazon SNS</a> comes in two flavors. SNS Standard (which was the only option until October 2020) and SNS FIFO. This section covers SNS Standard. The next section will talk about SNS FIFO.</p><p>SNS is a fully managed, publish&#x2F;subscribe system. You send a message to a topic, and all subscribers to that topic will receive a copy of the message.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/sns@730w.webp 730w, /images/2020/07/sns@730w2x.webp 1460w, /images/2020/07/sns@610w.webp 610w, /images/2020/07/sns@610w2x.webp 1220w, /images/2020/07/sns@450w.webp 450w, /images/2020/07/sns@450w2x.webp 900w, /images/2020/07/sns@330w.webp 330w, /images/2020/07/sns@330w2x.webp 660w, /images/2020/07/sns@545w.webp 545w, /images/2020/07/sns@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/sns@730w.png 730w, /images/2020/07/sns@730w2x.png 1460w, /images/2020/07/sns@610w.png 610w, /images/2020/07/sns@610w2x.png 1220w, /images/2020/07/sns@450w.png 450w, /images/2020/07/sns@450w2x.png 900w, /images/2020/07/sns@330w.png 330w, /images/2020/07/sns@330w2x.png 660w, /images/2020/07/sns@545w.png 545w, /images/2020/07/sns@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/sns.png" alt="SNS" title="SNS"></picture></p><p>Subscribers are always push-based. For example:</p><ul><li>SNS invokes an HTTP endpoint</li><li>SNS invokes a Lambda function</li><li>SNS sends a message to SQS</li><li>SNS sends an email&#x2F;SMS&#x2F;mobile push</li></ul><p>It is not possible to <a href="https://cloud.google.com/pubsub/docs/pull" target="_blank" rel="noopener">pull messages as with Google’s PubSub</a>.</p><p>A message can be any string that is not larger than 256 KB. If you want to filter messages, you have to add message attributes because SNS can not inspect the payload of messages.</p><p>It is important to note that messages might be delivered out of order and might be delivered more than once (at least once).</p><p>Operating SNS requires that you monitor the <code>NumberOfMessagesPublished</code> metric to be lower than <a href="https://docs.aws.amazon.com/general/latest/gr/sns.html#limits_sns_api_throttles_soft" target="_blank" rel="noopener">the soft limit</a>. The default soft limit is different for individual regions (in the range of 300 - 30,000). The hard limit is not disclosed.</p><p>SNS can be used to implement massive message fanout. A single topic can have millions of subscribers. Keep in mind that if a topic has zero subscribers, the message is sent into a black hole and disappears.</p><p>SNS pricing is based on the number of messages published and the number of messaged delivered. You pay $0.50 per 1 million requests. If your request is larger than 64 KB, you pay for each 64KB chunk of your request. No matter how small your request is, you always pay for one request. The maximum size of 256 KB will result in 4 requests on your bill. Depending on the subscriber type, you pay for deliveries as well. For example, an HTTP subscriber is charged $0.60 for 1 million messages while Lambda and SQS subscribers are free (Lambda and SQS charges apply).</p><p>SNS’s typical latency is under 30 ms. AWS <a href="https://aws.amazon.com/eventbridge/faqs/#Architecture_and_design" target="_blank" rel="noopener">recommends using SNS</a> for applications that react to high throughput or low latency messages.</p><h2 id="SNS-FIFO"><a href="#SNS-FIFO" class="headerlink" title="SNS FIFO"></a>SNS FIFO</h2><p>SNS FIFO topics provide strict ordering of messages. SNS FIFO also provides a way to ensure that a message is not created twice if a producer retries in case of failures. Therefore, exactly once delivery is possible.</p><p>But there are downsides:</p><ul><li>FIFO topics can only handle 300 messages per second or 3000 messages if you send messages in batches of ten but no more than 10 MB per second.</li><li>The only possible subscriber type for a FIFO topic is an SQS FIFO queue.</li><li>FIFO topics are more expensive. You pay per message and for the size of the payload.</li></ul><h2 id="EventBridge"><a href="#EventBridge" class="headerlink" title="EventBridge"></a>EventBridge</h2><p><a href="https://aws.amazon.com/eventbridge/" target="_blank" rel="noopener">Amazon EventBridge</a> (formerly CloudWatch Events) is a fully managed, publish&#x2F;subscribe system. The publisher sends a JSON event to an event bus. If you want to receive events, you create a rule. If the published event matches with a rule, the event is routed to up to five targets. More than 15 target types are supported (including SQS, SNS, Lambda). EventBridge guarantees are similar to SNS Standard: zero operational effort but no order guarantee and at least once delivery of messages.</p><p>EventBridge can be used to implement modest message fanout. A rule can trigger up to 5 targets. By default, you can create 300 rules per event bus (a soft limit that can be increased; the hard limit is not disclosed). Keep in mind that if an event does not match with a rule, it disappears unnoticed. You can optionally archive all events delivered to an event bus. Archived events can be replayed at any time. </p><p>EventBridge uses soft limits to throttle message producers. The <a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/cloudwatch-limits-eventbridge.html#putevents-limits" target="_blank" rel="noopener">default limits depend on the region</a>. For example, in <code>eu-west-1</code>, the default limit is 10,000 msg&#x2F;sec. There is no metric to monitor this resource constraint. The hard limit is not disclosed. Keep in mind that invocations are also throttled according to soft limits. Monitor the <code>Invocations</code> metric.</p><p>Typical latency is about half a second. (from the FAQS: “Note that this can vary”)</p><p>You pay $1.00 per 1 million events published. The same 64KB chunk logic as with SNS applies.</p><p>Want to learn more about the latest features of EventBridge? Watch the following video to larn about the latest features.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=-R8SpbCYMY0">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/-R8SpbCYMY0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p>CloudFormation code: <a href="/download/files/eventbridge-demo.zip">eventbridge-demo.zip</a></p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Use the following comparison table to support your decision.</p><div class="table-responsive"><table class="table table-striped"><thead><tr><th></th><th>Amazon SNS Standard</th><th>Amazon SNS FIFO</th><th>Amazon EventBridge (formerly CloudWatch Events)</th></tr></thead><tbody><tr><td>Scaling</td><td><p>not disclosed (default soft limit depends on region; e.g., 9000 msg&#x2F;sec in eu-west-1)</p></td><td><p>3000 msg&#x2F;sec (batch write) or 10 MB per second</p></td><td><p>not disclosed (default soft limit depends on region; e.g., 10,000 msg&#x2F;sec in eu-west-1)</p></td></tr><tr><td>Max. message size</td><td><p>256 KB</p></td><td><p>256 KB</p></td><td><p>256 KB</p></td></tr><tr><td>Persistence</td><td><p>no</p></td><td><p>no</p></td><td><p>archiving is possible</p></td></tr><tr><td>Replication</td><td><p>Multi-AZ</p></td><td><p>Multi-AZ</p></td><td><p>Multi-AZ</p></td></tr><tr><td>Order guarantee</td><td><p>no</p></td><td><p>yes</p></td><td><p>no</p></td></tr><tr><td>Delivery guarantee</td><td><p>at least once</p></td><td><p><a href="https://docs.aws.amazon.com/sns/latest/dg/fifo-message-dedup.html" target="_blank" rel="noopener">exactly-once possible</a></p></td><td><p>at least once</p></td></tr><tr><td>Pricing</td><td><p>per message</p></td><td><p>per message</p></td><td><p>per message</p></td></tr><tr><td>Protocols</td><td><p>AWS Rest API</p></td><td><p>AWS Rest API</p></td><td><p>AWS Rest API</p></td></tr><tr><td>AWS Integrations</td><td><p>Lambda, SQS, webhook</p></td><td><p>SQS FIFO</p></td><td><p>Lambda, SQS, SNS, and <a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-targets.html" target="_blank" rel="noopener">many more</a></p></td></tr><tr><td>License</td><td><p>AWS only</p></td><td><p>AWS only</p></td><td><p>AWS only</p></td></tr><tr><td>Encryption at rest</td><td><p>yes</p></td><td><p>yes</p></td><td><p>yes</p></td></tr><tr><td>Encryption in transit</td><td><p>yes</p></td><td><p>yes</p></td><td><p>yes</p></td></tr></tbody></table></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Comparing API Gateways on AWS</title>
      <link>https://cloudonaut.io/comparing-api-gateways-on-aws/</link>
      <description>
        <![CDATA[<p>Of the different API gateways offered by AWS, which option fits your needs? An API Gateway is “a server that acts as an API front-end, re]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/apigateway/">apigateway</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/alb/">alb</category>
      <category domain="https://cloudonaut.io/tag/appsync/">appsync</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/comparing-api-gateways-on-aws/</guid>
      <pubDate>Wed, 11 Nov 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Of the different API gateways offered by AWS, which option fits your needs? An API Gateway is “a server that acts as an API front-end, receives API requests, enforces throttling and security policies, passes requests to the back-end service, and then passes the response back to the requester.”</p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><p>AWS offers five different types of API Gateways:</p><ul><li>API Gateway REST API</li><li>API Gateway HTTP API</li><li>API Gateway WebSocket API</li><li>AppSync</li><li>Application Load Balancer (ALB)</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/11/comparison@730w.webp 730w, /images/2020/11/comparison@730w2x.webp 1460w, /images/2020/11/comparison@610w.webp 610w, /images/2020/11/comparison@610w2x.webp 1220w, /images/2020/11/comparison@450w.webp 450w, /images/2020/11/comparison@450w2x.webp 900w, /images/2020/11/comparison@330w.webp 330w, /images/2020/11/comparison@330w2x.webp 660w, /images/2020/11/comparison@545w.webp 545w, /images/2020/11/comparison@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/11/comparison@730w.jpg 730w, /images/2020/11/comparison@730w2x.jpg 1460w, /images/2020/11/comparison@610w.jpg 610w, /images/2020/11/comparison@610w2x.jpg 1220w, /images/2020/11/comparison@450w.jpg 450w, /images/2020/11/comparison@450w2x.jpg 900w, /images/2020/11/comparison@330w.jpg 330w, /images/2020/11/comparison@330w2x.jpg 660w, /images/2020/11/comparison@545w.jpg 545w, /images/2020/11/comparison@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/11/comparison.jpg" alt="Comparing API Gateways on AWS" title="Comparing API Gateways on AWS"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/32-comparing-api-gateways-on-aws/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>Typically, an API Gateway forwards requests to Lambda, DynamoDB, a load balancer (ELB), or even on-premises or third-party endpoints.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/11/apigw-overview@730w.webp 730w, /images/2020/11/apigw-overview@730w2x.webp 1460w, /images/2020/11/apigw-overview@610w.webp 610w, /images/2020/11/apigw-overview@610w2x.webp 1220w, /images/2020/11/apigw-overview@450w.webp 450w, /images/2020/11/apigw-overview@450w2x.webp 900w, /images/2020/11/apigw-overview@330w.webp 330w, /images/2020/11/apigw-overview@330w2x.webp 660w, /images/2020/11/apigw-overview@545w.webp 545w, /images/2020/11/apigw-overview@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/11/apigw-overview@730w.png 730w, /images/2020/11/apigw-overview@730w2x.png 1460w, /images/2020/11/apigw-overview@610w.png 610w, /images/2020/11/apigw-overview@610w2x.png 1220w, /images/2020/11/apigw-overview@450w.png 450w, /images/2020/11/apigw-overview@450w2x.png 900w, /images/2020/11/apigw-overview@330w.png 330w, /images/2020/11/apigw-overview@330w2x.png 660w, /images/2020/11/apigw-overview@545w.png 545w, /images/2020/11/apigw-overview@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/11/apigw-overview.png" alt="What's an API Gateway all about?" title="What's an API Gateway all about?"></picture></p><h2 id="API-Gateway-REST-API"><a href="#API-Gateway-REST-API" class="headerlink" title="API Gateway REST API"></a>API Gateway REST API</h2><p>The most mature API gateway option is called API Gateway REST API which is the full-feature flagship service to build REST APIs and has been available since 2015. As implied by the name, the service is about making RESTful web services, probably the most popular API schema those days.</p><p>A feature that makes the API Gateway REST API service stand out from the crowd is user&#x2F;tenant-based throttling. The feature allows you to limit the number of requests per user or tenant. A functionality that is important for building APIs for a Software-as-a-Service business or any other scenario where 3rd parties interact with your API.</p><p>One point of criticism on API Gateway REST API is the mediocre user experience for developers. Configuring an API Gateway is complicated and cumbersome because of its feature-richness and flexibility.</p><p>In general, I recommend API Gateway REST API for Serverless applications consumed by 3rd parties as well as for enterprise scenarios.</p><h2 id="API-Gateway-HTTP-API"><a href="#API-Gateway-HTTP-API" class="headerlink" title="API Gateway HTTP API"></a>API Gateway HTTP API</h2><p>AWS announced HTTP APIs as a modern alternative to REST APIs. Despite the name, API Gateway HTTP API is also about RESTful APIs. AWS promises to deliver lower latency, reduced costs, and improved user experience with HTTP APIs.</p><p>However, API Gateway HTTP API is a very new service which was announced in December 2019 and has been generally available since March 2020. As usual, new AWS services are a minimum viable product, which means important features are missing and will probably be delivered step by step in the coming years.</p><p>I do recommend API Gateway HTTP API for prototyping. I’d be careful with using HTTP APIs for production-grade workloads due to missing user&#x2F;tenant-based throttling capabilities.</p><h2 id="API-Gateway-WebSocket-API"><a href="#API-Gateway-WebSocket-API" class="headerlink" title="API Gateway WebSocket API"></a>API Gateway WebSocket API</h2><p>Nowadays, many architectures utilize the request&#x2F;response model. However, in many scenarios an event-driven approach is more promising. API Gateway WebSocket API offers an event-driven API that allows you to send messages from the client to the server but also the other way around.</p><p>The client establishes a connection to the API Gateway, while both sides aim to keep the connection alive. When the client sends an event over the WebSocket, the API Gateway forwards the event to a backend – for example a Lambda function. Whenever needed, it is possible to send API Gateway a message for the connected client as well.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/11/apigw-websockets@730w.webp 730w, /images/2020/11/apigw-websockets@730w2x.webp 1460w, /images/2020/11/apigw-websockets@610w.webp 610w, /images/2020/11/apigw-websockets@610w2x.webp 1220w, /images/2020/11/apigw-websockets@450w.webp 450w, /images/2020/11/apigw-websockets@450w2x.webp 900w, /images/2020/11/apigw-websockets@330w.webp 330w, /images/2020/11/apigw-websockets@330w2x.webp 660w, /images/2020/11/apigw-websockets@545w.webp 545w, /images/2020/11/apigw-websockets@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/11/apigw-websockets@730w.png 730w, /images/2020/11/apigw-websockets@730w2x.png 1460w, /images/2020/11/apigw-websockets@610w.png 610w, /images/2020/11/apigw-websockets@610w2x.png 1220w, /images/2020/11/apigw-websockets@450w.png 450w, /images/2020/11/apigw-websockets@450w2x.png 900w, /images/2020/11/apigw-websockets@330w.png 330w, /images/2020/11/apigw-websockets@330w2x.png 660w, /images/2020/11/apigw-websockets@545w.png 545w, /images/2020/11/apigw-websockets@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/11/apigw-websockets.png" alt="Realtime API with Websockets" title="Realtime API with Websockets"></picture></p><p>The API Gateway WebSocket API is a perfect choice for scenarios where minimizing latency is critical or whenever you want to push events from the server to the client.</p><h2 id="AppSync"><a href="#AppSync" class="headerlink" title="AppSync"></a>AppSync</h2><p>RESTful APIs have been popular for years. However, more and more architects are looking into GraphQL. A GraphQL API is the perfect fit for mobile applications, because of two main design goals:</p><ul><li>The client asks only for the data that is needed. The API won’t return any useless attributes. Doing so reduces the amount of data that needs to be transferred.</li><li>The client bundles multiple queries in a single request. For example, by specifying nested queries. Doing so reduces the amount of required requests.</li><li>An API specification is built in automatically, defining the possible API queries in every detail. Doing so allows you to validate requests or to generate SDKs automatically.</li></ul><p>AWS AppSync is a managed service to deploy GraphQL APIs. The service has been around since 2018. Personally, I do like the experience of building APIs with AppSync. The main reason for that, is everything starts with a GraphQL API specification. Everything else is built around that contract between client and server.</p><p>A typical use case for a GraphQL is to unify access to different backend systems (e.g., legacy applications, microservices, etc.). AppSync is definitely also a good choice for building top-notch Serverless applications.</p><h2 id="Application-Load-Balancer-ALB"><a href="#Application-Load-Balancer-ALB" class="headerlink" title="Application Load Balancer (ALB)"></a>Application Load Balancer (ALB)</h2><p>Strictly speaking, an Application Load Balancer (ALB) is not an API Gateway. However, the ALB provides similar functionality. Compared to the alternatives an ALB is cost effective and very simple to use.</p><p>However, an ALB does not cover all features of a typical API Gateway. Most importantly, it is not possible to transform requests or responses. The possibilities for authentication are also limited.</p><p>Unfortunately, comparing costs between the ALB and the other options is not that simple, as their pricing models are completely different. Roughly speaking, an ALB should be more cost effective for high volume but steady workloads.</p><h2 id="Comparison"><a href="#Comparison" class="headerlink" title="Comparison"></a>Comparison</h2><p>Concluding the following table compares the different API Gateway options on AWS. As always, there is no one size fits all solution.</p><div class="table-responsive"><table class="table table-striped table-sm"><thead><tr><th></th><th>API Gateway REST API</th><th>API Gateway HTTP API</th><th>API Gateway WebSocket API</th><th>AppSync</th><th>ALB</th><th>ALB WebSocket</th></tr></thead><tbody><tr><td>Type</td><td><p>REST API</p></td><td><p>REST API</p></td><td><p>Websocket</p></td><td><p>GraphQL</p></td><td><p>REST API</p></td><td><p>Websocket</p></td></tr><tr><td>HTTPS/TLS</td><td><p>✅</p></td><td><p>✅</p></td><td><p>✅</p></td><td><p>✅</p></td><td><p>✅</p></td><td><p>✅</p></td></tr><tr><td>Backends</td><td><p>✅ Lambda<br>✅ Public endpoint<br>✅ Private endpoint<br>✅ AWS Service APIs</p></td><td><p>✅ Lambda<br>✅ Public endpoint<br>✅ Private endpoint<br>⚠️ AWS Service APIs</p></td><td><p>✅  Lambda<br>✅ Public endpoint<br>✅ Private endpoint<br>✅ AWS Service APIs</p></td><td><p>✅ Lambda<br>✅  Public endpoint<br>❌ Private endpoint<br>✅ AWS Service APIs</p></td><td><p>✅ Lambda<br>❌ Public endpoint<br>✅ Private endpoint<br>❌ AWS Service APIs</p></td><td><p>❌ Lambda<br>❌ Public endpoint<br>✅ Private endpoint<br>❌ AWS Service APIs</p></td></tr><tr><td>Costs</td><td><p>💰💰</p></td><td><p>💰</p></td><td><p>💰</p></td><td><p>💰💰💰</p></td><td><p>💰💰</p></td><td><p>💰💰</p></td></tr><tr><td>Maturity</td><td><p>⭐️⭐️⭐️⭐️⭐️</p></td><td><p>⭐️⭐️</p></td><td><p>⭐️⭐️⭐️⭐️</p></td><td><p>⭐️⭐️⭐️⭐️</p></td><td><p>⭐️⭐️⭐️⭐️</p></td><td><p>⭐️⭐️⭐️⭐️</p></td></tr><tr><td>Authentication</td><td><p>✅ IAM<br>✅ API Key<br>✅ OpenID Connect<br>✅ SAML<br>✅ Social<br>✅ Custom</p></td><td><p>✅ IAM<br>❌ API Key<br>✅ OpenID Connect<br>❌ SAML<br>❌ Social<br>✅ Custom</p></td><td><p>✅ IAM<br>❌ API Key<br>❌ OpenID Connect<br>❌ SAML<br>❌ Social<br>✅ Custom</p></td><td><p>✅ IAM<br>✅ API Key<br>✅ OpenID Connect<br>✅ SAML<br>✅ Social<br>✅ Custom</p></td><td><p>❌ IAM<br>❌ API Key<br>✅ OpenID Connect<br>✅ SAML<br>✅ Social<br>❌ Custom</p></td><td><p>❌ IAM<br>❌ API Key<br>✅ OpenID Connect<br>✅ SAML<br>✅ Social<br>❌ Custom</p></td></tr><tr><td>Tenant-based Throttling</td><td><p>✅</p></td><td><p>❌</p></td><td><p>❌</p></td><td><p>❌</p></td><td><p>❌</p></td><td><p>❌</p></td></tr><tr><td>Request Validation</td><td><p>✅</p></td><td><p>❌</p></td><td><p>✅</p></td><td><p>✅</p></td><td><p>❌</p></td><td><p>❌</p></td></tr><tr><td>Req./Res. Transformation</td><td><p>✅</p></td><td><p>❌</p></td><td><p>✅</p></td><td><p>✅</p></td><td><p>❌</p></td><td><p>❌</p></td></tr><tr><td>Endpoints</td><td><p>✅ Public<br>✅ Private</p></td><td><p>✅ Public<br>❌ Private</p></td><td><p>✅ Public<br>❌ Private</p></td><td><p>✅ Public<br>❌ Private</p></td><td><p>✅  Public<br>✅ Private</p></td><td><p>✅ Public<br>✅ Private</p></td></tr><tr><td>WAF</td><td><p>✅</p></td><td><p>❌</p></td><td><p>❌</p></td><td><p>✅</p></td><td><p>✅</p></td><td><p>✅</p></td></tr></tbody></table></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Transition to IMDSv2 on EC2 - Introduction, Preparation, Pitfalls</title>
      <link>https://cloudonaut.io/transition-to-imdsv2-on-ec2/</link>
      <description>
        <![CDATA[<p>IMDSv2 can improve EC2 security. For a couple of weeks, AWS Foundational Security Best Practices recommends that EC2 instances use IMDSv2]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <guid isPermaLink="true">https://cloudonaut.io/transition-to-imdsv2-on-ec2/</guid>
      <pubDate>Tue, 10 Nov 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>IMDSv2 can improve EC2 security. For a couple of weeks, AWS Foundational Security Best Practices recommends that EC2 instances use IMDSv2 (<a href="https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-ec2-8" target="_blank" rel="noopener">control EC2.8</a>).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/11/imdsv2@730w.webp 730w, /images/2020/11/imdsv2@730w2x.webp 1460w, /images/2020/11/imdsv2@610w.webp 610w, /images/2020/11/imdsv2@610w2x.webp 1220w, /images/2020/11/imdsv2@450w.webp 450w, /images/2020/11/imdsv2@450w2x.webp 900w, /images/2020/11/imdsv2@330w.webp 330w, /images/2020/11/imdsv2@330w2x.webp 660w, /images/2020/11/imdsv2@545w.webp 545w, /images/2020/11/imdsv2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/11/imdsv2@730w.jpg 730w, /images/2020/11/imdsv2@730w2x.jpg 1460w, /images/2020/11/imdsv2@610w.jpg 610w, /images/2020/11/imdsv2@610w2x.jpg 1220w, /images/2020/11/imdsv2@450w.jpg 450w, /images/2020/11/imdsv2@450w2x.jpg 900w, /images/2020/11/imdsv2@330w.jpg 330w, /images/2020/11/imdsv2@330w2x.jpg 660w, /images/2020/11/imdsv2@545w.jpg 545w, /images/2020/11/imdsv2@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/11/imdsv2.jpg" alt="Transition to IMDSv2 on EC2" title="Transition to IMDSv2 on EC2"></picture></p><p>This video explains why IMDSv2 is useful and what attacks it protects you against, including a live demo. I also highlight a pitfall with Docker containers in a demo. Last but not least, I show you the preparation steps for a smooth transition.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=bi3bIs92xE0">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/bi3bIs92xE0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Tracing an HTTP request</title>
      <link>https://cloudonaut.io/tracing-an-http-request/</link>
      <description>
        <![CDATA[<p>What to do when customers complain about high latencies or server-side errors? How to find out which part of your infrastructure is causi]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudfront/">cloudfront</category>
      <category domain="https://cloudonaut.io/tag/alb/">alb</category>
      <category domain="https://cloudonaut.io/tag/athena/">athena</category>
      <guid isPermaLink="true">https://cloudonaut.io/tracing-an-http-request/</guid>
      <pubDate>Wed, 04 Nov 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>What to do when customers complain about high latencies or server-side errors? How to find out which part of your infrastructure is causing trouble? Is something wrong with CloudFront, the ELB, or your web application?</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/11/tracing@730w.webp 730w, /images/2020/11/tracing@730w2x.webp 1460w, /images/2020/11/tracing@610w.webp 610w, /images/2020/11/tracing@610w2x.webp 1220w, /images/2020/11/tracing@450w.webp 450w, /images/2020/11/tracing@450w2x.webp 900w, /images/2020/11/tracing@330w.webp 330w, /images/2020/11/tracing@330w2x.webp 660w, /images/2020/11/tracing@545w.webp 545w, /images/2020/11/tracing@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/11/tracing@730w.jpg 730w, /images/2020/11/tracing@730w2x.jpg 1460w, /images/2020/11/tracing@610w.jpg 610w, /images/2020/11/tracing@610w2x.jpg 1220w, /images/2020/11/tracing@450w.jpg 450w, /images/2020/11/tracing@450w2x.jpg 900w, /images/2020/11/tracing@330w.jpg 330w, /images/2020/11/tracing@330w2x.jpg 660w, /images/2020/11/tracing@545w.jpg 545w, /images/2020/11/tracing@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/11/tracing.jpg" alt="Tracing an HTTP request" title="Tracing an HTTP request"></picture></p><p>Our video demonstrates how to use request IDs, also called tracing IDs, to debug your systems. You will learn how to use Athena to combine access logs from the ALB stored on S3 and an NGINX server stored on CloudWatch Logs.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=6QEsQJ9AP1I">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/6QEsQJ9AP1I" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Getting started with IPv6 on AWS</title>
      <link>https://cloudonaut.io/getting-started-with-ipv6-on-aws/</link>
      <description>
        <![CDATA[<p>In mid-2019, <a href="http://www.southgatearc.org/news/2020/october/sale-of-amateur-radio-amprnet-tcp-ip-addresses.htm" target="_blank" r]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/getting-started-with-ipv6-on-aws/</guid>
      <pubDate>Wed, 28 Oct 2020 17:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>In mid-2019, <a href="http://www.southgatearc.org/news/2020/october/sale-of-amateur-radio-amprnet-tcp-ip-addresses.htm" target="_blank" rel="noopener">AWS paid ~$108M to access 4 million IPv4 addresses from Amateur Radio Digital Communications</a> - $27 per IP address. The reason why AWS spends so much money on IPv4 addresses is simple: There are no free IPv4 addresses that regional internet registries (such as ICANN) can assign anymore. IPv4 addresses, such as 143.204.94.123, have become a scarce resource. That’s one motivation to move on to the successor: IPv6. The notable difference for many of us is that IPv6 addresses are much longer. Instead of the 32-bit IPv4 addresses, IPv6 uses 128-bit addresses.</p><p>With 32 bits, the IP address space contains 2<sup>32</sup> or 4.3 billion possible addresses. Given that 7.5 billion people live on earth, you get the problem. The mind-blowing fact is that going to 128 bit expands the IP address space to 2<sup>128</sup> or 340 sextillions. The number is too big to imagine it. The bottom line is, IPv6 overcomes the problem of IPv4 address scarcity. But that’s not the only benefit.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/network@730w.webp 730w, /images/2020/10/network@730w2x.webp 1460w, /images/2020/10/network@610w.webp 610w, /images/2020/10/network@610w2x.webp 1220w, /images/2020/10/network@450w.webp 450w, /images/2020/10/network@450w2x.webp 900w, /images/2020/10/network@330w.webp 330w, /images/2020/10/network@330w2x.webp 660w, /images/2020/10/network@545w.webp 545w, /images/2020/10/network@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/network@730w.jpg 730w, /images/2020/10/network@730w2x.jpg 1460w, /images/2020/10/network@610w.jpg 610w, /images/2020/10/network@610w2x.jpg 1220w, /images/2020/10/network@450w.jpg 450w, /images/2020/10/network@450w2x.jpg 900w, /images/2020/10/network@330w.jpg 330w, /images/2020/10/network@330w2x.jpg 660w, /images/2020/10/network@545w.jpg 545w, /images/2020/10/network@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/network.jpg" alt="Network" title="Network"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/30-getting-started-with-ipv6-on-aws/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>Much of the improvements of IPv6 will never be noticed by most of us - including myself. And that’s fine. Isn’t it great that AWS solves all those challenges for us, and we can just make use of IPv6? The rest of this post outlines your IPv6 on AWS journey.</p><h2 id="Public-services-first"><a href="#Public-services-first" class="headerlink" title="Public services first"></a>Public services first</h2><p>Before you think about designing your IPv6 network, you should enable IPv6 alongside IPv4 on endpoints accessed by end-users. <a href="https://www.google.com/intl/en/ipv6/statistics.html#tab=ipv6-adoption" target="_blank" rel="noopener">Around 30% of Internet traffic</a> is already IPv6 traffic. The IPv6 deployment progress is mainly driven by mobile devices and <a href="https://www.google.com/intl/en/ipv6/statistics.html#tab=per-country-ipv6-adoption" target="_blank" rel="noopener">varies heavily between countries</a>.</p><table class="table table-striped table-responsive"><thead><tr><th>Service</th><th>Ipv6 support?</th><th>Implementation Detail</th></tr></thead><tbody><tr><td>Route53</td><td>yes, by default</td><td>-</td></tr><tr><td>CloudFront</td><td>yes, optional</td><td>Don&#39;t forget to add a DNS AAAA Alias record.</td></tr><tr><td>S3</td><td>yes, optional</td><td><a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/ipv6-access.html">Use dual-stack endpoints</a></td></tr><tr><td>API Gateway REST EDGE</td><td>no</td><td>-</td></tr><tr><td>API Gateway REST REGIONAL</td><td>no, workaround available</td><td>Put an IPv6 enabled CloudFront distribution in front of the regional API Gateway</td></tr><tr><td>AppSync</td><td>no</td><td>-</td></tr><tr><td>IoT Core</td><td>yes, by default</td><td>-</td></tr><tr><td>Global Accelerator</td><td>no</td><td>-</td></tr></tbody></table><blockquote><p>CloudFront will always communicate to the origin in IPv4!</p></blockquote><p>You might ask yourself why load balancers are missing on this list. The reason is that load balancers run in a VPC. You need an IPv6 enabled VPC before you can enable IPv6 on a load balancer. The next section will cover this.</p><h2 id="Enable-IPv6-in-your-VPC"><a href="#Enable-IPv6-in-your-VPC" class="headerlink" title="Enable IPv6 in your VPC"></a>Enable IPv6 in your VPC</h2><p>First of all, there is no IPv6-only VPC on AWS. A VPC is always IPv4 enabled, but you can optionally enable IPv6 (dual-stack). When you do so, AWS assigns a &#x2F;56 block of IPv6 Global Unicast Addresses (GUA) to your VPC - you can bring your own block as well. A GUA is like a public IPv4 address. You can assign each subnet in your VPC a &#x2F;64 sub-block. <strong>There is no VPC&#x2F;subnet size planing anymore!</strong></p><p>To connect to the Internet, you have two options:</p><ul><li>Internet Gateway: Resources can send and receive packets from the Internet.</li><li>Egress-only internet gateways: Resources can send packets to the Internet.</li></ul><p><strong>There are no NAT Gateways&#x2F;Instances anymore!</strong> If you want to talk to the Internet over IPv6, you need an IPv6 GUA. End-to-end connectivity is the goal of IPv6.</p><p><strong>There are no private IPv6 addresses in an IPv6 VPC</strong>. You could use Link Local Addresses to talk to other ENIs on the same subnet. Link Local IPv6 traffic is only allowed if the ENI has an IPv6 GUA.</p><p>A typical Public&#x2F;Private VPC setup with IPv6 support can look like this:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/vpc-ipv6@730w.webp 730w, /images/2020/10/vpc-ipv6@730w2x.webp 1460w, /images/2020/10/vpc-ipv6@610w.webp 610w, /images/2020/10/vpc-ipv6@610w2x.webp 1220w, /images/2020/10/vpc-ipv6@450w.webp 450w, /images/2020/10/vpc-ipv6@450w2x.webp 900w, /images/2020/10/vpc-ipv6@330w.webp 330w, /images/2020/10/vpc-ipv6@330w2x.webp 660w, /images/2020/10/vpc-ipv6@545w.webp 545w, /images/2020/10/vpc-ipv6@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/vpc-ipv6@730w.png 730w, /images/2020/10/vpc-ipv6@730w2x.png 1460w, /images/2020/10/vpc-ipv6@610w.png 610w, /images/2020/10/vpc-ipv6@610w2x.png 1220w, /images/2020/10/vpc-ipv6@450w.png 450w, /images/2020/10/vpc-ipv6@450w2x.png 900w, /images/2020/10/vpc-ipv6@330w.png 330w, /images/2020/10/vpc-ipv6@330w2x.png 660w, /images/2020/10/vpc-ipv6@545w.png 545w, /images/2020/10/vpc-ipv6@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/vpc-ipv6.png" alt="Topology of VPC with IPv6" title="Topology of VPC with IPv6"></picture></p><p>After the VPC is IPv6 ready, ENIs can get an IPv6 GUA assigned besides the IPv4 private (and public) address. By default, DHCPv6 is used to allocated IPv6 addresses. You can manually specify IPv6 addresses as well (not recommended). <strong>Security Groups are still used to protect ENIs inbound and outbound and are the way to implement “defense in depth”.</strong></p><p>Want to learn more about IPv6 on AWS? The following video shows how to configure a VPC and EC2 for the use with IPv6.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=cBynCyJ8iMY">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/cBynCyJ8iMY" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="Load-balancers"><a href="#Load-balancers" class="headerlink" title="Load balancers"></a>Load balancers</h2><p>As soon as your VPC is IPv6 enabled, you can deploy your public ALBs with IPv6 support as well.</p><table><thead><tr><th>Load Balancer Type</th><th>Scheme</th><th>Ipv6 support?</th><th>Implementation Detail</th></tr></thead><tbody><tr><td>Application</td><td>internet-facing</td><td>yes, optional</td><td>Don’t forget to add an DNS AAAA Alias record.</td></tr><tr><td>Application</td><td>internal</td><td>no</td><td></td></tr><tr><td>Network</td><td>internet-facing</td><td>yes, optional</td><td>Don’t forget to add an DNS AAAA Alias record.</td></tr><tr><td>Network</td><td>internal</td><td>no</td><td></td></tr><tr><td>Classic</td><td>internet-facing</td><td>no</td><td></td></tr><tr><td>Classic</td><td>internet</td><td>no</td><td></td></tr></tbody></table><blockquote><p>ALB will always communicate to the target in IPv4!</p></blockquote><h2 id="Workloads"><a href="#Workloads" class="headerlink" title="Workloads"></a>Workloads</h2><p>IPv6 is supported on all current generation <strong>EC2</strong> instance types and the C3, R3, and I2 previous generation instance types. Depending on your subnet configuration (Auto-assign IPv6), an EC2 instance will automatically receive an IPv6 GUA, or you have to request one explicitly.</p><blockquote><p>Unfortunately, CloudFormation does not allow us to configure a subnet with Auto-assign IPv4 and Auto-assign IPv6). But it is possible with the CLI, UI, and SDKs.</p></blockquote><p><strong>RDS</strong> does not support IPv6.</p><p><strong>Fargate</strong> does support IPv6.</p><p><strong>Lambda</strong> does not support IPv6.</p><h2 id="AWS-API"><a href="#AWS-API" class="headerlink" title="AWS API"></a>AWS API</h2><p>The AWS API is IPv4-only. amazon.com is IPv4-only as well :)</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>IPv6 is coming. At some point in time, you have to start your journey towards IPv6. For now, I recommend enabling IPv6 for the endpoints that are used by your end-users.</p><p>There is not much benefit in enabling IPv6 in your VPC yet. The only exception is if you want IPv6 support for your ALB. As a workaround, you can front the IPv4 ALB with CloudFront to offer IPv6 to your end-users.</p><h2 id="Learn-more"><a href="#Learn-more" class="headerlink" title="Learn more"></a>Learn more</h2><p>I recommend a re:Invent talk from 2017: <a href="https://youtu.be/GE_FqZ-XLR0?t=1620" target="_blank" rel="noopener">IPv6 in the Cloud: Protocol and AWS Service Overview (NET202)</a>. Besides that, I read IPv6 Essentials from Silvia Hagen (O’Reilly), which goes into the details (far beyond what you need to get IPv6 working on AWS).</p><p><em>Thanks to <a href="https://x.com/hoegertn" target="_blank" rel="noopener">Thorsten Höger</a> for reviewing this article.</em></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to enable CORS on API Gateway with Lambda proxy integration?</title>
      <link>https://cloudonaut.io/how-to-enable-cors-on-api-gateway-with-lambda-proxy-integration/</link>
      <description>
        <![CDATA[<p>When building single-page applications (SPA), you will sooner or later stumble upon <a href="https://developer.mozilla.org/en-US/docs/Web]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/apigateway/">apigateway</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-enable-cors-on-api-gateway-with-lambda-proxy-integration/</guid>
      <pubDate>Wed, 21 Oct 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>When building single-page applications (SPA), you will sooner or later stumble upon <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" target="_blank" rel="noopener">Cross-Origin Resource Sharing (CORS)</a>. In short, a browser does only allow requests to the same same origin (domain, protocol and port), that was used for the initial request by default.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/gateway@730w.webp 730w, /images/2020/10/gateway@730w2x.webp 1460w, /images/2020/10/gateway@610w.webp 610w, /images/2020/10/gateway@610w2x.webp 1220w, /images/2020/10/gateway@450w.webp 450w, /images/2020/10/gateway@450w2x.webp 900w, /images/2020/10/gateway@330w.webp 330w, /images/2020/10/gateway@330w2x.webp 660w, /images/2020/10/gateway@545w.webp 545w, /images/2020/10/gateway@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/gateway@730w.jpg 730w, /images/2020/10/gateway@730w2x.jpg 1460w, /images/2020/10/gateway@610w.jpg 610w, /images/2020/10/gateway@610w2x.jpg 1220w, /images/2020/10/gateway@450w.jpg 450w, /images/2020/10/gateway@450w2x.jpg 900w, /images/2020/10/gateway@330w.jpg 330w, /images/2020/10/gateway@330w2x.jpg 660w, /images/2020/10/gateway@545w.jpg 545w, /images/2020/10/gateway@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/gateway.jpg" alt="How to enable CORS on API Gateway with Lambda proxy integration?" title="How to enable CORS on API Gateway with Lambda proxy integration?"></picture></p><p>However, a typical Serverless application uses CloudFront and S3 to deliver the static files like <code>.html</code>, <code>.css</code>, and <code>.js</code> and an API Gateway acting as the front door for the backend. In that case, the hostname to access CloudFront - for example, <code>myapp.com</code> - is different than the hostname to access the API Gateway - for example, <code>api.myapp.com</code>. Therefore, a browser would block requests to <code>api.myapp.com</code>. To allow the SPA to access the API Gateway, you need to implement CORS on the backend. This article explains how to do so when using the API Gateway with Lambda proxy integration.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/apigw-cors@730w.webp 730w, /images/2020/10/apigw-cors@730w2x.webp 1460w, /images/2020/10/apigw-cors@610w.webp 610w, /images/2020/10/apigw-cors@610w2x.webp 1220w, /images/2020/10/apigw-cors@450w.webp 450w, /images/2020/10/apigw-cors@450w2x.webp 900w, /images/2020/10/apigw-cors@330w.webp 330w, /images/2020/10/apigw-cors@330w2x.webp 660w, /images/2020/10/apigw-cors@545w.webp 545w, /images/2020/10/apigw-cors@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/apigw-cors@730w.png 730w, /images/2020/10/apigw-cors@730w2x.png 1460w, /images/2020/10/apigw-cors@610w.png 610w, /images/2020/10/apigw-cors@610w2x.png 1220w, /images/2020/10/apigw-cors@450w.png 450w, /images/2020/10/apigw-cors@450w2x.png 900w, /images/2020/10/apigw-cors@330w.png 330w, /images/2020/10/apigw-cors@330w2x.png 660w, /images/2020/10/apigw-cors@545w.png 545w, /images/2020/10/apigw-cors@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/apigw-cors.png" alt="CORS required for Serverless single-page applications with CloudFront and API Gateway" title="CORS required for Serverless single-page applications with CloudFront and API Gateway"></picture></p><p>The <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html" target="_blank" rel="noopener">proxy integration</a> is an easy way to configure the API Gateway. It starts with the gateway forwarding all parts of an HTTP request to the Lambda function. Next, the Lambda function returns all details of an HTTP response. No complicated configuration and data mapping needed on the API Gateway.</p><p>Three steps are necessary to enable CORS for the backend when using the Lambda proxy integration:</p><ol><li>Implement adding CORS headers with the Lambda function</li><li>Add static response for OPTIONS requests</li><li>Add CORS headers to server-side errors</li></ol><p>You will learn more about those three steps in the following.</p><h2 id="Implement-adding-CORS-headers-with-the-Lambda-function"><a href="#Implement-adding-CORS-headers-with-the-Lambda-function" class="headerlink" title="Implement adding CORS headers with the Lambda function"></a>Implement adding CORS headers with the Lambda function</h2><p>When configuring the proxy integration on the API Gateway, the <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format" target="_blank" rel="noopener">Lambda function needs to return a response in a specific format</a>. The following code snippet shows how to add the necessary CORS header <code>Access-Control-Allow-Origin</code>.</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line">exports.handler = <span class="keyword">async</span> (<span class="keyword">event</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">let</span> data = &#123;&#125;;</span><br><span class="line">  <span class="keyword">let</span> res =  &#123;</span><br><span class="line">    statusCode: <span class="number">200</span>,</span><br><span class="line">    headers: &#123; </span><br><span class="line">      <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;Access-Control-Allow-Origin&#x27;</span>: <span class="string">&#x27;*&#x27;</span> <span class="comment">// replace with hostname of frontend (CloudFront)</span></span><br><span class="line">    &#125;,</span><br><span class="line">    body: JSON.stringify(data)</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">return</span> res;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>Remember to add the <code>Access-Control-Allow-Origin</code> to all Lambda functions.</p><h2 id="Add-static-response-for-OPTIONS-requests"><a href="#Add-static-response-for-OPTIONS-requests" class="headerlink" title="Add static response for OPTIONS requests"></a>Add static response for OPTIONS requests</h2><p>Additionally, the browser sends a so called CORS preflight requests (HTTP method <code>OPTIONS</code>) to each API resource to check for CORS configuration. Therefore, you need to make sure that the API Gateway answers <code>OPTIONS</code> requests for all resources that should be accessed by the SPA. To simplify the configuration, creating a proxy resource allows you to add an <code>OPTIONS</code> method to all resources at once. The following Terraform configuration snippet shows how to create a proxy resource to answer the  <code>OPTIONS</code> requests for all API resources.</p><figure class="highlight applescript"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_api_gateway_rest_api&quot;</span> <span class="string">&quot;backend&quot;</span> &#123;</span><br><span class="line">  <span class="built_in">name</span> = <span class="string">&quot;myapp&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_api_gateway_resource&quot;</span> <span class="string">&quot;cors&quot;</span> &#123;</span><br><span class="line">  rest_api_id = aws_api_gateway_rest_api.backend.<span class="built_in">id</span></span><br><span class="line">  parent_id   = aws_api_gateway_rest_api.backend.root_resource_id</span><br><span class="line">  path_part   = <span class="string">&quot;&#123;cors+&#125;&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_api_gateway_method&quot;</span> <span class="string">&quot;cors&quot;</span> &#123;</span><br><span class="line">  rest_api_id   = aws_api_gateway_rest_api.backend.<span class="built_in">id</span></span><br><span class="line">  resource_id   = aws_api_gateway_resource.cors.<span class="built_in">id</span></span><br><span class="line">  http_method   = <span class="string">&quot;OPTIONS&quot;</span></span><br><span class="line">  authorization = <span class="string">&quot;NONE&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_api_gateway_integration&quot;</span> <span class="string">&quot;cors&quot;</span> &#123;</span><br><span class="line">  rest_api_id = aws_api_gateway_rest_api.backend.<span class="built_in">id</span></span><br><span class="line">  resource_id = aws_api_gateway_resource.cors.<span class="built_in">id</span></span><br><span class="line">  http_method = aws_api_gateway_method.cors.http_method</span><br><span class="line">  type = <span class="string">&quot;MOCK&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_api_gateway_method_response&quot;</span> <span class="string">&quot;cors&quot;</span> &#123;</span><br><span class="line">  depends_on = [aws_api_gateway_method.cors]</span><br><span class="line">  rest_api_id = aws_api_gateway_rest_api.backend.<span class="built_in">id</span></span><br><span class="line">  resource_id = aws_api_gateway_resource.cors.<span class="built_in">id</span></span><br><span class="line">  http_method = aws_api_gateway_method.cors.http_method</span><br><span class="line">  status_code = <span class="number">200</span></span><br><span class="line">  response_parameters = &#123;</span><br><span class="line">    <span class="string">&quot;method.response.header.Access-Control-Allow-Origin&quot;</span> = <span class="literal">true</span>,</span><br><span class="line">    <span class="string">&quot;method.response.header.Access-Control-Allow-Methods&quot;</span> = <span class="literal">true</span>,</span><br><span class="line">    <span class="string">&quot;method.response.header.Access-Control-Allow-Headers&quot;</span> = <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">  response_models = &#123;</span><br><span class="line">    <span class="string">&quot;application/json&quot;</span> = <span class="string">&quot;Empty&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_api_gateway_integration_response&quot;</span> <span class="string">&quot;cors&quot;</span> &#123;</span><br><span class="line">  depends_on = [aws_api_gateway_integration.cors, aws_api_gateway_method_response.cors]</span><br><span class="line">  rest_api_id = aws_api_gateway_rest_api.backend.<span class="built_in">id</span></span><br><span class="line">  resource_id = aws_api_gateway_resource.cors.<span class="built_in">id</span></span><br><span class="line">  http_method = aws_api_gateway_method.cors.http_method</span><br><span class="line">  status_code = <span class="number">200</span></span><br><span class="line">  response_parameters = &#123;</span><br><span class="line">    <span class="string">&quot;method.response.header.Access-Control-Allow-Origin&quot;</span> = <span class="string">&quot;&#x27;*&#x27;&quot;</span>, <span class="comment"># replace with hostname of frontend (CloudFront)</span></span><br><span class="line">    <span class="string">&quot;method.response.header.Access-Control-Allow-Headers&quot;</span> = <span class="string">&quot;&#x27;Content-Type&#x27;&quot;</span>,</span><br><span class="line">    <span class="string">&quot;method.response.header.Access-Control-Allow-Methods&quot;</span> = <span class="string">&quot;&#x27;GET, POST&#x27;&quot;</span> <span class="comment"># remove or add HTTP methods as needed</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>That’s it. The API Gateway will answer all CORS related <code>OPTIONS</code> requests now.</p><h2 id="Add-CORS-headers-to-server-side-errors"><a href="#Add-CORS-headers-to-server-side-errors" class="headerlink" title="Add CORS headers to server-side errors"></a>Add CORS headers to server-side errors</h2><p>Finally, you need to make sure that the API Gateway is also adding the CORS headers in case of server-side errors. Why is that important? For example, when the API Gateway fails to invoke your Lambda function, it will return a server-side error. In that case, the Lambda function cannot add the necessary <code>Access-Control-Allow-Origin</code> header to the response. Therefore, you need to configure the API Gateway to add the CORS header for server-side errors, as illustrated in the following Terraform configuration snippet.</p><figure class="highlight applescript"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_api_gateway_gateway_response&quot;</span> <span class="string">&quot;response_4xx&quot;</span> &#123;</span><br><span class="line">  rest_api_id   = aws_api_gateway_rest_api.backend.<span class="built_in">id</span></span><br><span class="line">  response_type = <span class="string">&quot;DEFAULT_4XX&quot;</span></span><br><span class="line"></span><br><span class="line">  response_templates = &#123;</span><br><span class="line">    <span class="string">&quot;application/json&quot;</span> = <span class="string">&quot;&#123;&#x27;message&#x27;:$context.error.messageString&#125;&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  response_parameters = &#123;</span><br><span class="line">    <span class="string">&quot;gatewayresponse.header.Access-Control-Allow-Origin&quot;</span> = <span class="string">&quot;&#x27;*&#x27;&quot;</span> <span class="comment"># replace with hostname of frontend (CloudFront)</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_api_gateway_gateway_response&quot;</span> <span class="string">&quot;response_5xx&quot;</span> &#123;</span><br><span class="line">  rest_api_id   = aws_api_gateway_rest_api.backend.<span class="built_in">id</span></span><br><span class="line">  response_type = <span class="string">&quot;DEFAULT_5XX&quot;</span></span><br><span class="line"></span><br><span class="line">  response_templates = &#123;</span><br><span class="line">    <span class="string">&quot;application/json&quot;</span> = <span class="string">&quot;&#123;&#x27;message&#x27;:$context.error.messageString&#125;&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  response_parameters = &#123;</span><br><span class="line">    <span class="string">&quot;gatewayresponse.header.Access-Control-Allow-Origin&quot;</span> = <span class="string">&quot;&#x27;*&#x27;&quot;</span> <span class="comment"># replace with hostname of frontend (CloudFront)</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>That’s it. You have successfully enabled CORS for your backend.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>When using the Lambda proxy integration, the following steps are necessary to enable CORS on the backend of your Serverless application.</p><ol><li>Implement adding CORS headers with the Lambda function</li><li>Add static response for OPTIONS requests</li><li>Add CORS headers to server-side errors</li></ol><p>By the way, you could also avoid CORS entirely by running the API Gateway behind the same domain as your SPA. For example, by configuring CloudFront to route requests to <code>myapp.com/api/*</code> to a regional API Gateway while routing everything else to an S3 bucket.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Unboxing Amazon Timestream</title>
      <link>https://cloudonaut.io/unboxing-amazon-timestream/</link>
      <description>
        <![CDATA[<p>My first job after graduation in 2011 was all about time-series data. My first task was to connect an exchange data feed with our on-prem]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <category domain="https://cloudonaut.io/tag/timestream/">timestream</category>
      <guid isPermaLink="true">https://cloudonaut.io/unboxing-amazon-timestream/</guid>
      <pubDate>Wed, 14 Oct 2020 17:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>My first job after graduation in 2011 was all about time-series data. My first task was to connect an exchange data feed with our on-premises time-series database (we used kdb+ by KX Systems). Whenever the exchange matches a buyer and seller, a trade is published on its data feed. A trade contains the following information: time, financial instrument, price, and size of the deal. E.g., At 8:10 am, 50 Amazon shares traded for $3373,23. Trades are naturally ordered by time. But collecting data is not where the story ends.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/timeseries@730w.webp 730w, /images/2020/10/timeseries@730w2x.webp 1460w, /images/2020/10/timeseries@610w.webp 610w, /images/2020/10/timeseries@610w2x.webp 1220w, /images/2020/10/timeseries@450w.webp 450w, /images/2020/10/timeseries@450w2x.webp 900w, /images/2020/10/timeseries@330w.webp 330w, /images/2020/10/timeseries@330w2x.webp 660w, /images/2020/10/timeseries@545w.webp 545w, /images/2020/10/timeseries@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/timeseries@730w.jpg 730w, /images/2020/10/timeseries@730w2x.jpg 1460w, /images/2020/10/timeseries@610w.jpg 610w, /images/2020/10/timeseries@610w2x.jpg 1220w, /images/2020/10/timeseries@450w.jpg 450w, /images/2020/10/timeseries@450w2x.jpg 900w, /images/2020/10/timeseries@330w.jpg 330w, /images/2020/10/timeseries@330w2x.jpg 660w, /images/2020/10/timeseries@545w.jpg 545w, /images/2020/10/timeseries@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/timeseries.jpg" alt="Time-series data" title="Time-series data"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/29-unboxing-amazon-timestream/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>My next step was to analyze the data. Either for humans or computers that buy and sell financial instruments. A couple of questions I had to answer:</p><ul><li>What is the latest price of the Amazon share on the New York Stock Exchange?</li><li>What is the best price of the Amazon share across all marketplaces at the moment?</li><li>Show me today’s Amazon share prices at a granularity of 1 minute</li><li>Show me the last three years of Amazon share prices at a daily granularity</li><li>Is the Amazon share cheap or expensive compared to a basket of financial instruments in the last month?</li><li>How strong does the Amazon share price correlate with the Apple share price in the 300 days?</li></ul><p>One thing that all of those questions have in common is that they operate on a time series. In SQL terms, think of <code>WHERE time&gt;&#39;2020-10-01T00:00:00.000&#39;</code>, <code>ORDER BY time</code>, or <code>GROUP BY time.minute</code>. A time-series database is optimized for such queries.</p><h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>So far, ingesting, storing, and analyzing time-series data on AWS was more challenging than it could be. Luckily, <a href="https://aws.amazon.com/about-aws/whats-new/2020/09/amazon-timestream-now-generally-available/" target="_blank" rel="noopener">AWS released Amazon Timestream to all of us on Sep 30, 2020</a>.</p><p><a href="https://aws.amazon.com/timestream/" target="_blank" rel="noopener">Amazon Timestream</a> is a fully managed time-series database with zero operational overhead. Timestream auto-scales both the ingestion and the query processing layer. You <a href="https://aws.amazon.com/timestream/pricing/" target="_blank" rel="noopener">pay for ingesting data, storing data, and analyzing data independently</a>. Timestream keeps your latest data (up to one year) in memory and stores it in a durable way. Afterward, Timestream rolls the data to magnetic disks where you can keep it for up to 200 years. SSD support is coming soon. Keep in mind that time-series databases perform many sequential reads, and magnetic disks are very good at that.</p><blockquote><p>“Unboxing xyz” is a new series on cloudonaut that helps you to get used to new services on AWS. We demonstrate the service based on a use case. We also cover the basic concepts and the core features of the service.<br>I’m looking forward to writing an <a href="/tag/review/">in-depth review</a> of Timestream soon!</p></blockquote><h2 id="Concepts"><a href="#Concepts" class="headerlink" title="Concepts"></a>Concepts</h2><p>So let’s look a bit closer at Timestream. You start by creating a database by specifying a name. Within the database, you can create tables. A table has a name, and you configure how long data is kept in memory and on-disk.</p><p>Within a <strong>table</strong>, data is organized in <strong>time series</strong>. A time series is identified by its <strong>dimensions</strong> that you defined during data ingestions. To store stock prices, I used one dimension <em>symbol</em> with values such as <code>AMZN</code>, <code>AAPL</code>, <code>MSFT</code>. Each row in the time series is called a record. A record has a timestamp and one or many measures. To store stock prices, I used two measures: <em>size</em> and <em>price</em>. Attention: It is impossible to store two records with the same timestamp in a time series!</p><p>There are a <a href="https://docs.aws.amazon.com/timestream/latest/developerguide/OtherServices.html" target="_blank" rel="noopener">bunch of integrations</a> available to help you with the data ingestion and analytics part. If you want to write queries from scratch, check out the SQL based <a href="https://docs.aws.amazon.com/timestream/latest/developerguide/reference.html" target="_blank" rel="noopener">Timestream Query Language Reference</a>. If you are interested in the details, I created a video where I demonstrate how I ingest and analyze stock prices with Timestream.</p><p>Watch Michael analyzing data from the stock market. The video explains how to ingest data into a Timestream table and shows how to query the data.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=HmFyKv32CgA">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/HmFyKv32CgA" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p>Node.js Code: <a href="/download/files/timestream-stocks.zip">timestream-stocks.zip</a></p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>I’m impressed by the simplicity of Timestream. The time-series database I worked with before was much harder to operate and use. I enjoy the following features a lot:</p><ul><li>I can run a single query over in-memory and on-disk data. It just works. Timestream cares about merging the two data sources.</li><li>Timestream infers the data schema based on the data that I ingest.</li><li>Timestream scales ingestion, storage, and data processing independently.</li><li>You only pay for what you use. There are no hourly fees.</li><li><a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_Timestream.html" target="_blank" rel="noopener">CloudFormation fully supports databases and tables</a>.</li></ul><p>I can only imagine the effort it takes to build such a system. In early 2015, I started a SaaS business with Andreas: TimeSeries.Guru. Our offering might sound familiar: A time-series database as a service on AWS. It was based on the kdb+ technology. It was a lot of work, but the technology was expensive. We closed down the service after one year and focused on our consulting business. But it was a lot of fun!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Caching on AWS 101</title>
      <link>https://cloudonaut.io/caching-on-aws-101/</link>
      <description>
        <![CDATA[<p>Oftentimes, the idea of adding a caching layer arises when users start complaining about the performance of an application. Adding a cach]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/elasticache/">elasticache</category>
      <category domain="https://cloudonaut.io/tag/cloudfront/">cloudfront</category>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/caching-on-aws-101/</guid>
      <pubDate>Thu, 08 Oct 2020 19:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Oftentimes, the idea of adding a caching layer arises when users start complaining about the performance of an application. Adding a cache to your architecture does not solve all problems — especially when implementing that change under pressure to fix performance issues. Therefore, thinking about a caching strategy should be part of the process when designing your architecture.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/memory@730w.webp 730w, /images/2020/10/memory@730w2x.webp 1460w, /images/2020/10/memory@610w.webp 610w, /images/2020/10/memory@610w2x.webp 1220w, /images/2020/10/memory@450w.webp 450w, /images/2020/10/memory@450w2x.webp 900w, /images/2020/10/memory@330w.webp 330w, /images/2020/10/memory@330w2x.webp 660w, /images/2020/10/memory@545w.webp 545w, /images/2020/10/memory@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/memory@730w.jpg 730w, /images/2020/10/memory@730w2x.jpg 1460w, /images/2020/10/memory@610w.jpg 610w, /images/2020/10/memory@610w2x.jpg 1220w, /images/2020/10/memory@450w.jpg 450w, /images/2020/10/memory@450w2x.jpg 900w, /images/2020/10/memory@330w.jpg 330w, /images/2020/10/memory@330w2x.jpg 660w, /images/2020/10/memory@545w.jpg 545w, /images/2020/10/memory@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/memory.jpg" alt="Caching on AWS 101" title="Caching on AWS 101"></picture></p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><p>Let’s take a look at the following architecture sketch, created with <a href="http://cloudcraft.com/" target="_blank" rel="noopener">Cloudcraft</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/caching-overview@730w.webp 730w, /images/2020/10/caching-overview@730w2x.webp 1460w, /images/2020/10/caching-overview@610w.webp 610w, /images/2020/10/caching-overview@610w2x.webp 1220w, /images/2020/10/caching-overview@450w.webp 450w, /images/2020/10/caching-overview@450w2x.webp 900w, /images/2020/10/caching-overview@330w.webp 330w, /images/2020/10/caching-overview@330w2x.webp 660w, /images/2020/10/caching-overview@545w.webp 545w, /images/2020/10/caching-overview@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/caching-overview@730w.png 730w, /images/2020/10/caching-overview@730w2x.png 1460w, /images/2020/10/caching-overview@610w.png 610w, /images/2020/10/caching-overview@610w2x.png 1220w, /images/2020/10/caching-overview@450w.png 450w, /images/2020/10/caching-overview@450w2x.png 900w, /images/2020/10/caching-overview@330w.png 330w, /images/2020/10/caching-overview@330w2x.png 660w, /images/2020/10/caching-overview@545w.png 545w, /images/2020/10/caching-overview@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/caching-overview.png" alt="Caching on AWS: Example" title="Caching on AWS: Example"></picture></p><p>You will learn how to improve your architecture by adding a cache. After that, I will point out best practices for integrating a cache into your system. Finally, you will learn about caching services provided by AWS.</p><h2 id="Why"><a href="#Why" class="headerlink" title="Why?"></a>Why?</h2><p>In medium to high traffic scenarios, a cache will make a big difference. As a rule of thumb, I would start thinking about caching when expecting more than 100 concurrent users.</p><p>What are the benefits of caching?</p><ul><li><strong>Optimize performance</strong> by asking the cache for results instead of executing complex SQL queries or wait for relatively slow storage systems. For example, cache results within memory instead of reading data from disk.</li><li><strong>Flatten traffic spikes</strong> by storing pre-calculated results to the most frequent requests. For example, the rendered HTML of a very popular product page of an online shop.</li><li><strong>Reduce costs</strong> by reducing the load on the resources within your architecture. For example, to be able to downgrade the database instance.</li></ul><p>I hope you are sold on the idea of adding a cache to your architecture. Let’s have a look at some best practices for integrating a cache into your system next.</p><h2 id="How"><a href="#How" class="headerlink" title="How?"></a>How?</h2><p>When thinking about how to cache requests, it comes down to two questions.</p><p>First, how do you populate your cache? The “lazy loading” strategy uses the following approach:</p><ol><li>Ask the cache for a result.</li><li>If the cache cannot answer the request, send the request to the database.</li><li>Write the result from the database to the cache.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/caching-lazy-loading@730w.webp 730w, /images/2020/10/caching-lazy-loading@730w2x.webp 1460w, /images/2020/10/caching-lazy-loading@610w.webp 610w, /images/2020/10/caching-lazy-loading@610w2x.webp 1220w, /images/2020/10/caching-lazy-loading@450w.webp 450w, /images/2020/10/caching-lazy-loading@450w2x.webp 900w, /images/2020/10/caching-lazy-loading@330w.webp 330w, /images/2020/10/caching-lazy-loading@330w2x.webp 660w, /images/2020/10/caching-lazy-loading@545w.webp 545w, /images/2020/10/caching-lazy-loading@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/caching-lazy-loading@730w.png 730w, /images/2020/10/caching-lazy-loading@730w2x.png 1460w, /images/2020/10/caching-lazy-loading@610w.png 610w, /images/2020/10/caching-lazy-loading@610w2x.png 1220w, /images/2020/10/caching-lazy-loading@450w.png 450w, /images/2020/10/caching-lazy-loading@450w2x.png 900w, /images/2020/10/caching-lazy-loading@330w.png 330w, /images/2020/10/caching-lazy-loading@330w2x.png 660w, /images/2020/10/caching-lazy-loading@545w.png 545w, /images/2020/10/caching-lazy-loading@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/caching-lazy-loading.png" alt="Caching: Lazy Loading" title="Caching: Lazy Loading"></picture></p><p>As an alternative, you could use the “write-through” strategy.</p><ol><li>When writing data to the database, update the cache as well.</li><li>For each read request, ask the cache instead of the database.</li><li>Fallback to reading from the database in exceptional cases only.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/caching-write-through@730w.webp 730w, /images/2020/10/caching-write-through@730w2x.webp 1460w, /images/2020/10/caching-write-through@610w.webp 610w, /images/2020/10/caching-write-through@610w2x.webp 1220w, /images/2020/10/caching-write-through@450w.webp 450w, /images/2020/10/caching-write-through@450w2x.webp 900w, /images/2020/10/caching-write-through@330w.webp 330w, /images/2020/10/caching-write-through@330w2x.webp 660w, /images/2020/10/caching-write-through@545w.webp 545w, /images/2020/10/caching-write-through@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/caching-write-through@730w.png 730w, /images/2020/10/caching-write-through@730w2x.png 1460w, /images/2020/10/caching-write-through@610w.png 610w, /images/2020/10/caching-write-through@610w2x.png 1220w, /images/2020/10/caching-write-through@450w.png 450w, /images/2020/10/caching-write-through@450w2x.png 900w, /images/2020/10/caching-write-through@330w.png 330w, /images/2020/10/caching-write-through@330w2x.png 660w, /images/2020/10/caching-write-through@545w.png 545w, /images/2020/10/caching-write-through@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/caching-write-through.png" alt="Caching: Write Through" title="Caching: Write Through"></picture></p><p>Second, how do you make sure that when changing data in the database, the cache gets updated as well? Let me answer that question with a quote from Phil Karlton: “There are only two hard things in Computer Science: cache invalidation and naming things.”</p><p>Invalidating a cache is a challenge. An approach to that problem: define a time-to-live (TTL) for all results stored in the cache. By doing so, the cached data will expire after a certain amount of time — no need to think through all the scenarios that could require your system to update the cache. However, the clients will receive stale data from the cache.</p><p>It might be fine to deliver an outdated profile picture for a few hours, but it might be an issue to display an obsolete price for a product in your online shop or web-based service. Therefore, defining a TTL is typically something to think about from a business perspective.</p><p>Alternatively, many database systems offer a stream with create, update, and delete events. By subscribing to those change events, you can quickly invalidate your cache.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/caching-eventbased-invalidate@730w.webp 730w, /images/2020/10/caching-eventbased-invalidate@730w2x.webp 1460w, /images/2020/10/caching-eventbased-invalidate@610w.webp 610w, /images/2020/10/caching-eventbased-invalidate@610w2x.webp 1220w, /images/2020/10/caching-eventbased-invalidate@450w.webp 450w, /images/2020/10/caching-eventbased-invalidate@450w2x.webp 900w, /images/2020/10/caching-eventbased-invalidate@330w.webp 330w, /images/2020/10/caching-eventbased-invalidate@330w2x.webp 660w, /images/2020/10/caching-eventbased-invalidate@545w.webp 545w, /images/2020/10/caching-eventbased-invalidate@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/caching-eventbased-invalidate@730w.png 730w, /images/2020/10/caching-eventbased-invalidate@730w2x.png 1460w, /images/2020/10/caching-eventbased-invalidate@610w.png 610w, /images/2020/10/caching-eventbased-invalidate@610w2x.png 1220w, /images/2020/10/caching-eventbased-invalidate@450w.png 450w, /images/2020/10/caching-eventbased-invalidate@450w2x.png 900w, /images/2020/10/caching-eventbased-invalidate@330w.png 330w, /images/2020/10/caching-eventbased-invalidate@330w2x.png 660w, /images/2020/10/caching-eventbased-invalidate@545w.png 545w, /images/2020/10/caching-eventbased-invalidate@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/caching-eventbased-invalidate.png" alt="Eventbased Cache Invalidation" title="Eventbased Cache Invalidation"></picture></p><p>It is also worth noting that the “write-through” strategy does update the cache automatically. There is no need to think about cache invalidation in that scenario. But it requires that all your data fit into the cache!</p><p>Next, you will learn about managed caching services offered by AWS.</p><h2 id="Caching-on-AWS"><a href="#Caching-on-AWS" class="headerlink" title="Caching on AWS"></a>Caching on AWS</h2><p>The large portfolio of AWS also includes services for caching.</p><ul><li><strong>Amazon ElastiCache</strong> offers in-memory caches based on Redis or Memcached.</li><li><strong>Amazon CloudFront</strong> provides a Content Delivery Network (CDN) caching HTTP close to your worldwide customers.</li><li><strong>Amazon DynamoDB Accelerator (DAX)</strong> adds caching capabilities to Amazon’s NoSQL database.</li></ul><p>ElastiCache is for in-memory caches what RDS is for SQL databases. You can choose between two engines:</p><ul><li>Redis</li><li>Memcached</li></ul><p>Both Redis and Memcached are open-source in-memory data stores. Overall, Redis comes with some advanced features like complex data structures (e.g., sorted sets), in-memory snapshots on disk, and a built-in message broker. Compared to that, Memcached focuses on simplicity and provides only the functionality needed for an in-memory cache.</p><p>ElastiCache allows you to deploy a cluster of in-memory instances. All you have to do is to specify the instance type – which defines the CPU, memory, and networking capabilities – as well as the number of instances. The managed service will spin up and maintain the needed instances and provides you a hostname that your application can connect to. It is important to note that ElastiCache runs within your private network (VPC).</p><p>Many application frameworks like Spring or Ruby on Rails come with support for either Redis or Memcached to cache database queries, rendered partials, and many more.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/caching-elasticache@730w.webp 730w, /images/2020/10/caching-elasticache@730w2x.webp 1460w, /images/2020/10/caching-elasticache@610w.webp 610w, /images/2020/10/caching-elasticache@610w2x.webp 1220w, /images/2020/10/caching-elasticache@450w.webp 450w, /images/2020/10/caching-elasticache@450w2x.webp 900w, /images/2020/10/caching-elasticache@330w.webp 330w, /images/2020/10/caching-elasticache@330w2x.webp 660w, /images/2020/10/caching-elasticache@545w.webp 545w, /images/2020/10/caching-elasticache@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/caching-elasticache@730w.png 730w, /images/2020/10/caching-elasticache@730w2x.png 1460w, /images/2020/10/caching-elasticache@610w.png 610w, /images/2020/10/caching-elasticache@610w2x.png 1220w, /images/2020/10/caching-elasticache@450w.png 450w, /images/2020/10/caching-elasticache@450w2x.png 900w, /images/2020/10/caching-elasticache@330w.png 330w, /images/2020/10/caching-elasticache@330w2x.png 660w, /images/2020/10/caching-elasticache@545w.png 545w, /images/2020/10/caching-elasticache@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/caching-elasticache.png" alt="Caching with ElastiCache" title="Caching with ElastiCache"></picture></p><p>By the way, another typical use case for ElastiCache is storing sessions or all kinds of data that you do not need to persist on disk.</p><p>Typically, ElastiCache is used by the application to cache database queries or pre-calculated results. However, the load balancer and web server still need to process the request. Adding CloudFront, a Content Delivery Network (CDN), to the architecture allows you to answer requests even before they hit your load balancer. Doing so reduces latency and costs even further.</p><p>AWS operates infrastructure worldwide. Besides the big data centers – called regions – there are more than 200 edge locations all around the world. CloudFront utilizes those edge locations to minimize the distance and, therefore, latency between your users and your cloud infrastructure. You can think of CloudFront as a cache for static web content – like stylesheets, images, and videos – as well as a cache for dynamic content – like a rendered HTML page. CloudFront is priced per request and data transfer. There is no minimum fee.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/caching-cloudfront@730w.webp 730w, /images/2020/10/caching-cloudfront@730w2x.webp 1460w, /images/2020/10/caching-cloudfront@610w.webp 610w, /images/2020/10/caching-cloudfront@610w2x.webp 1220w, /images/2020/10/caching-cloudfront@450w.webp 450w, /images/2020/10/caching-cloudfront@450w2x.webp 900w, /images/2020/10/caching-cloudfront@330w.webp 330w, /images/2020/10/caching-cloudfront@330w2x.webp 660w, /images/2020/10/caching-cloudfront@545w.webp 545w, /images/2020/10/caching-cloudfront@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/caching-cloudfront@730w.png 730w, /images/2020/10/caching-cloudfront@730w2x.png 1460w, /images/2020/10/caching-cloudfront@610w.png 610w, /images/2020/10/caching-cloudfront@610w2x.png 1220w, /images/2020/10/caching-cloudfront@450w.png 450w, /images/2020/10/caching-cloudfront@450w2x.png 900w, /images/2020/10/caching-cloudfront@330w.png 330w, /images/2020/10/caching-cloudfront@330w2x.png 660w, /images/2020/10/caching-cloudfront@545w.png 545w, /images/2020/10/caching-cloudfront@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/caching-cloudfront.png" alt="Caching with CloudFront (CDN)" title="Caching with CloudFront (CDN)"></picture></p><p>By default, CloudFront uses the <code>Cache-Control</code> or <code>Expire</code> HTTP header defined by your application to decide how long a response should be cached. Besides that, you can configure the caching behavior for different paths of your application. It is also possible to invalidate the cached responses manually, for example, after a deployment.</p><p>Finally, I want to introduce another caching solution. DynamoDB Accelerator (DAX) provides a write-through caching service for DynamoDB, the NoSQL database by AWS. Write requests are stored in the in-memory cache as well as in the DynamoDB table. AWS manages a cluster of caching nodes for you.</p><p>When using the AWS SDK for Java, JavaScript, .NET, Python, or Go, switching to the DAX client is simple. However, I have to mention that DAX supports eventually consistent reads only, strongly consistent reads are not supported. Also, DAX requires you to provision caching nodes of a certain instance type. So you are losing the benefits of DynamoDB’s pay-per-request pricing model and automated scaling.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Adding a cache to your architecture can help to optimize performance, flatten traffic spikes, and reduce costs. I highly recommend thinking about caching already when designing an architecture. Introducing a caching layer later might be challenging. </p><p>AWS provides in-memory data stores with ElastiCache. Both Redis and Memcached are popular open-source projects and available as options at ElastiCache. CloudFront — Amazon’s Content Delivery Network — can be used to cache static but also dynamic content close to your users worldwide.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Have you replaced IAM Users with AWS SSO yet?</title>
      <link>https://cloudonaut.io/aws-sso-instead-of-iam-users/</link>
      <description>
        <![CDATA[<p>The most secure option to isolate workloads from each other is to use multiple AWS accounts. Many organizations use different AWS account]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <category domain="https://cloudonaut.io/tag/sso/">sso</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-sso-instead-of-iam-users/</guid>
      <pubDate>Fri, 02 Oct 2020 07:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The most secure option to isolate workloads from each other is to use multiple AWS accounts. Many organizations use different AWS accounts for testing and production, for example. The more AWS accounts you use, the more complicated it gets to manage users and grant them access.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/door@730w.webp 730w, /images/2020/10/door@730w2x.webp 1460w, /images/2020/10/door@610w.webp 610w, /images/2020/10/door@610w2x.webp 1220w, /images/2020/10/door@450w.webp 450w, /images/2020/10/door@450w2x.webp 900w, /images/2020/10/door@330w.webp 330w, /images/2020/10/door@330w2x.webp 660w, /images/2020/10/door@545w.webp 545w, /images/2020/10/door@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/door@730w.jpg 730w, /images/2020/10/door@730w2x.jpg 1460w, /images/2020/10/door@610w.jpg 610w, /images/2020/10/door@610w2x.jpg 1220w, /images/2020/10/door@450w.jpg 450w, /images/2020/10/door@450w2x.jpg 900w, /images/2020/10/door@330w.jpg 330w, /images/2020/10/door@330w2x.jpg 660w, /images/2020/10/door@545w.jpg 545w, /images/2020/10/door@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/door.jpg" alt="AWS SSO integrates with AWS Organizations" title="AWS SSO integrates with AWS Organizations"></picture></p><p>Formerly, using IAM roles for cross-account access was a popular pattern. How did that work in practice? Often, everything started with an AWS account that contained nothing but the IAM users and groups, allowing engineers to authenticate. Besides that, account administrators added IAM roles for cross-account access to every AWS account.</p><p>When an engineer wanted to access AWS account <code>B</code>, she needed to authenticate with her IAM user at AWS account <code>A</code> first. In a second step, she could assume the IAM role in AWS account <code>B</code>. Using temporary security credentials, she could act under the IAM role to access or modify resources in AWS account <code>B</code>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/iam-flow@730w.webp 730w, /images/2020/10/iam-flow@730w2x.webp 1460w, /images/2020/10/iam-flow@610w.webp 610w, /images/2020/10/iam-flow@610w2x.webp 1220w, /images/2020/10/iam-flow@450w.webp 450w, /images/2020/10/iam-flow@450w2x.webp 900w, /images/2020/10/iam-flow@330w.webp 330w, /images/2020/10/iam-flow@330w2x.webp 660w, /images/2020/10/iam-flow@545w.webp 545w, /images/2020/10/iam-flow@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/iam-flow@730w.png 730w, /images/2020/10/iam-flow@730w2x.png 1460w, /images/2020/10/iam-flow@610w.png 610w, /images/2020/10/iam-flow@610w2x.png 1220w, /images/2020/10/iam-flow@450w.png 450w, /images/2020/10/iam-flow@450w2x.png 900w, /images/2020/10/iam-flow@330w.png 330w, /images/2020/10/iam-flow@330w2x.png 660w, /images/2020/10/iam-flow@545w.png 545w, /images/2020/10/iam-flow@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/iam-flow.png" alt="Accessing AWS accounts with IAM users and roles" title="Accessing AWS accounts with IAM users and roles"></picture></p><p>That’s a cumbersome approach because you need to deploy and manage many policies and roles across your AWS accounts. Also, the user experience for anyone who wants to access resources in an AWS account was far away from being simple.</p><p>AWS introduced <a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html" target="_blank" rel="noopener">AWS Single Sign-On (SSO)</a> in December 2017. In the beginning, an Active Directory was a requirement to be able to use the service. But AWS SSO made astonishing progress within the last few years. Nowadays, AWS SSO is an excellent alternative to using IAM users and groups for managing access to AWS accounts for your engineers. </p><p>AWS provides three options to manage users and groups:</p><ol><li>Built-in user store.</li><li>SAML to integrate with 3rd party identity providers (e.g., Google).</li><li>Connect with Active Directory (requires AWS Directory Service).</li></ol><p>The first two options are very lightweight and work well for SMBs. Next, I’ll demonstrate why you should replace your IAM users with AWS SSO next.</p><h2 id="Minify-Overhead-AWS-Organizations"><a href="#Minify-Overhead-AWS-Organizations" class="headerlink" title="Minify Overhead: AWS Organizations"></a>Minify Overhead: AWS Organizations</h2><p>Managing tens or hundreds of AWS accounts is a challenge. AWS Organizations simplifies governing AWS accounts not only for big enterprise customers. You can think of AWS Organizations as a layer on top of AWS accounts.</p><blockquote><p>There are some pitfalls when introducing AWS Organizations as well. I’ve summarized my thoughts on that in a former blog post: <a href="https://cloudonaut.io/aws-account-structure-think-twice-before-using-aws-organizations/">AWS Account Structure: Think twice before using AWS Organizations</a>.</p></blockquote><p>AWS SSO integrates with AWS Organizations. By doing so, AWS SSO provisions IAM roles and identity providers within all your AWS accounts with the click of a button. No need to roll out IAM roles manually anymore.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/sso-overview@730w.webp 730w, /images/2020/10/sso-overview@730w2x.webp 1460w, /images/2020/10/sso-overview@610w.webp 610w, /images/2020/10/sso-overview@610w2x.webp 1220w, /images/2020/10/sso-overview@450w.webp 450w, /images/2020/10/sso-overview@450w2x.webp 900w, /images/2020/10/sso-overview@330w.webp 330w, /images/2020/10/sso-overview@330w2x.webp 660w, /images/2020/10/sso-overview@545w.webp 545w, /images/2020/10/sso-overview@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/sso-overview@730w.png 730w, /images/2020/10/sso-overview@730w2x.png 1460w, /images/2020/10/sso-overview@610w.png 610w, /images/2020/10/sso-overview@610w2x.png 1220w, /images/2020/10/sso-overview@450w.png 450w, /images/2020/10/sso-overview@450w2x.png 900w, /images/2020/10/sso-overview@330w.png 330w, /images/2020/10/sso-overview@330w2x.png 660w, /images/2020/10/sso-overview@545w.png 545w, /images/2020/10/sso-overview@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/sso-overview.png" alt="AWS SSO integrates with AWS Organizations" title="AWS SSO integrates with AWS Organizations"></picture></p><p>That’s a significant benefit compared to managing IAM users and roles yourself.</p><h2 id="Improve-User-Experience-Login-Portal"><a href="#Improve-User-Experience-Login-Portal" class="headerlink" title="Improve User Experience: Login Portal"></a>Improve User Experience: Login Portal</h2><p>On top of that, AWS SSO comes with a login portal. First of all, the user needs to authenticate. Afterward, the login portal shows a list of all AWS accounts and permission sets (aka. roles) that the user has access to.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/10/sso-login-portal@730w.webp 730w, /images/2020/10/sso-login-portal@730w2x.webp 1460w, /images/2020/10/sso-login-portal@610w.webp 610w, /images/2020/10/sso-login-portal@610w2x.webp 1220w, /images/2020/10/sso-login-portal@450w.webp 450w, /images/2020/10/sso-login-portal@450w2x.webp 900w, /images/2020/10/sso-login-portal@330w.webp 330w, /images/2020/10/sso-login-portal@330w2x.webp 660w, /images/2020/10/sso-login-portal@545w.webp 545w, /images/2020/10/sso-login-portal@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/10/sso-login-portal@730w.png 730w, /images/2020/10/sso-login-portal@730w2x.png 1460w, /images/2020/10/sso-login-portal@610w.png 610w, /images/2020/10/sso-login-portal@610w2x.png 1220w, /images/2020/10/sso-login-portal@450w.png 450w, /images/2020/10/sso-login-portal@450w2x.png 900w, /images/2020/10/sso-login-portal@330w.png 330w, /images/2020/10/sso-login-portal@330w2x.png 660w, /images/2020/10/sso-login-portal@545w.png 545w, /images/2020/10/sso-login-portal@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/10/sso-login-portal.png" alt="Login Portal showing a list of AWS accounts" title="Login Portal showing a list of AWS accounts"></picture></p><p>From there, a user jumps into one of the AWS accounts directly. It gets even better. The AWS CLI works with AWS SSO as well.</p><p>Want to learn more about AWS SSO? Andreas shows how to use AWS SSO for SMBs in the following video.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=L3xoiabmcO0">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/L3xoiabmcO0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Are you using IAM users to authenticate and jump into other AWS accounts by using IAM roles? Give AWS SSO a try. It is easy to manage for administrators and easy to use for engineers. The best comes last: AWS SSO is free of charge.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Amazon ECR vs. Docker Hub vs. GitHub Container Registry</title>
      <link>https://cloudonaut.io/amazon-ecr-vs-docker-hub-vs-github-container-registry/</link>
      <description>
        <![CDATA[<p>Have you worked with a Linux package manager like <code>apt</code> or <code>yum</code> before? A container registry is similar, but inste]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecr/">ecr</category>
      <guid isPermaLink="true">https://cloudonaut.io/amazon-ecr-vs-docker-hub-vs-github-container-registry/</guid>
      <pubDate>Mon, 28 Sep 2020 19:30:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Have you worked with a Linux package manager like <code>apt</code> or <code>yum</code> before? A container registry is similar, but instead of packages, it distributes container images. A container registry is a crucial aspect of a containerized workflow and infrastructure. This blog post compares three different container registries: Amazon ECR, Docker Hub, and GitHub Container Registry. I’ve selected those three options out of many because they are following unique approaches.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/warehouse@730w.webp 730w, /images/2020/09/warehouse@730w2x.webp 1460w, /images/2020/09/warehouse@610w.webp 610w, /images/2020/09/warehouse@610w2x.webp 1220w, /images/2020/09/warehouse@450w.webp 450w, /images/2020/09/warehouse@450w2x.webp 900w, /images/2020/09/warehouse@330w.webp 330w, /images/2020/09/warehouse@330w2x.webp 660w, /images/2020/09/warehouse@545w.webp 545w, /images/2020/09/warehouse@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/warehouse@730w.jpg 730w, /images/2020/09/warehouse@730w2x.jpg 1460w, /images/2020/09/warehouse@610w.jpg 610w, /images/2020/09/warehouse@610w2x.jpg 1220w, /images/2020/09/warehouse@450w.jpg 450w, /images/2020/09/warehouse@450w2x.jpg 900w, /images/2020/09/warehouse@330w.jpg 330w, /images/2020/09/warehouse@330w2x.jpg 660w, /images/2020/09/warehouse@545w.jpg 545w, /images/2020/09/warehouse@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/warehouse.jpg" alt="Amazon ECR vs. Docker Hub vs. GitHub Container Registry" title="Amazon ECR vs. Docker Hub vs. GitHub Container Registry"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/28-how-to-choose-a-container-registry/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="What-is-a-container-registry"><a href="#What-is-a-container-registry" class="headerlink" title="What is a container registry?"></a>What is a container registry?</h2><p>A container registry is a central place to store and distribute container images. A container image includes all the data needed to start a container—for example, the operating system, libraries, runtime environments, and the application itself.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/container-registry@730w.webp 730w, /images/2020/09/container-registry@730w2x.webp 1460w, /images/2020/09/container-registry@610w.webp 610w, /images/2020/09/container-registry@610w2x.webp 1220w, /images/2020/09/container-registry@450w.webp 450w, /images/2020/09/container-registry@450w2x.webp 900w, /images/2020/09/container-registry@330w.webp 330w, /images/2020/09/container-registry@330w2x.webp 660w, /images/2020/09/container-registry@545w.webp 545w, /images/2020/09/container-registry@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/container-registry@730w.jpg 730w, /images/2020/09/container-registry@730w2x.jpg 1460w, /images/2020/09/container-registry@610w.jpg 610w, /images/2020/09/container-registry@610w2x.jpg 1220w, /images/2020/09/container-registry@450w.jpg 450w, /images/2020/09/container-registry@450w2x.jpg 900w, /images/2020/09/container-registry@330w.jpg 330w, /images/2020/09/container-registry@330w2x.jpg 660w, /images/2020/09/container-registry@545w.jpg 545w, /images/2020/09/container-registry@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/container-registry.jpg" alt="The concepts behind a container registry" title="The concepts behind a container registry"></picture></p><p>The following command builds an image with the files from the current directory. The build image will use the repository <code>cloudonaut</code> and tags the image with the version <code>1.0.0</code>.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">docker</span> build -t cloudonaut:<span class="number">1</span>.<span class="number">0</span>.<span class="number">0</span> .</span><br></pre></td></tr></table></figure><p>After the build succeeded, you can upload the image to the container registry.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">docker</span> push cloudonaut:<span class="number">1</span>.<span class="number">0</span>.<span class="number">0</span></span><br></pre></td></tr></table></figure><p>After that, whenever someone tries to start a container based on that image, the daemon will contact the container registry to pull the required image layers.</p><h2 id="Scenarios"><a href="#Scenarios" class="headerlink" title="Scenarios"></a>Scenarios</h2><p>There are different scenarios for using a container registry. I want to introduce three common ones.</p><p>Packaging your software into a container image is an excellent way for distribution as users or customers can get up and running quickly. Not only open-source projects but also ISV make use of this approach. In this scenario, you are looking for a public container registry that anyone can access to pull your images. Of course, only your developers - or even better an automated build - are allowed to push images to the registry.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/container-registry-scenario-1@730w.webp 730w, /images/2020/09/container-registry-scenario-1@730w2x.webp 1460w, /images/2020/09/container-registry-scenario-1@610w.webp 610w, /images/2020/09/container-registry-scenario-1@610w2x.webp 1220w, /images/2020/09/container-registry-scenario-1@450w.webp 450w, /images/2020/09/container-registry-scenario-1@450w2x.webp 900w, /images/2020/09/container-registry-scenario-1@330w.webp 330w, /images/2020/09/container-registry-scenario-1@330w2x.webp 660w, /images/2020/09/container-registry-scenario-1@545w.webp 545w, /images/2020/09/container-registry-scenario-1@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/container-registry-scenario-1@730w.png 730w, /images/2020/09/container-registry-scenario-1@730w2x.png 1460w, /images/2020/09/container-registry-scenario-1@610w.png 610w, /images/2020/09/container-registry-scenario-1@610w2x.png 1220w, /images/2020/09/container-registry-scenario-1@450w.png 450w, /images/2020/09/container-registry-scenario-1@450w2x.png 900w, /images/2020/09/container-registry-scenario-1@330w.png 330w, /images/2020/09/container-registry-scenario-1@330w2x.png 660w, /images/2020/09/container-registry-scenario-1@545w.png 545w, /images/2020/09/container-registry-scenario-1@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/container-registry-scenario-1.png" alt="Use a public container registry to distribute software" title="Use a public container registry to distribute software"></picture></p><p>Whenever you are planning to build a deployment pipeline for continuous deployment, you should start with building the image that will traverse through the pipeline in the following. A private container registry is a perfect place to store the artifact immediately after your pipeline’s build step succeeded. Whenever you deploy a new version of your software to one of your environments (test, prod, etc.), the infrastructure fetches the image from the repository.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/container-registry-scenario-2@730w.webp 730w, /images/2020/09/container-registry-scenario-2@730w2x.webp 1460w, /images/2020/09/container-registry-scenario-2@610w.webp 610w, /images/2020/09/container-registry-scenario-2@610w2x.webp 1220w, /images/2020/09/container-registry-scenario-2@450w.webp 450w, /images/2020/09/container-registry-scenario-2@450w2x.webp 900w, /images/2020/09/container-registry-scenario-2@330w.webp 330w, /images/2020/09/container-registry-scenario-2@330w2x.webp 660w, /images/2020/09/container-registry-scenario-2@545w.webp 545w, /images/2020/09/container-registry-scenario-2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/container-registry-scenario-2@730w.png 730w, /images/2020/09/container-registry-scenario-2@730w2x.png 1460w, /images/2020/09/container-registry-scenario-2@610w.png 610w, /images/2020/09/container-registry-scenario-2@610w2x.png 1220w, /images/2020/09/container-registry-scenario-2@450w.png 450w, /images/2020/09/container-registry-scenario-2@450w2x.png 900w, /images/2020/09/container-registry-scenario-2@330w.png 330w, /images/2020/09/container-registry-scenario-2@330w2x.png 660w, /images/2020/09/container-registry-scenario-2@545w.png 545w, /images/2020/09/container-registry-scenario-2@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/container-registry-scenario-2.png" alt="Use a private container registry for continuous deployment" title="Use a private container registry for continuous deployment"></picture></p><p>Are you developing microservices as a team? Do you need to share your service with other developers or teams so they can run parts of the microservice architecture on their machines? In that scenario, a private container registry allows you to distribute images within your organization.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/container-registry-scenario-3@730w.webp 730w, /images/2020/09/container-registry-scenario-3@730w2x.webp 1460w, /images/2020/09/container-registry-scenario-3@610w.webp 610w, /images/2020/09/container-registry-scenario-3@610w2x.webp 1220w, /images/2020/09/container-registry-scenario-3@450w.webp 450w, /images/2020/09/container-registry-scenario-3@450w2x.webp 900w, /images/2020/09/container-registry-scenario-3@330w.webp 330w, /images/2020/09/container-registry-scenario-3@330w2x.webp 660w, /images/2020/09/container-registry-scenario-3@545w.webp 545w, /images/2020/09/container-registry-scenario-3@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/container-registry-scenario-3@730w.png 730w, /images/2020/09/container-registry-scenario-3@730w2x.png 1460w, /images/2020/09/container-registry-scenario-3@610w.png 610w, /images/2020/09/container-registry-scenario-3@610w2x.png 1220w, /images/2020/09/container-registry-scenario-3@450w.png 450w, /images/2020/09/container-registry-scenario-3@450w2x.png 900w, /images/2020/09/container-registry-scenario-3@330w.png 330w, /images/2020/09/container-registry-scenario-3@330w2x.png 660w, /images/2020/09/container-registry-scenario-3@545w.png 545w, /images/2020/09/container-registry-scenario-3@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/container-registry-scenario-3.png" alt="Use a private container registry for collaboration" title="Use a private container registry for collaboration"></picture></p><p>In the following, you will learn which container registry fits best for each of the described scenarios.</p><h2 id="Amazon-ECR"><a href="#Amazon-ECR" class="headerlink" title="Amazon ECR"></a>Amazon ECR</h2><p>As all major cloud providers do, AWS offers a container registry as a service, as well: Elastic Container Registry (ECR).</p><p>The most crucial aspect of ECR is that AWS IAM handles authentication and authorization for the container registry. Therefore, it is easy to access ECR from all the different services AWS provides (ECS, EKS, CodeBuild, and many more). AWS IAM is not easy to use but allows you to define strict access control to your container registry, even with multi-factor authentication (MFA) for push and pull access.</p><p>Besides that, ECR offers other security-relevant features:</p><ol><li><a href="https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-tag-mutability.html" target="_blank" rel="noopener">Immutable Image Tags</a>: By enabling immutable image tags, you ensure that no one can override an image once it has been pushed to the repository. </li><li><a href="https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-scanning.html" target="_blank" rel="noopener">Image Scanning</a>: If desired, ECR will scan images after they have been pushed to a repository. ECR uses the CVEs database of the open-source project Clair to check images for known security vulnerabilities.</li></ol><p>Besides that, <a href="https://aws.amazon.com/blogs/aws/amazon-ecr-public-a-new-public-container-registry/" target="_blank" rel="noopener">AWS introduced public registries in December 2020</a> as well. However, Docker Hub is still more relevant for sharing images pubicly. Currently ECR hosts 62,476 repositories while there are 8,532,342 repositories available on Docker Hub.</p><p>It is essential to mention that Amazon ECR provides private repositories only. It is not possible to pull the images without authentication and authorization.</p><p>From my personal experience, I can tell that Amazon ECR is a rock-solid service and the best option to manage images for all container workloads running on AWS. So especially, if you do continuous deployments on AWS, ECR is your go-to service.</p><h2 id="Docker-Hub"><a href="#Docker-Hub" class="headerlink" title="Docker Hub"></a>Docker Hub</h2><p>Docker Hub is the most popular container registry, as it is the default registry for Docker. Many open-source projects and ISVs host their container images on Docker Hub.</p><p>Because Docker Hub is the default registry, the command to start a container is straightforward.</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">docker <span class="built_in">run</span> nginx:latest</span><br></pre></td></tr></table></figure><p>Compare that to launching a container based on the same image hosted on ECR.</p><figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">docker run <span class="number">111111111111</span><span class="selector-class">.dkr</span><span class="selector-class">.ecr</span><span class="selector-class">.eu-west-1</span><span class="selector-class">.amazonaws</span>.com/nginx:latest</span><br></pre></td></tr></table></figure><p>That’s the power of defaults. On top of that, think of Docker Hub as a marketplace for container images. That’s why I would state that Docker Hub is the best choice for publicly distributing software.</p><p>It is important to mention that Docker Hub made some unpleasant announcements recently. First of all, Docker Hub introduced strict rate limits for repositories on a free plan.</p><ul><li>100 pulls for unauthenticated users per 6 hours.</li><li>200 pulls for authenticated users per 6 hours.</li></ul><p>Introducing this rate limit broke many deployment pipelines, including one of ours. On top of that, Docker Hub announced a <a href="https://www.docker.com/pricing/resource-consumption-updates" target="_blank" rel="noopener">strict retention policy</a>. In the future, Docker Hub will delete images that haven’t been accessed within the last six months. Again, the restriction applies to repositories that are running on the free plan only.</p><p>Unluckily, both changes make hosting public repositories on Docker Hub less convenient.</p><p>Besides hosting public repositories, Docker Hub offers private repositories as well. However, the pricing model is not compelling. ECR and GitHub Container Registry are better options in that scenario.</p><h2 id="GitHub-Container-Registry"><a href="#GitHub-Container-Registry" class="headerlink" title="GitHub Container Registry"></a>GitHub Container Registry</h2><p>As of today, the GitHub Container Registry is still in beta. But it seems to become a good choice for organizations using GitHub to collaborate on source code already. The GitHub Container Registry supports both: public and private repositories.</p><p>Accessing the GitHub Container Registry is quite simple for anyone with access to GitHub. All you need to do is <a href="https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token" target="_blank" rel="noopener">create a personal access token</a>. That’s all you need to authenticate for the container registry. A seamless experience for developers.</p><p>It is worth mentioning that the GitHub Container Registry integrates well with GitHub Actions (CI&#x2F;CD). I’ve created a <code>Hello World</code> <a href="https://github.com/widdix/container-hello-world/" target="_blank" rel="noopener">example repository</a> to evaluate the service. As expected, I enjoyed the developer experience very much. On top of that, accessing the container registry from GitHub Actions does not incur any data transfer charges.</p><p>By the way, GitHub Container Registry provides public repositories. However, anyone who wants to download your container image needs to authenticate with a GitHub user account. Docker Hub allows unauthenticated access as well.</p><p>I recommend using the GitHub Container Registry to distribute container images within a team of software developers or an organization. For example, to share pre-build services of a microservice architecture. Also, if you are using GitHub Actions for CI&#x2F;CD, the integration with GitHub Container Registry might be a pro argument as well.</p><h2 id="Comparison"><a href="#Comparison" class="headerlink" title="Comparison"></a>Comparison</h2><p>The following tables compare all three options: Amazon ECR, Docker Hub, and GitHub Container Registry.</p><div class="table-responsive"><table class="table table-striped"><thead><tr><th></th><th>Amazon ECR</th><th>Docker Hub</th><th>GitHub Container Registry</th></tr></thead><tbody><tr><td>Public Repository</td><td><p>✅ yes</p></td><td><p>✅ yes</p></td><td><p>✅ yes</p></td></tr><tr><td>Private Repository</td><td><p>✅ yes</p></td><td><p>✅ yes</p></td><td><p>✅ yes</p></td></tr><tr><td>Pricing (Public Repository)</td><td><p>💰<br>Storage: $0.10 per GB</p></td><td><p>🆓<br>$0</p></td><td><p>🆓<br>$0</p></td></tr><tr><td>Pricing (Private Repository)</td><td><p>💰<br>Storage: $0.10 per GB, Data Transfer: $0.09 per GB</p></td><td><p>💰💰💰<br>&gt;&#x3D; $7 per user&#x2F;month[^2]</p></td><td><p>💰💰<br>Storage: $0.25 per per GB, Outgoing Data Transfer: $0.50 per GB</p></td></tr><tr><td>Authentication</td><td><p>AWS IAM</p></td><td><p>Password or Access Token</p></td><td><p>Personal Access Token (PAT)</p></td></tr><tr><td>MFA for Image Push/Pull</td><td><p>✅ yes</p></td><td><p>❌ no</p></td><td><p>❌ no</p></td></tr><tr><td>SLA Availability</td><td><p>✅ 99.9%</p></td><td><p>❌ n&#x2F;a</p></td><td><p>⚠️ n&#x2F;a during beta</p></td></tr><tr><td>General Available</td><td><p>✅ yes</p></td><td><p>✅ yes</p></td><td><p>⚠️ beta</p></td></tr><tr><td>Immutable Images</td><td><p>✅ yes</p></td><td><p>❌ no</p></td><td><p>❌ no</p></td></tr><tr><td>Image Scanning</td><td><p>✅ yes</p></td><td><p>✅ yes (paid plans only)</p></td><td><p>❌ no</p></td></tr><tr><td>Regions</td><td><p>Choose between one of 25 regions worldwide</p></td><td><p>⚠️ unknown</p></td><td><p>⚠️ unknown</p></td></tr><tr><td>Rate Limits</td><td><p>Pull: 1,000 per second, Push: 10 per second</p></td><td><p>Pull: 100&#x2F;200 (Free Plan), unlimited (Paid Plan)</p></td><td><p>n&#x2F;a</p></td></tr></tbody></table></div><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>There is no one size fits all solution. Choose the container registry based on your needs for a specific scenario. It is not a big deal to use more than one container registry. Amazon ECR shines for container-based workloads running on AWS. Docker Hub is still the best choice for distributing software publicly. And GitHub Container Registry extends the place where you store your source code with a container registry to store your build artifacts.</p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:5"><p>1. https://docs.docker.com/docker-hub/vulnerability-scanning/ <a href="#fnref:5" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>I'm losing trust in AWS. SNS is broken for 65 days.</title>
      <link>https://cloudonaut.io/loosing-trust-in-aws-sns-broken-for-24-days/</link>
      <description>
        <![CDATA[<p>I’m frustrated. A major service of AWS is broken for 65 days. The Simple Notification Service (SNS) delivers messages to HTTPS subscripti]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/sns/">sns</category>
      <guid isPermaLink="true">https://cloudonaut.io/loosing-trust-in-aws-sns-broken-for-24-days/</guid>
      <pubDate>Thu, 24 Sep 2020 14:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>I’m frustrated. A major service of AWS is broken for 65 days. The Simple Notification Service (SNS) delivers messages to HTTPS subscriptions with a delay of more than 30 minutes. That issue impacts our <a href="https://marbot.io/" target="_blank" rel="noopener">SaaS business</a>. But AWS did not fix the problem yet.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/headache@730w.webp 730w, /images/2020/09/headache@730w2x.webp 1460w, /images/2020/09/headache@610w.webp 610w, /images/2020/09/headache@610w2x.webp 1220w, /images/2020/09/headache@450w.webp 450w, /images/2020/09/headache@450w2x.webp 900w, /images/2020/09/headache@330w.webp 330w, /images/2020/09/headache@330w2x.webp 660w, /images/2020/09/headache@545w.webp 545w, /images/2020/09/headache@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/headache@730w.jpg 730w, /images/2020/09/headache@730w2x.jpg 1460w, /images/2020/09/headache@610w.jpg 610w, /images/2020/09/headache@610w2x.jpg 1220w, /images/2020/09/headache@450w.jpg 450w, /images/2020/09/headache@450w2x.jpg 900w, /images/2020/09/headache@330w.jpg 330w, /images/2020/09/headache@330w2x.jpg 660w, /images/2020/09/headache@545w.jpg 545w, /images/2020/09/headache@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/headache.jpg" alt="I'm losing trust in AWS. SNS is broken for 65 days." title="I'm losing trust in AWS. SNS is broken for 65 days."></picture></p><blockquote><p>You will find the latest updates on the issue at the end of the blog post.</p></blockquote><h2 id="The-Problem"><a href="#The-Problem" class="headerlink" title="The Problem"></a>The Problem</h2><p>Our SaaS business runs on a Serverless architecture. Our customers send all kinds of alarms from their AWS infrastructure to our chatbot. To do so, our solution configures CloudWatch alarms and an SNS topic within our customer’s AWS accounts. The SNS topic forwards all incoming alarms to our API Gateway by using an HTTP subscription.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/sns-delay@730w.webp 730w, /images/2020/09/sns-delay@730w2x.webp 1460w, /images/2020/09/sns-delay@610w.webp 610w, /images/2020/09/sns-delay@610w2x.webp 1220w, /images/2020/09/sns-delay@450w.webp 450w, /images/2020/09/sns-delay@450w2x.webp 900w, /images/2020/09/sns-delay@330w.webp 330w, /images/2020/09/sns-delay@330w2x.webp 660w, /images/2020/09/sns-delay@545w.webp 545w, /images/2020/09/sns-delay@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/sns-delay@730w.png 730w, /images/2020/09/sns-delay@730w2x.png 1460w, /images/2020/09/sns-delay@610w.png 610w, /images/2020/09/sns-delay@610w2x.png 1220w, /images/2020/09/sns-delay@450w.png 450w, /images/2020/09/sns-delay@450w2x.png 900w, /images/2020/09/sns-delay@330w.png 330w, /images/2020/09/sns-delay@330w2x.png 660w, /images/2020/09/sns-delay@545w.png 545w, /images/2020/09/sns-delay@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/sns-delay.png" alt="Our Serverless architecture: SNS sends messages to API Gateway" title="Our Serverless architecture: SNS sends messages to API Gateway"></picture></p><p>On September 1st, a customer wrote in: they observed that CloudWatch alarms showed up in Slack with a delay of more than 30 minutes. A monitoring and incident management solution is kind of worthless with delayed alarms. Therefore, I started investigating immediately.</p><p>First of all, I had a look at the AWS Service Health Dashboard. All systems operating normally.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/aws-service-health-dashboard@730w.webp 730w, /images/2020/09/aws-service-health-dashboard@730w2x.webp 1460w, /images/2020/09/aws-service-health-dashboard@610w.webp 610w, /images/2020/09/aws-service-health-dashboard@610w2x.webp 1220w, /images/2020/09/aws-service-health-dashboard@450w.webp 450w, /images/2020/09/aws-service-health-dashboard@450w2x.webp 900w, /images/2020/09/aws-service-health-dashboard@330w.webp 330w, /images/2020/09/aws-service-health-dashboard@330w2x.webp 660w, /images/2020/09/aws-service-health-dashboard@545w.webp 545w, /images/2020/09/aws-service-health-dashboard@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/aws-service-health-dashboard@730w.png 730w, /images/2020/09/aws-service-health-dashboard@730w2x.png 1460w, /images/2020/09/aws-service-health-dashboard@610w.png 610w, /images/2020/09/aws-service-health-dashboard@610w2x.png 1220w, /images/2020/09/aws-service-health-dashboard@450w.png 450w, /images/2020/09/aws-service-health-dashboard@450w2x.png 900w, /images/2020/09/aws-service-health-dashboard@330w.png 330w, /images/2020/09/aws-service-health-dashboard@330w2x.png 660w, /images/2020/09/aws-service-health-dashboard@545w.png 545w, /images/2020/09/aws-service-health-dashboard@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/aws-service-health-dashboard.png" alt="All systems operating normally?" title="All systems operating normally?"></picture></p><p>Next, I analyzed our log messages to track down the issue. But I could not find any delayed messages—incoming alarms were delivered to Slack within milliseconds after arriving at our API Gateway.</p><p>I was wondering how to find out whether SNS caused the delay. But how to investigate an issue like that? Luckily, I stumbled upon <a href="https://docs.aws.amazon.com/sns/latest/dg/sms_stats_cloudwatch.html#sns-viewing-cloudwatch-logs" target="_blank" rel="noopener">delivery status logging</a>. A SNS topic is capable of writing delivery logs to CloudWatch Logs. The perfect way to debug a problem like that.</p><p>I found log messages similar to this one. SNS sent a message to <code>api.marbot.io</code>, and our API Gateway answered with status code <code>204</code>. SNS tried to deliver the message once. The important information is <code>dwellTimeMs = 2748244</code>. It took SNS about 45 minutes to send the alarm to our backend.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;notification&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;messageMD5Sum&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cce294ff201662ea5c91bd5f7391e086&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;messageId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;60926e45-a356-5d80-8e26-39a3cddfdab5&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;topicArn&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:sns:us-east-1:xxx&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;timestamp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2020-09-24 09:52:09.633&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;delivery&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;deliveryId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;bbd19bef-563e-5d9a-8e7a-cc092f7b993f&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;destination&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://api.marbot.io/v1/endpoint/xxx&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;providerResponse&quot;</span><span class="punctuation">:</span> <span class="string">&quot;No Content&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;dwellTimeMs&quot;</span><span class="punctuation">:</span> <span class="number">2748244</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;attempts&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;statusCode&quot;</span><span class="punctuation">:</span> <span class="number">204</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SUCCESS&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>And this is not an outlier. The same is true for all other messages as well. Immediately, I’ve contacted AWS Support. Unfortunately, the story does not end here.</p><h2 id="A-Workaround"><a href="#A-Workaround" class="headerlink" title="A Workaround"></a>A Workaround</h2><p>After the typical back and forth between the support engineer and the service team, I was told to remove the rate limit from our HTTP subscription. AWS announced that feature in December 2011, so I would expect that to be pretty stable, but hey. I’ve removed the throttling policy - the parameter is named <code>maxReceivesPerSecond</code> - and indeed, doing so fixed the problem.</p><p>Let me clarify. We are using a rate limit of 1 message per second to avoid flooding our API Gateway in case of misconfigured alarms. Typically only a few messages pass the SNS topic per hour! We are far away from reaching the rate limit. Also, we are talking about the rate limit enforced by our API Gateway, but by our customer’s SNS topics or subscriptions. However, there enabling a rate limit on your SNS topics or subscriptions might cause serious delays.</p><p>Fine, there is a workaround for the issue.</p><p>All we have to do, is to remove the <code>throttlePolicy</code> from all SNS topics and subscriptions. Our default delivery retry policy looks like this.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;healthyRetryPolicy&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;minDelayTarget&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;maxDelayTarget&quot;</span><span class="punctuation">:</span> <span class="number">60</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;numRetries&quot;</span><span class="punctuation">:</span> <span class="number">100</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;numMaxDelayRetries&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;numNoDelayRetries&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;numMinDelayRetries&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;backoffFunction&quot;</span><span class="punctuation">:</span> <span class="string">&quot;exponential&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;sicklyRetryPolicy&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;throttlePolicy&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;maxReceivesPerSecond&quot;</span><span class="punctuation">:</span> <span class="number">1</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;guaranteed&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>However, implementing the workaround is a challenge for us. We need to update about 1,000 SNS topics. And unfortunately, those SNS topics are not part of our AWS accounts but are managed by our customers. Therefore it is quite expensive to roll out the recommended workaround.</p><h2 id="Losing-Trust"><a href="#Losing-Trust" class="headerlink" title="Losing Trust"></a>Losing Trust</h2><p>We love our Serverless architecture. However, our business depends on AWS to operate all the involved services (SNS, API Gateway, Lambda, DynamoDB, Kinesis, Step Functions, etc.) professionally and fix any issues within hours.</p><p>Unfortunately, more than 24 days passed, and AWS did not fix the problem. SNS messages are still delayed. Neither AWS Support nor any AWS employee that I have contacted could help with the issue. Until today, we do not even know when AWS is planning to fix the problem - “Unfortunately, I cannot provide any ETA.”. The AWS Service Health Dashboard still states <em>All systems operating normally.</em> It is not!</p><p>I’m building on AWS for more than eight years. I’ve never experienced anything like that before. My trust in AWS has strongly decreased over the past month.</p><p>Want to learn more about debugging delayed SNS subscriptions? Watch the following video!</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=b9r6Tlbij6Q">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/b9r6Tlbij6Q" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="1st-Update-September-29th-2020"><a href="#1st-Update-September-29th-2020" class="headerlink" title="1st Update (September 29th, 2020)"></a>1st Update (September 29th, 2020)</h2><p>This blog post gained traction. It did not take too long until AWS wrote in. I had some background talks about our issue. Not sure what I am allowed to share from these conversations. Luckily, Jeff Barr - Chief Evangelist for AWS - posted a <a href="https://www.reddit.com/r/aws/comments/izbcu8/im_losing_trust_in_aws_sns_is_broken_for_24_days/g6ord4t/" target="_blank" rel="noopener">response to our blog post on Reddit</a> publicly. Let me summarize the reaction for you.</p><ol><li>The SNS team acknowledges an issue with HTTP subscriptions when using a throttling policy (<code>maxReceivesPerSecond</code>).</li><li>The SNS team points out that only a few customers&#x2F;messages are affected by the issue.</li><li>The SNS team promises to fix the problem until October 29, 2020.</li></ol><p>I’ve collected some data to quantify the number of affected customers on our side.</p><blockquote><p>20 % of our customers receive at least 50% of their alarms with a delay of more than 15 minutes.</p></blockquote><p>Also, based on the data we collected, it seems to me that some regions are more affected than others. The following table shows the 90th percentile for a period of 24 hours for different regions.</p><table class="table table-striped table-responsive"><thead><tr><th>Region</th><th style="text-align:right">Delay (ms)</th></tr></thead><tbody><tr><td>us-east-1</td><td style="text-align:right">7011903</td></tr><tr><td>us-west-2</td><td style="text-align:right">1020785</td></tr><tr><td>ap-southeast-1</td><td style="text-align:right">178609</td></tr><tr><td>eu-central-1</td><td style="text-align:right">93179</td></tr><tr><td>sa-east-1</td><td style="text-align:right">89614</td></tr><tr><td>eu-west-3</td><td style="text-align:right">65389</td></tr><tr><td>ap-southeast-2</td><td style="text-align:right">36381</td></tr><tr><td>eu-west-2</td><td style="text-align:right">22211</td></tr><tr><td>eu-west-1</td><td style="text-align:right">1893</td></tr><tr><td>ap-south-1</td><td style="text-align:right">831</td></tr><tr><td>ap-northeast-1</td><td style="text-align:right">699</td></tr><tr><td>us-east-2</td><td style="text-align:right">367</td></tr><tr><td>us-west-1</td><td style="text-align:right">350</td></tr><tr><td>ca-central-1</td><td style="text-align:right">331</td></tr></tbody></table><p>I’m happy to hear that AWS is working on fixing the issue that causes us many troubles. I’m a little bit disappointed that we might have to wait another month for a fix. Let’s hope that AWS learned from that issue and will improve their processes to escalate issues like that much faster.</p><h2 id="2nd-Update-October-6th-2020"><a href="#2nd-Update-October-6th-2020" class="headerlink" title="2nd Update (October 6th, 2020)"></a>2nd Update (October 6th, 2020)</h2><p>AWS informed customers about SNS via email on October 6th. The email included a list of potential affected SNS topics.</p><ul><li>The message delay depends on the <code>maxReceivesPerSecond</code> attribute. Delay starts at a <code>maxReceivesPerSecond</code> of less than 20,000.</li><li>Again, AWS promised to fix the issue until October 29th, 2020.</li><li>AWS explains that disabling the throttling policy is a workaround for the issue.</li></ul><h2 id="3rd-Update-November-4th-2020"><a href="#3rd-Update-November-4th-2020" class="headerlink" title="3rd Update (November 4th, 2020)"></a>3rd Update (November 4th, 2020)</h2><p>The deadline has passed. AWS did not fix the issue with delayed SNS messages yet. Our SaaS is still affected by the problem. I’m frustrated that AWS did not inform other affected customers and us about postponing the ETA for a fix.</p><h2 id="4th-Update-November-13th-2020"><a href="#4th-Update-November-13th-2020" class="headerlink" title="4th Update (November 13th, 2020)"></a>4th Update (November 13th, 2020)</h2><p>AWS did finally fix the issue. We are not observing delayed SNS messages any more. We have been waiting for that for 74 days. Hurray!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS needs a bug bounty program</title>
      <link>https://cloudonaut.io/aws-needs-a-bug-bounty-program/</link>
      <description>
        <![CDATA[<p>A few weeks ago, while evaluating an AWS service, I stumbled upon an issue with the way the AWS API evaluates IAM policies for a particul]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-needs-a-bug-bounty-program/</guid>
      <pubDate>Thu, 17 Sep 2020 08:45:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>A few weeks ago, while evaluating an AWS service, I stumbled upon an issue with the way the AWS API evaluates IAM policies for a particular IAM action. I contacted <a href="mailto:&#x61;&#x77;&#x73;&#x2d;&#x73;&#x65;&#99;&#117;&#114;&#x69;&#116;&#121;&#64;&#x61;&#109;&#97;&#122;&#111;&#x6e;&#46;&#99;&#x6f;&#109;">aws-security@amazon.com</a> about that and was positively surprised about the professionalism in which the team handled my request. In the end, my reported issue was classified as a <em>won’t fix</em>. AWS updated the documentation to clarify the behavior. Fine!</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/hacker@730w.webp 730w, /images/2020/09/hacker@730w2x.webp 1460w, /images/2020/09/hacker@610w.webp 610w, /images/2020/09/hacker@610w2x.webp 1220w, /images/2020/09/hacker@450w.webp 450w, /images/2020/09/hacker@450w2x.webp 900w, /images/2020/09/hacker@330w.webp 330w, /images/2020/09/hacker@330w2x.webp 660w, /images/2020/09/hacker@545w.webp 545w, /images/2020/09/hacker@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/hacker@730w.jpg 730w, /images/2020/09/hacker@730w2x.jpg 1460w, /images/2020/09/hacker@610w.jpg 610w, /images/2020/09/hacker@610w2x.jpg 1220w, /images/2020/09/hacker@450w.jpg 450w, /images/2020/09/hacker@450w2x.jpg 900w, /images/2020/09/hacker@330w.jpg 330w, /images/2020/09/hacker@330w2x.jpg 660w, /images/2020/09/hacker@545w.jpg 545w, /images/2020/09/hacker@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/hacker.jpg" alt="AWS is missing a bug bounty program" title="AWS is missing a bug bounty program"></picture></p><p>The whole process made me think about how AWS handles vulnerability reports from its customers and ethical hackers. And I realized that AWS does not offer a bug bounty program. That’s a poor choice, in my opinion.</p><h2 id="What"><a href="#What" class="headerlink" title="What?"></a>What?</h2><p>What is a bug bounty program? The deal is simple. An individual reports a bug to an organization and receives compensation in return. Most often, those bug bounty programs focus on security exploits and vulnerabilities. The bug bounty program sets the rules for reporting a bug and receiving compensation, typically based on severity. For example, when reporting a bug that could lead to remote code execution on Azure, Microsoft will pay you up to $40,000.<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup></p><h2 id="Why"><a href="#Why" class="headerlink" title="Why?"></a>Why?</h2><p>In my opinion, having a public bug bounty program is essential for two reasons:</p><ol><li>Filing a high-quality bug report is a lot of work. Customers who do the extra work of reporting their observations should be compensated. Otherwise, some bugs will never be reported.</li><li>Attracting independent security experts - some call them ethical hackers - to uncover vulnerabilities provides an extra layer of protection.</li></ol><p>I can’t find a reason for not having a bug bounty program.</p><h2 id="Bug-Bounty-Program"><a href="#Bug-Bounty-Program" class="headerlink" title="Bug Bounty Program?"></a>Bug Bounty Program?</h2><p>Let’s compare the major cloud providers</p><table class="table table-striped table-responsive"><thead><tr><th>✅ Yes</th><th>❌ No</th></tr></thead><tbody><tr><td>✅ Microsoft Azure<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup></td><td>❌ Amazon Web Services</td></tr><tr><td>✅ Google Cloud Platform<sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup></td><td>❌ Oracle Cloud</td></tr><tr><td>✅ Alibaba Cloud<sup><a href="#fn:3" id="fnref:3" class="footnote-ref">3</a></sup></td><td>❌ IBM Cloud<sup><a href="#fn:4" id="fnref:4" class="footnote-ref">4</a></sup></td></tr><tr><td>✅ Tencent Cloud<sup><a href="#fn:5" id="fnref:5" class="footnote-ref">5</a></sup></td><td></td></tr></tbody></table><p>Four of seven cloud providers offer a bug bounty program. Unfortunately, the market leader Amazon Web Services, does not. Even though AWS never misses an opportunity to assure security is their top priority. If these are not empty promises, I expect AWS to launch a bug bounty program soon!</p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. https://www.microsoft.com/en-us/msrc/bounty-microsoft-azure <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. https://www.google.com/about/appsecurity/reward-program/ <a href="#fnref:2" class="footnote-backref">↩</a></p></li><li id="fn:3"><p>3. https://hackerone.com/alibaba <a href="#fnref:3" class="footnote-backref">↩</a></p></li><li id="fn:4"><p>4. https://hackerone.com/ibm but does not offer any bounties <a href="#fnref:4" class="footnote-backref">↩</a></p></li><li id="fn:5"><p>5. https://hackerone.com/tencent <a href="#fnref:5" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Record AWS API calls to improve IAM Policies</title>
      <link>https://cloudonaut.io/record-aws-api-calls-to-improve-iam-policies/</link>
      <description>
        <![CDATA[<p>Have you ever looked at an IAM policy and wondered: Is it really necessary to grant access to this specific action? Or do you need to kno]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/record-aws-api-calls-to-improve-iam-policies/</guid>
      <pubDate>Fri, 11 Sep 2020 10:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Have you ever looked at an IAM policy and wondered: Is it really necessary to grant access to this specific action? Or do you need to know which API calls a legacy or 3rd party application is actually sending to come up with a secure IAM policy? CloudTrail can help here, but there is something better: Record API calls with the AWS SDKs and CLI (including the stuff that is not visible in CloudTrail).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/record@730w.webp 730w, /images/2020/09/record@730w2x.webp 1460w, /images/2020/09/record@610w.webp 610w, /images/2020/09/record@610w2x.webp 1220w, /images/2020/09/record@450w.webp 450w, /images/2020/09/record@450w2x.webp 900w, /images/2020/09/record@330w.webp 330w, /images/2020/09/record@330w2x.webp 660w, /images/2020/09/record@545w.webp 545w, /images/2020/09/record@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/record@730w.jpg 730w, /images/2020/09/record@730w2x.jpg 1460w, /images/2020/09/record@610w.jpg 610w, /images/2020/09/record@610w2x.jpg 1220w, /images/2020/09/record@450w.jpg 450w, /images/2020/09/record@450w2x.jpg 900w, /images/2020/09/record@330w.jpg 330w, /images/2020/09/record@330w2x.jpg 660w, /images/2020/09/record@545w.jpg 545w, /images/2020/09/record@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/record.jpg" alt="Record AWS API calls" title="Record AWS API calls"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/27-record-aws-api-calls-to-improve-iam-policies/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>In this blog post, you learn to capture the data without touching source code. You also analyze the data and use the results to improve your IAM policies.</p><h2 id="Record-AWS-API-calls-to-improve-IAM-Policies-in-Action"><a href="#Record-AWS-API-calls-to-improve-IAM-Policies-in-Action" class="headerlink" title="Record AWS API calls to improve IAM Policies in Action"></a>Record AWS API calls to improve IAM Policies in Action</h2><p>Watch the following video to learn how to record AWS API calls to improve your IAM Policies.</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=Aepu7AiTFRk">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/Aepu7AiTFRk" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><h2 id="Turning-Client-Side-Monitoring-on"><a href="#Turning-Client-Side-Monitoring-on" class="headerlink" title="Turning Client Side Monitoring on"></a>Turning Client Side Monitoring on</h2><p>All AWS SDKs and the AWS CLI support “Client Side Monitoring (CSM)”. Luckily, most applications use AWS SDKs to integrate with AWS. If you enable CSM, each API request is reported via UDP on port 31000. You can turn on CSM by setting the environment variable <code>AWS_CSM_ENABLED</code> to <code>true</code> or via the shared config file <code>~/.aws/config</code>:</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[default]</span></span><br><span class="line"><span class="attr">csm_enabled</span> = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="section">[profile profile1]</span></span><br><span class="line"><span class="attr">csm_enabled</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>Warning</strong> Keep in mind that you need to add the <code>csm_enabled</code> setting for each Linux user, e.g.:<br><code>/root/.aws/config</code><br><code>/home/ec2-user/.aws/config</code></p></blockquote><blockquote><p><strong>Warning</strong> Keep in mind that you have to restart the process that uses an AWS SDK after changing the config!</p></blockquote><p>You can check if API calls are reported with this command:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">tcpdump -i lo -n udp port 31000 -A</span><br></pre></td></tr></table></figure><p>To debug a process where no data shows up, get the PID of the process with <code>ps -ef</code>, and inspect the environment variables:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">xargs -0 -L1 -a /proc/PID/environ</span><br></pre></td></tr></table></figure><p>If <code>HOME</code> or <code>AWS_CSM_ENABLED</code> are not defined, CSM will not work. If <code>AWS_CONFIG_FILE</code> is defined, you have to edit that file and append <code>csm_enabled = true</code>.</p><p>If the process is started by <code>systemd</code>, edit the unit file (e.g., <code>/usr/lib/systemd/system/amazon-ssm-agent.service</code>) and turn on <code>AWS_CSM_ENABLED</code> in the <code>[Service]</code> section:</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[Service]</span></span><br><span class="line"><span class="attr">Environment</span>=AWS_CSM_ENABLED=<span class="literal">true</span></span><br></pre></td></tr></table></figure><p>Next, you will learn how to capture and archive the CSM data to S3. Doing so allows you to analyze the data with the help of Athena later.</p><h2 id="Capturing-the-data"><a href="#Capturing-the-data" class="headerlink" title="Capturing the data"></a>Capturing the data</h2><p>First, create an S3 bucket for storing the data.</p><p><a href="https://www.fluentd.org/" target="_blank" rel="noopener">fluentd</a> can listen on a UDP port, transform and buffer the data, and upload it to S3.</p><p>On Amazon Linux 2 EC2 instance, installing <code>fluentd</code> is a one-liner (<a href="https://www.fluentd.org/download" target="_blank" rel="noopener">other distros are supported as well</a>):</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl -L https://toolbelt.treasuredata.com/sh/install-amazon2-td-agent4.sh | sh</span><br></pre></td></tr></table></figure><p>Allow your EC2 instance to interact with the newly created bucket (replace <code>BUCKET_NAME</code> with the name of your bucket) by adding the following IAM policy:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2012-10-17&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Statement&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;s3:GetObject&quot;</span><span class="punctuation">,</span> <span class="string">&quot;s3:PutObject&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:s3:::BUCKET_NAME/awscsm/*&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="string">&quot;s3:ListBucket&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:s3:::BUCKET_NAME&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Last but not least, configure <code>fluentd</code> by replacing the file <code>/etc/td-agent/td-agent.conf</code> with the following content (replace <code>BUCKET_NAME</code> with the name of your bucket, and <code>REGION</code> with your AWS region (e.g., us-east-1)):</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">&lt;<span class="built_in">source</span>&gt;</span><br><span class="line">  @<span class="built_in">type</span> udp</span><br><span class="line">  tag awscsm</span><br><span class="line">  &lt;parse&gt;</span><br><span class="line">    @<span class="built_in">type</span> json</span><br><span class="line">  &lt;/parse&gt;</span><br><span class="line">  port 31000</span><br><span class="line">  <span class="built_in">bind</span> 127.0.0.1</span><br><span class="line">&lt;/source&gt;</span><br><span class="line">&lt;filter awscsm&gt;</span><br><span class="line">  @<span class="built_in">type</span> record_transformer</span><br><span class="line">  &lt;record&gt;</span><br><span class="line">    hostname <span class="string">&quot;#&#123;Socket.gethostname&#125;&quot;</span></span><br><span class="line">  &lt;/record&gt;</span><br><span class="line">&lt;/filter&gt;</span><br><span class="line">&lt;match awscsm&gt;</span><br><span class="line">  @<span class="built_in">type</span> s3</span><br><span class="line">  s3_region REGION</span><br><span class="line">  s3_bucket BUCKET_NAME</span><br><span class="line">  check_apikey_on_start <span class="literal">false</span></span><br><span class="line">  check_bucket <span class="literal">false</span></span><br><span class="line">  path <span class="variable">$&#123;tag&#125;</span>/year=%Y/month=%m/day=%d/</span><br><span class="line">  &lt;buffer tag,<span class="keyword">time</span>&gt;</span><br><span class="line">    @<span class="built_in">type</span> memory</span><br><span class="line">    timekey 10m</span><br><span class="line">    timekey_use_utc <span class="literal">true</span></span><br><span class="line">    timekey_wait 1m</span><br><span class="line">    chunk_limit_size 256m</span><br><span class="line">  &lt;/buffer&gt;</span><br><span class="line">  &lt;format&gt;</span><br><span class="line">    @<span class="built_in">type</span> json</span><br><span class="line">  &lt;/format&gt;</span><br><span class="line">&lt;/match&gt;</span><br></pre></td></tr></table></figure><p>Activate the new <code>fluentd</code> configuration with this command:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">systemctl start td-agent.service</span><br></pre></td></tr></table></figure><p>If data comes in, <code>fluentd</code> uploads a file to S3 every 10 minutes (or every 256 MB) with a 1-minute delay.</p><p>I recommend waiting for a couple of days to capture enough data.</p><h2 id="Analyzing-the-data"><a href="#Analyzing-the-data" class="headerlink" title="Analyzing the data"></a>Analyzing the data</h2><p>Create an AWS Glue Crawler that looks into <code>s3://BUCKET_NAME/awscsm/</code> every hour. Run the crawler manually to speed up table creation for the first time.</p><p>After the crawler finished, switch to Athena. There you will find a table <strong>awscsm</strong> that you can query.</p><p>To get an idea of how the data looks like, run:</p><figure class="highlight n1ql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> awscsm <span class="keyword">LIMIT</span> <span class="number">25</span></span><br></pre></td></tr></table></figure><p>Only get API calls, but not the attempts (one call can have multiple attempts):</p><figure class="highlight n1ql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> * <span class="keyword">FROM</span> awscsm <span class="keyword">WHERE</span> <span class="built_in">type</span>=<span class="string">&#x27;ApiCall&#x27;</span> <span class="keyword">LIMIT</span> <span class="number">25</span></span><br></pre></td></tr></table></figure><p>Get the most popular API calls:</p><figure class="highlight n1ql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> service, api, <span class="built_in">COUNT</span>(*) <span class="keyword">as</span> <span class="built_in">count</span> <span class="keyword">FROM</span> awscsm <span class="keyword">WHERE</span> <span class="built_in">type</span>=<span class="string">&#x27;ApiCall&#x27;</span> <span class="keyword">GROUP</span> <span class="keyword">by</span> service, api <span class="keyword">ORDER</span> <span class="keyword">BY</span> <span class="built_in">count</span> <span class="keyword">DESC</span> <span class="keyword">LIMIT</span> <span class="number">25</span></span><br></pre></td></tr></table></figure><h2 id="Comparing-with-CloudTrail"><a href="#Comparing-with-CloudTrail" class="headerlink" title="Comparing with CloudTrail"></a>Comparing with CloudTrail</h2><p>I use the following CloudWatch Insights query to get the most popular API calls (replace <code>IAM_ROLE_NAME</code> with the name of the IAM role attached to your EC2 instance):</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">fields @<span class="type">timestamp</span>, @message</span><br><span class="line">| <span class="keyword">filter</span> userIdentity.arn <span class="keyword">like</span> &quot;IAM_ROLE_NAME&quot;</span><br><span class="line">| stats count() <span class="keyword">as</span> count <span class="keyword">by</span> eventSource, eventName</span><br><span class="line">| sort count <span class="keyword">desc</span></span><br><span class="line">| <span class="keyword">limit</span> <span class="number">25</span></span><br></pre></td></tr></table></figure><p>The top calls look entirely different. From CSM, I get:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/api-calls-csm@730w.webp 730w, /images/2020/09/api-calls-csm@730w2x.webp 1460w, /images/2020/09/api-calls-csm@610w.webp 610w, /images/2020/09/api-calls-csm@610w2x.webp 1220w, /images/2020/09/api-calls-csm@450w.webp 450w, /images/2020/09/api-calls-csm@450w2x.webp 900w, /images/2020/09/api-calls-csm@330w.webp 330w, /images/2020/09/api-calls-csm@330w2x.webp 660w, /images/2020/09/api-calls-csm@545w.webp 545w, /images/2020/09/api-calls-csm@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/api-calls-csm@730w.png 730w, /images/2020/09/api-calls-csm@730w2x.png 1460w, /images/2020/09/api-calls-csm@610w.png 610w, /images/2020/09/api-calls-csm@610w2x.png 1220w, /images/2020/09/api-calls-csm@450w.png 450w, /images/2020/09/api-calls-csm@450w2x.png 900w, /images/2020/09/api-calls-csm@330w.png 330w, /images/2020/09/api-calls-csm@330w2x.png 660w, /images/2020/09/api-calls-csm@545w.png 545w, /images/2020/09/api-calls-csm@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/api-calls-csm.png" alt="API calls captured with CSM" title="API calls captured with CSM"></picture></p><p>And from CloudTrail, I get:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/api-calls-cloudtrail@730w.webp 730w, /images/2020/09/api-calls-cloudtrail@730w2x.webp 1460w, /images/2020/09/api-calls-cloudtrail@610w.webp 610w, /images/2020/09/api-calls-cloudtrail@610w2x.webp 1220w, /images/2020/09/api-calls-cloudtrail@450w.webp 450w, /images/2020/09/api-calls-cloudtrail@450w2x.webp 900w, /images/2020/09/api-calls-cloudtrail@330w.webp 330w, /images/2020/09/api-calls-cloudtrail@330w2x.webp 660w, /images/2020/09/api-calls-cloudtrail@545w.webp 545w, /images/2020/09/api-calls-cloudtrail@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/api-calls-cloudtrail@730w.png 730w, /images/2020/09/api-calls-cloudtrail@730w2x.png 1460w, /images/2020/09/api-calls-cloudtrail@610w.png 610w, /images/2020/09/api-calls-cloudtrail@610w2x.png 1220w, /images/2020/09/api-calls-cloudtrail@450w.png 450w, /images/2020/09/api-calls-cloudtrail@450w2x.png 900w, /images/2020/09/api-calls-cloudtrail@330w.png 330w, /images/2020/09/api-calls-cloudtrail@330w2x.png 660w, /images/2020/09/api-calls-cloudtrail@545w.png 545w, /images/2020/09/api-calls-cloudtrail@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/api-calls-cloudtrail.png" alt="API calls captured with CloudTrail" title="API calls captured with CloudTrail"></picture></p><p>As you can see, CloudTrail does not capture most of the “data” events. Unfortunately, most calls of a typical application are categorized as “data” events.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Securing IAM policies of running systems is hard. You need all available data to reduce the risk of accidentally removing permissions required by the system. CloudTrail provides a good foundation. Unfortunately, not all API calls are visible in CloudTrail. E.g., <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-logging-using-cloudtrail.html" target="_blank" rel="noopener">SQS “data events” are not captured by CloudTrail</a>. Client Side Monitoring (CSM) can be used to capture the calls that are made with AWS SDKs and the AWS CLI. Both sources combined can help you to detect IAM permissions that are not needed anymore.</p><p>Thanks, <a href="https://summitroute.com/blog/2020/05/25/client_side_monitoring/" target="_blank" rel="noopener">Scott Piper</a>, for bringing CSM to my attention.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>NoSQL on AWS: Document-Oriented Databases</title>
      <link>https://cloudonaut.io/nosql-on-aws-document-oriented-databases/</link>
      <description>
        <![CDATA[<p>A document-oriented database stores keys mapped to JSON documents. You can query all documents in such a document-oriented database and r]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/dynamodb/">dynamodb</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <category domain="https://cloudonaut.io/tag/documentdb/">documentdb</category>
      <guid isPermaLink="true">https://cloudonaut.io/nosql-on-aws-document-oriented-databases/</guid>
      <pubDate>Thu, 03 Sep 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>A document-oriented database stores keys mapped to JSON documents. You can query all documents in such a document-oriented database and retrieve only parts of documents to save network bandwidth.</p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/nosql@730w.webp 730w, /images/2020/09/nosql@730w2x.webp 1460w, /images/2020/09/nosql@610w.webp 610w, /images/2020/09/nosql@610w2x.webp 1220w, /images/2020/09/nosql@450w.webp 450w, /images/2020/09/nosql@450w2x.webp 900w, /images/2020/09/nosql@330w.webp 330w, /images/2020/09/nosql@330w2x.webp 660w, /images/2020/09/nosql@545w.webp 545w, /images/2020/09/nosql@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/nosql@730w.jpg 730w, /images/2020/09/nosql@730w2x.jpg 1460w, /images/2020/09/nosql@610w.jpg 610w, /images/2020/09/nosql@610w2x.jpg 1220w, /images/2020/09/nosql@450w.jpg 450w, /images/2020/09/nosql@450w2x.jpg 900w, /images/2020/09/nosql@330w.jpg 330w, /images/2020/09/nosql@330w2x.jpg 660w, /images/2020/09/nosql@545w.jpg 545w, /images/2020/09/nosql@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/nosql.jpg" alt="NoSQL on AWS" title="NoSQL on AWS"></picture></p><p>A data model for a document-oriented database looks different from a relation model where you usually have many tables that reference each other. In an online shop, you could easily have tens of tables, like users, settings, sessions, favorites, and carts, while you might store all of that in a single user document. All 1:1, 1:n relationships, can be stored in one document. If you have many m:1, or m:n relationships you likely run into issues with document-oriented databases.</p><p>Many NoSQL databases (all that I highlight in the article) allow you to scale workloads horizontally by adding more nodes. Horizontal scaling is usually cheaper than scaling vertically, where hardware gets costly at the end of the spectrum. It is still difficult to compare the scalability options because some NoSQL databases only allow to scale reads horizontally or have some upper limits on the number of nodes that can be added.</p><p>Keep in mind that most NoSQL databases use the concept of eventual consistency to support the system’s scalability. If you need read-after-write consistency, you have to look for stronger guarantees. Some NoSQL systems provide options to read with strong or eventual consistency to allow your application to optimize database access. </p><p>After <a href="https://blog.cloudcraft.co/databases-on-aws/" target="_blank" rel="noopener">comparing all database options on AWS</a>, I will discuss the document-oriented database offerings on AWS in this article. </p><h2 id="DynamoDB"><a href="#DynamoDB" class="headerlink" title="DynamoDB"></a>DynamoDB</h2><p><a href="https://aws.amazon.com/dynamodb/" target="_blank" rel="noopener">Amazon DynamoDB</a> is a database with virtually unlimited scaling. Both in terms of storage and queries per second. Documents (aka items) can have a size of up to 400 KB and are stored in a table that can be of infinite size1. You pay for the used storage and read and write activity either on-demand or by provisioned capacity.</p><p>The core assumptions of DynamoDB are:</p><ul><li>Items are accessed in an evenly distributed manner: Avoid “hot” items that are accessed significantly more often than other items.</li><li>Items are independent: Only one write operation can run on for a single item at a time. Use transactions if you need to change multiple items in an atomic operation.</li></ul><p>The following best practices should be followed:</p><ul><li>Items cannot be sorted in a table (there is no ORDER BY) unless you provide a sort key and&#x2F;or a local secondary index.</li><li>When reading items, you have to select the index that you use. There is no query optimizer that ensures that your query is executed most efficiently! The query is executed exactly in the way you define it.</li><li>Avoid scanning a table to retrieve items. Look up items based on their key. You can create global secondary indexes if you need to lookup an item based on multiple attributes.</li></ul><p>You can deploy DynamoDB into multiple AWS regions and keep the data in sync. This enables users all over the world to access their data with minimal latency. If a user boards a plane in Germany, she can still access her data in the US with minimum latency.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/dynamodb@730w.webp 730w, /images/2020/09/dynamodb@730w2x.webp 1460w, /images/2020/09/dynamodb@610w.webp 610w, /images/2020/09/dynamodb@610w2x.webp 1220w, /images/2020/09/dynamodb@450w.webp 450w, /images/2020/09/dynamodb@450w2x.webp 900w, /images/2020/09/dynamodb@330w.webp 330w, /images/2020/09/dynamodb@330w2x.webp 660w, /images/2020/09/dynamodb@545w.webp 545w, /images/2020/09/dynamodb@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/dynamodb@730w.png 730w, /images/2020/09/dynamodb@730w2x.png 1460w, /images/2020/09/dynamodb@610w.png 610w, /images/2020/09/dynamodb@610w2x.png 1220w, /images/2020/09/dynamodb@450w.png 450w, /images/2020/09/dynamodb@450w2x.png 900w, /images/2020/09/dynamodb@330w.png 330w, /images/2020/09/dynamodb@330w2x.png 660w, /images/2020/09/dynamodb@545w.png 545w, /images/2020/09/dynamodb@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/dynamodb.png" alt="DynamoDB" title="DynamoDB"></picture></p><h2 id="DocumentDB"><a href="#DocumentDB" class="headerlink" title="DocumentDB"></a>DocumentDB</h2><p><a href="https://aws.amazon.com/documentdb/" target="_blank" rel="noopener">Amazon DocumentDB</a> provides a “mostly” <a href="https://docs.aws.amazon.com/documentdb/latest/developerguide/mongo-apis.html" target="_blank" rel="noopener">MongoDB 3.6 API compatible</a> database hosted by AWS.</p><p>A shortlist of missing features:</p><ul><li>Storage compression not available (when migrating from MongoDB to DocumentDB your storage usage likely grows)</li><li>Geospatial operations not available</li><li>Map-Reduce operation not available</li><li>GridFS not available</li></ul><p><a href="https://www.isdocumentdbreallymongodb.com/" target="_blank" rel="noopener">According to MongoDB</a>, DocumentDB is about 37% compatible with the latest MongoDB Atlas feature set. DocumentDB is powered by Aurora storage technology and can scale horizontally up to 15 read replicas to scale read workloads. Writes can only be scaled vertically. I created the following cloud diagram with Cloudcraft to visualize the architecture.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/documentdb@730w.webp 730w, /images/2020/09/documentdb@730w2x.webp 1460w, /images/2020/09/documentdb@610w.webp 610w, /images/2020/09/documentdb@610w2x.webp 1220w, /images/2020/09/documentdb@450w.webp 450w, /images/2020/09/documentdb@450w2x.webp 900w, /images/2020/09/documentdb@330w.webp 330w, /images/2020/09/documentdb@330w2x.webp 660w, /images/2020/09/documentdb@545w.webp 545w, /images/2020/09/documentdb@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/documentdb@730w.png 730w, /images/2020/09/documentdb@730w2x.png 1460w, /images/2020/09/documentdb@610w.png 610w, /images/2020/09/documentdb@610w2x.png 1220w, /images/2020/09/documentdb@450w.png 450w, /images/2020/09/documentdb@450w2x.png 900w, /images/2020/09/documentdb@330w.png 330w, /images/2020/09/documentdb@330w2x.png 660w, /images/2020/09/documentdb@545w.png 545w, /images/2020/09/documentdb@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/documentdb.png" alt="DocumentDB" title="DocumentDB"></picture></p><p>You pay for the number of database nodes that are running, used storage, and disk IO.</p><h2 id="MongoDB-Atlas"><a href="#MongoDB-Atlas" class="headerlink" title="MongoDB Atlas"></a>MongoDB Atlas</h2><p><a href="https://www.mongodb.com/cloud/atlas" target="_blank" rel="noopener">MongoDB Atlas</a> is a service offered by MongoDB, not Amazon. The MongoDB cluster runs in an AWS account operated by MongoDB. As the following figure shows, you can get private network access to the cluster using <a href="https://docs.aws.amazon.com/vpc/latest/userguide/vpc-peering.html" target="_blank" rel="noopener">VPC Peering</a> or <a href="https://aws.amazon.com/privatelink/" target="_blank" rel="noopener">AWS Private Link</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/09/mongodb@730w.webp 730w, /images/2020/09/mongodb@730w2x.webp 1460w, /images/2020/09/mongodb@610w.webp 610w, /images/2020/09/mongodb@610w2x.webp 1220w, /images/2020/09/mongodb@450w.webp 450w, /images/2020/09/mongodb@450w2x.webp 900w, /images/2020/09/mongodb@330w.webp 330w, /images/2020/09/mongodb@330w2x.webp 660w, /images/2020/09/mongodb@545w.webp 545w, /images/2020/09/mongodb@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/09/mongodb@730w.png 730w, /images/2020/09/mongodb@730w2x.png 1460w, /images/2020/09/mongodb@610w.png 610w, /images/2020/09/mongodb@610w2x.png 1220w, /images/2020/09/mongodb@450w.png 450w, /images/2020/09/mongodb@450w2x.png 900w, /images/2020/09/mongodb@330w.png 330w, /images/2020/09/mongodb@330w2x.png 660w, /images/2020/09/mongodb@545w.png 545w, /images/2020/09/mongodb@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/09/mongodb.png" alt="MongoDB Atlas" title="MongoDB Atlas"></picture></p><p>The biggest advantage is that it supports all features of MongoDB and the latest versions. MongoDB provides features that are closer to traditional databases. You can run a query without thinking about keys, indexes. The query optimizer will figure this out for you.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>The following table provides a comparison of the different managed document-oriented database options on AWS.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th>DynamoDB</th><th>DocumentDB</th><th>MongoDB Atlas</th></tr></thead><tbody><tr><td>Consistency</td><td>✅ Consistent reads are possible</td><td>⚠️ Consistent reads only from primary node (limits scalability)</td><td>✅ Consistent reads are possible</td></tr><tr><td>Max. document size</td><td>400 KB</td><td>16 MB</td><td>16 MB</td></tr><tr><td>Max. table size</td><td>unlimited<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup></td><td>32 TB</td><td>32 TB</td></tr><tr><td>Max. Indexes / table</td><td>Global: 20<sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup>, Local: 5</td><td>64</td><td>64</td></tr><tr><td>Query optimizer</td><td>❌ no</td><td>✅ yes</td><td>✅ yes</td></tr><tr><td>Pricing</td><td>on-demand or provisioned</td><td>database layer: provisioned, storage layer: on-demand</td><td>provisioned</td></tr><tr><td>Smallest HA setup costs</td><td>$0.25 / GB / month plus reads / writes</td><td>$400 / month plus $0.10 / GB / month plus reads / writes</td><td>$115 / month plus storage if you need more than provided by default</td></tr><tr><td>Global replication</td><td>✅ yes</td><td>❌ no</td><td>✅ yes</td></tr><tr><td>Transactions</td><td>✅ yes</td><td>✅ yes</td><td>✅ yes</td></tr><tr><td>Scaling</td><td>horizontally</td><td>reads: horizontally (max. 15 nodes), writes: vertically (max. 1 node)</td><td>horizontally (max. 402 nodes)</td></tr><tr><td>Auto Scaling</td><td>✅ yes</td><td>⚠️ database layer: no, storage layer: yes</td><td>✅ yes</td></tr><tr><td>Backups</td><td>✅ yes</td><td>✅ yes</td><td>✅ yes</td></tr></tbody></table><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LSI.html#LSI.ItemCollections">All Items with the same partition key (but different sort key) must be &lt;= 10GB (includes local secondary indexes as well).</a> <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. Vendor might increase this limit. <a href="#fnref:2" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Review: AWS App Mesh – A service mesh for EC2, ECS, and EKS</title>
      <link>https://cloudonaut.io/review-aws-app-mesh-service-mesh-ec2-ecs-eks/</link>
      <description>
        <![CDATA[<p>It seems to me like everyone is talking about service meshes these days - definitely a hot topic in the world of containers and microserv]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <category domain="https://cloudonaut.io/tag/eks/">eks</category>
      <category domain="https://cloudonaut.io/tag/review/">review</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <category domain="https://cloudonaut.io/tag/appmesh/">appmesh</category>
      <guid isPermaLink="true">https://cloudonaut.io/review-aws-app-mesh-service-mesh-ec2-ecs-eks/</guid>
      <pubDate>Tue, 25 Aug 2020 22:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>It seems to me like everyone is talking about service meshes these days - definitely a hot topic in the world of containers and microservices. A service mesh promises reducing latency, increasing observability, and simplifying security within microservice architectures. AWS announced a preview for App Mesh in November 2018 and the general availability in March 2019. Therefore, it is about time to take a closer look at App Mesh. As always, my review focuses on the technical details and educates about pitfalls. There is a lot more to know about the service than written on the <a href="https://aws.amazon.com/app-mesh/" target="_blank" rel="noopener">official marketing page</a> or demonstrated by technical evangelists.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/mesh@730w.webp 730w, /images/2020/08/mesh@730w2x.webp 1460w, /images/2020/08/mesh@610w.webp 610w, /images/2020/08/mesh@610w2x.webp 1220w, /images/2020/08/mesh@450w.webp 450w, /images/2020/08/mesh@450w2x.webp 900w, /images/2020/08/mesh@330w.webp 330w, /images/2020/08/mesh@330w2x.webp 660w, /images/2020/08/mesh@545w.webp 545w, /images/2020/08/mesh@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/mesh@730w.jpg 730w, /images/2020/08/mesh@730w2x.jpg 1460w, /images/2020/08/mesh@610w.jpg 610w, /images/2020/08/mesh@610w2x.jpg 1220w, /images/2020/08/mesh@450w.jpg 450w, /images/2020/08/mesh@450w2x.jpg 900w, /images/2020/08/mesh@330w.jpg 330w, /images/2020/08/mesh@330w2x.jpg 660w, /images/2020/08/mesh@545w.jpg 545w, /images/2020/08/mesh@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/mesh.jpg" alt="Review: AWS App Mesh" title="Review: AWS App Mesh"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/26-review-aws-app-mesh/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="What-is-a-service-mesh"><a href="#What-is-a-service-mesh" class="headerlink" title="What is a service mesh?"></a>What is a service mesh?</h2><p>The idea behind a microservice architecture is to split a big problem into many small chunks by building a distributed system. Typical challenges of a microservice architecture are:</p><ul><li><strong>Service Discovery</strong>: service A needs to find out how to talk to service B</li><li><strong>Load Balancing</strong>: distribute requests among multiple instances of a service (e.g., various containers in a cluster)</li><li><strong>Fault Tolerance</strong>: detect failures (health checks, …) and mitigate problems (circuit breaker, …) automatically</li><li><strong>0-Downtime Deployments</strong>: avoid failures during a deployment of a service by using canary blue-green deployment strategies</li><li><strong>Observability</strong>: track a request while traveling through the distributed system, monitor the health of each service, detect anomalies within metrics, analyze log messages</li><li><strong>Security</strong>: service-to-service authentication and authorization, encrypt data in transit</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/service-mesh@730w.webp 730w, /images/2020/08/service-mesh@730w2x.webp 1460w, /images/2020/08/service-mesh@610w.webp 610w, /images/2020/08/service-mesh@610w2x.webp 1220w, /images/2020/08/service-mesh@450w.webp 450w, /images/2020/08/service-mesh@450w2x.webp 900w, /images/2020/08/service-mesh@330w.webp 330w, /images/2020/08/service-mesh@330w2x.webp 660w, /images/2020/08/service-mesh@545w.webp 545w, /images/2020/08/service-mesh@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/service-mesh@730w.png 730w, /images/2020/08/service-mesh@730w2x.png 1460w, /images/2020/08/service-mesh@610w.png 610w, /images/2020/08/service-mesh@610w2x.png 1220w, /images/2020/08/service-mesh@450w.png 450w, /images/2020/08/service-mesh@450w2x.png 900w, /images/2020/08/service-mesh@330w.png 330w, /images/2020/08/service-mesh@330w2x.png 660w, /images/2020/08/service-mesh@545w.png 545w, /images/2020/08/service-mesh@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/service-mesh.png" alt="What is a service mesh?" title="What is a service mesh?"></picture></p><p>What is the origin of the term service mesh?</p><blockquote><p>A <strong>mesh network</strong> […] is a local <strong>network topology</strong> in which the infrastructure nodes […] <strong>connect directly, dynamically, and non-hierarchically</strong> to as many other nodes as possible and cooperate with one another to efficiently route data from&#x2F;to clients. […] Mesh networks dynamically self-organize and self-configure, which can reduce installation overhead. The ability to self-configure enables dynamic distribution of workloads, particularly in the event a few nodes should fail. This in turn contributes to fault-tolerance and reduced maintenance costs. (Source <a href="https://en.wikipedia.org/wiki/Mesh_networking" target="_blank" rel="noopener">Wikipedia</a>)</p></blockquote><p>A service mesh translates the idea of a mesh network to a microservice architecture. Services connect directly and route requests dynamically to balance the load and mitigate failure. There is no need for a central authority to orchestrate the service-to-service communication - for example, and there is no need for a load balancer (ALB or NLB).</p><h2 id="What-is-AWS-App-Mesh"><a href="#What-is-AWS-App-Mesh" class="headerlink" title="What is AWS App Mesh?"></a>What is AWS App Mesh?</h2><p>Use App Mesh to orchestrate a service mesh with compute nodes on ECS, EKS, and EC2. AWS promises to solve all the challenges of a microservice architecture listed above with App Mesh.</p><p>But how does App Mesh work? First of all, App Mesh utilizes Envoy, an L7 proxy and communication bus designed for large modern service-oriented architectures.<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup> The idea behind Envoy is that all the logic for service-to-service communication is deployed as a sidecar container. The services do not need to implement that functionality themselves. All App Mesh is doing is to provide Envoy configuration. On top of that, App Mesh integrates with many AWS services like Cloud Map, Certificate Manager, CloudWatch, and X-Ray.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/appmesh-intro@730w.webp 730w, /images/2020/08/appmesh-intro@730w2x.webp 1460w, /images/2020/08/appmesh-intro@610w.webp 610w, /images/2020/08/appmesh-intro@610w2x.webp 1220w, /images/2020/08/appmesh-intro@450w.webp 450w, /images/2020/08/appmesh-intro@450w2x.webp 900w, /images/2020/08/appmesh-intro@330w.webp 330w, /images/2020/08/appmesh-intro@330w2x.webp 660w, /images/2020/08/appmesh-intro@545w.webp 545w, /images/2020/08/appmesh-intro@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/appmesh-intro@730w.png 730w, /images/2020/08/appmesh-intro@730w2x.png 1460w, /images/2020/08/appmesh-intro@610w.png 610w, /images/2020/08/appmesh-intro@610w2x.png 1220w, /images/2020/08/appmesh-intro@450w.png 450w, /images/2020/08/appmesh-intro@450w2x.png 900w, /images/2020/08/appmesh-intro@330w.png 330w, /images/2020/08/appmesh-intro@330w2x.png 660w, /images/2020/08/appmesh-intro@545w.png 545w, /images/2020/08/appmesh-intro@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/appmesh-intro.png" alt="What is AWS App Mesh?" title="What is AWS App Mesh?"></picture></p><p>Let me introduce the basic concepts of App Mesh to you.</p><ul><li>The <strong>Service Mesh</strong> is the logical boundary of your microservice environment.</li><li>A <strong>Virtual Service</strong> is the abstract representation of a service.</li><li>A <strong>Virtual Router</strong> optionally routes requests of a service to different nodes depending on the request details (e.g., path).</li><li>A <strong>Virtual Node</strong> is the target for requests&#x2F;connections of a service and a pointer to an ECS or EKS service, for example.</li><li>A <strong>Virtual Gateway</strong> - an ingress gateway - allows resources from outside the mesh to connect to a service.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/appmesh-components@730w.webp 730w, /images/2020/08/appmesh-components@730w2x.webp 1460w, /images/2020/08/appmesh-components@610w.webp 610w, /images/2020/08/appmesh-components@610w2x.webp 1220w, /images/2020/08/appmesh-components@450w.webp 450w, /images/2020/08/appmesh-components@450w2x.webp 900w, /images/2020/08/appmesh-components@330w.webp 330w, /images/2020/08/appmesh-components@330w2x.webp 660w, /images/2020/08/appmesh-components@545w.webp 545w, /images/2020/08/appmesh-components@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/appmesh-components@730w.png 730w, /images/2020/08/appmesh-components@730w2x.png 1460w, /images/2020/08/appmesh-components@610w.png 610w, /images/2020/08/appmesh-components@610w2x.png 1220w, /images/2020/08/appmesh-components@450w.png 450w, /images/2020/08/appmesh-components@450w2x.png 900w, /images/2020/08/appmesh-components@330w.png 330w, /images/2020/08/appmesh-components@330w2x.png 660w, /images/2020/08/appmesh-components@545w.png 545w, /images/2020/08/appmesh-components@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/appmesh-components.png" alt="The App Mesh components: Service Mesh, Virtual Node, Virtual Router, Virtual Service, Virtual Gateway" title="The App Mesh components: Service Mesh, Virtual Node, Virtual Router, Virtual Service, Virtual Gateway"></picture></p><p>By the way, popular alternatives to AWS App Mesh for Kubernetes are: <a href="https://www.consul.io/" target="_blank" rel="noopener">Consul</a>, <a href="https://istio.io/" target="_blank" rel="noopener">Istio</a>, and <a href="https://linkerd.io/" target="_blank" rel="noopener">Linkerd</a>.</p><h2 id="App-Mesh-for-ECS-Walkthrough"><a href="#App-Mesh-for-ECS-Walkthrough" class="headerlink" title="App Mesh for ECS: Walkthrough"></a>App Mesh for ECS: Walkthrough</h2><p>I want to take you on a journey on what is needed to get App Mesh up and running for ECS use.</p><ol><li><strong>VPC</strong>: Although App Mesh is about application-level networking, you still need to configure subnets, route tables, and security groups under the hood.</li><li><strong>ECS</strong>: Obviously, you need to create an ECS cluster and services.</li><li><strong>Cloud Map</strong>: App Mesh requires Cloud Map for service discovery. Therefore, you need to configure a namespace and a service.</li><li><strong>Certificate Manager</strong>: Optionally, you need to create a private certificate authority to issue certificates for encrypting data in transit.</li><li><strong>App Mesh</strong>: On top of that, you need to configure App Mesh itself. The required resources are mesh, virtual service, and virtual node.</li></ol><p>On top of that, you need to add three sidecar containers to every ECS service’s task definition.</p><ul><li>Envoy Proxy</li><li>CloudWatch Agent</li><li>X-Ray Daemon</li></ul><p>Over the years, I have familiarized myself with many new AWS services. To build my example with App Mesh, I spent a lot of time. In my opinion, App Mesh is complicated to use. Many building blocks must be put together correctly. Unfortunately, the official documentation is not very helpful as well.</p><h2 id="Fully-managed"><a href="#Fully-managed" class="headerlink" title="Fully managed?"></a>Fully managed?</h2><p>A fundamental problem with App Mesh, in my opinion, is that it is not a service fully managed by AWS. For example, you need to deploy one to three sidecar containers for each service to your infrastructure (ECS, EKS, or EC2). You are responsible for detecting and fixing problems with these sidecar containers on your own. For example, a sidecar container could run out of memory.</p><p>The line between the part of App Mesh that is fully managed by AWS and the part that we, as a customer, have to run ourselves is not 100% clear. This makes troubleshooting difficult, especially in case of problems. Cloud services that are not fully encapsulated behind an API are problematic, in my opinion.</p><p>In theory, App Mesh could evolve to become a fully managed service in the future. For example, I can imagine AWS to deploy the sidecar containers transparently when launching a task or pod on Fargate.</p><h2 id="Observability"><a href="#Observability" class="headerlink" title="Observability"></a>Observability</h2><p>App Mesh promises end-to-end visibility<sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup> for microservices. Therefore, App Mesh provides metrics, logs, and traces. By default, App Mesh integrates with CloudWatch and X-Ray.</p><ul><li>CloudWatch Logs to collect and analyze incoming and outgoing requests or connections.</li><li>CloudWatch Metrics to gain insights into incoming and outgoing requests or connections.</li><li>X-Ray to record and analyze traces.</li></ul><p>It is worth mentioning that App Mesh does not provide any observability insights by default. You have to deploy additional sidecar containers: a CloudWatch Agent to send metrics to CloudWatch and the X-Ray daemon to record traces.</p><p>The integration with X-Ray looks fine. After deploying the sidecar container and enabling tracing, data was showing up quickly.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/xray@730w.webp 730w, /images/2020/08/xray@730w2x.webp 1460w, /images/2020/08/xray@610w.webp 610w, /images/2020/08/xray@610w2x.webp 1220w, /images/2020/08/xray@450w.webp 450w, /images/2020/08/xray@450w2x.webp 900w, /images/2020/08/xray@330w.webp 330w, /images/2020/08/xray@330w2x.webp 660w, /images/2020/08/xray@545w.webp 545w, /images/2020/08/xray@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/xray@730w.png 730w, /images/2020/08/xray@730w2x.png 1460w, /images/2020/08/xray@610w.png 610w, /images/2020/08/xray@610w2x.png 1220w, /images/2020/08/xray@450w.png 450w, /images/2020/08/xray@450w2x.png 900w, /images/2020/08/xray@330w.png 330w, /images/2020/08/xray@330w2x.png 660w, /images/2020/08/xray@545w.png 545w, /images/2020/08/xray@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/xray.png" alt="The X-Ray service map provides an overview of the system health" title="The X-Ray service map provides an overview of the system health"></picture></p><p>After enabling logging for the App Mesh nodes, the request logs were shipped to CloudWatch Logs. Nothing noteworthy on that.</p><p>After activating CloudWatch metrics, I experienced an unpleasant surprise. My App Mesh example consists of two services (<code>frontend</code> and <code>backend</code>). However, enabling CloudWatch metrics for Envoy resulted in more than 500 CloudWatch metrics. Because App Mesh uses so-called custom metrics, that’s quite expensive. A single custom metric is $0.30 per month. So I have to pay more than $150 per month for the CloudWatch metrics. It also comes to the fact that I could not find any way to reduce the number of metrics send to CloudWatch. So the whole feature is useless, in my opinion.</p><p>Using Prometheus is a possible workaround. However, I’m not a big fan of operating infrastructure that does not provide business value on my own.</p><h2 id="Encrypt-Data-In-Transit"><a href="#Encrypt-Data-In-Transit" class="headerlink" title="Encrypt Data-In-Transit"></a>Encrypt Data-In-Transit</h2><p>App Mesh integrates with the AWS Certificate Manager (ACM) to provide certificates to establish trust for the encrypted connections within the mesh. With App Mesh will automatically rotate and update certificates issued by the ACM. Great stuff!</p><p>But there is also a catch. We are all used to the fact that certificates are free, for the use with an ALB or NLB. However, App Mesh uses a Private CA provided by the AWS Certificate Manager. A Private CA is $400 per month. Probably, out of budget for most scenarios.</p><p>The only workaround is to provide and deploy a certificate on your own. Not an option, in my opinion.</p><h2 id="Deployment-Orchestration"><a href="#Deployment-Orchestration" class="headerlink" title="Deployment Orchestration"></a>Deployment Orchestration</h2><p>Within a service mesh, it is possible to control traffic between services in a granular way. That enables exciting opportunities during the deployment of a service. For example, you could send only a small portion of traffic to the latest version of a service to test if everything works fine in production. After a while, you could then switch all traffic to the newest version. This process is known as canary deployments.</p><p>App Mesh enables you to do canary and all other kinds of advanced deployment techniques. However, you need another tool for that. None of the AWS services are capable of orchestrating a deployment leveraging the features provided by App Mesh. We are missing integrations between App Mesh and CodePipeline, CodeDeploy, as well as CloudFormation.</p><p>You are lucky when using EKS because <a href="https://github.com/weaveworks/flagger" target="_blank" rel="noopener">Flagger</a> - a progressive delivery tool implementing canary releases, A&#x2F;B testing, blue&#x2F;green mirroring, and more - supports App Mesh.</p><h2 id="Missing-Features"><a href="#Missing-Features" class="headerlink" title="Missing Features"></a>Missing Features</h2><p>Some essential features are missing.</p><ul><li>Service-to-Service authentication and authorization is not supported yet.</li><li>Rate limiting to protect services from traffic spikes is not available yet.</li><li>Currently, there is no way to configure the options for the circuit breaker.</li></ul><h2 id="Pricing"><a href="#Pricing" class="headerlink" title="Pricing"></a>Pricing</h2><p>Using App Mesh is free of charge. That’s great! You are paying for the underlying infrastructure - EC2, Fargate, EKS, and so on - only. On the one hand, free service is good news. On the other hand, I’m always skeptical about whether free services find enough support within Amazon to drive further development.</p><h2 id="App-Mesh-vs-ELB"><a href="#App-Mesh-vs-ELB" class="headerlink" title="App Mesh vs. ELB"></a>App Mesh vs. ELB</h2><p>The concept of a service mesh is en vogue. Without a doubt, a service mesh is a promising approach. However, there is also a reliable alternative: good old load balancers. Therefore, I want to compare App Mesh with Elastic Load Balancing (ELB). Probably the most crucial difference is that App Mesh is reducing the number of network hops.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/appmesh-vs-elb@730w.webp 730w, /images/2020/08/appmesh-vs-elb@730w2x.webp 1460w, /images/2020/08/appmesh-vs-elb@610w.webp 610w, /images/2020/08/appmesh-vs-elb@610w2x.webp 1220w, /images/2020/08/appmesh-vs-elb@450w.webp 450w, /images/2020/08/appmesh-vs-elb@450w2x.webp 900w, /images/2020/08/appmesh-vs-elb@330w.webp 330w, /images/2020/08/appmesh-vs-elb@330w2x.webp 660w, /images/2020/08/appmesh-vs-elb@545w.webp 545w, /images/2020/08/appmesh-vs-elb@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/appmesh-vs-elb@730w.png 730w, /images/2020/08/appmesh-vs-elb@730w2x.png 1460w, /images/2020/08/appmesh-vs-elb@610w.png 610w, /images/2020/08/appmesh-vs-elb@610w2x.png 1220w, /images/2020/08/appmesh-vs-elb@450w.png 450w, /images/2020/08/appmesh-vs-elb@450w2x.png 900w, /images/2020/08/appmesh-vs-elb@330w.png 330w, /images/2020/08/appmesh-vs-elb@330w2x.png 660w, /images/2020/08/appmesh-vs-elb@545w.png 545w, /images/2020/08/appmesh-vs-elb@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/appmesh-vs-elb.png" alt="App Mesh vs. ELB" title="App Mesh vs. ELB"></picture></p><p>Let’s have a look at the differences in detail. I’m comparing App Mesh, ALB (Application Load Balancer), and NLB (Network Load Balancer) here.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th>App Mesh</th><th>ALB</th><th>NLB</th></tr></thead><tbody><tr><td>Obeservability</td><td>✅ inbound+outbound requests, requires additional sidecar containers</td><td>⚠️ inbound requests only</td><td>❌ very little insights</td></tr><tr><td>High Availablity</td><td>✅ health checks, cross-zone routing, ...</td><td>✅ health checks, cross-zone routing, ...</td><td>✅ health checks, cross-zone routing, ...</td></tr><tr><td>Fault Tolerance</td><td>✅ retries, circuit Breaker, ...</td><td>⚠️ client-side code needed</td><td>⚠️ client-side code needed</td></tr><tr><td>Intercompatibility</td><td>✅ EC2, ECS, EKS, Fargate</td><td>✅ EC2, ECS, EKS, Fargate, On-Premises</td><td>✅ EC2, ECS, EKS, On-Premises</td></tr><tr><td>Latency</td><td>✅ 0 additional network hops, 2 additional request processings</td><td>⚠️ 1 additional network hops, 1 additional request processings</td><td>✅ 0 additional network hops, 0 additional request processings</td></tr><tr><td>Resource Efficiency</td><td>⚠️ 1-3 sidecar containers per task/pod</td><td>✅</td><td>✅</td></tr><tr><td>Fully Managed</td><td>⚠️ only parts of the service</td><td>✅</td><td>✅</td></tr><tr><td>Costs</td><td>⚠️ free of charge, but costs for CPU and memory of sidecar containers</td><td>⚠️ hourly fee, traffic, ...</td><td>⚠️ hourly fee, traffic, ...</td></tr><tr><td>Complexity</td><td>⚠️ additional layer of abstraction</td><td>✅ simple</td><td>✅ simple</td></tr></tbody></table><p>In summary, App Mesh comes with a few advantages compared to an ALB or NLB. However, adding X-Ray or any other application monitoring solution to each service provides deep insights into the microservice architecture. Also, there are libraries for every programming language to retrofit retry strategies and circuit breakers.</p><p>In my opinion, the additional layer of abstraction and complexity introduced by App Mesh is not worth the advantages in most scenarios.</p><h2 id="Service-Maturity-Table"><a href="#Service-Maturity-Table" class="headerlink" title="Service Maturity Table"></a>Service Maturity Table</h2><p>The following table summarizes the maturity of the service:</p><table class="table table-striped table-responsive"><thead><tr><th>Criteria</th><th>Support</th><th style="text-align:right">Score</th></tr></thead><tbody><tr><td>Feature Completeness</td><td>✅</td><td style="text-align:right">4</td></tr><tr><td>Documentation Detailedness</td><td>✅</td><td style="text-align:right">4</td></tr><tr><td>Tags (Grouping + Billing)</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>CloudFormation + Terraform support</td><td>✅</td><td style="text-align:right">9</td></tr><tr><td>Emits CloudWatch Events</td><td>❌<sup><a href="#fn:3" id="fnref:3" class="footnote-ref">3</a></sup></td><td style="text-align:right">0</td></tr><tr><td>IAM granularity</td><td>✅</td><td style="text-align:right">9</td></tr><tr><td>Integrated with AWS Config</td><td>❌<sup><a href="#fn:4" id="fnref:4" class="footnote-ref">4</a></sup></td><td style="text-align:right">0</td></tr><tr><td>Auditing via AWS CloudTrail</td><td>✅<sup><a href="#fn:5" id="fnref:5" class="footnote-ref">5</a></sup></td><td style="text-align:right">8</td></tr><tr><td>Available in all commercial regions</td><td>⚠️</td><td style="text-align:right">8</td></tr><tr><td>SLA</td><td>❌</td><td style="text-align:right">0</td></tr><tr><td>Compliance (ISO, SOC HIPAA)</td><td>❌<sup><a href="#fn:6" id="fnref:6" class="footnote-ref">6</a></sup><sup><a href="#fn:7" id="fnref:7" class="footnote-ref">7</a></sup></td><td style="text-align:right">2</td></tr><tr><td><strong>Total Maturity Score (0-10)</strong></td><td>⚠️</td><td style="text-align:right"><strong>4.9</strong></td></tr></tbody></table><p>Our maturity score for AWS App Mesh is 4.7 on a scale from 0 to 10.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Building a service mesh is trending those days. App Mesh provides service mesh capabilities for EC2, ECS, and EKS. For free! On top of that, App Mesh integrates with a bunch of AWS services like Cloud Map, Certificate Manager, CloudWatch, and X-Ray. App Mesh is a new service still at the very beginning. Our service maturity score of 4.7 indicates that it is too early to use App Mesh right now. Let’s wait for AWS to improve the service step by step based on other AWS customers’ feedback.</p><p>The fundamental problem is that App Mesh is not a fully managed service. As an App Mesh customer, you need to deploy and operate 1-3 sidecar containers per task (aka. pod). This contradicts the goal of having the cloud provider take over as many tasks as possible.</p><p>It is frustrating that activating CloudWatch metrics incurs costs of more than $150 per month for a mesh consisting of two services. Also, $400 per month for a private CA provided by ACM will probably be a show stopper for most scenarios.</p><p>Overall, App Mesh is only for service mesh enthusiasts.</p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. https://www.envoyproxy.io/docs/envoy/latest/intro/what_is_envoy <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. https://aws.amazon.com/app-mesh/ <a href="#fnref:2" class="footnote-backref">↩</a></p></li><li id="fn:3"><p>3. https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html <a href="#fnref:3" class="footnote-backref">↩</a></p></li><li id="fn:4"><p>4. https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html <a href="#fnref:4" class="footnote-backref">↩</a></p></li><li id="fn:5"><p>5. https://docs.aws.amazon.com/app-mesh/latest/userguide/logging-using-cloudtrail.html <a href="#fnref:5" class="footnote-backref">↩</a></p></li><li id="fn:6"><p>6. https://aws.amazon.com/compliance/services-in-scope/ <a href="#fnref:6" class="footnote-backref">↩</a></p></li><li id="fn:7"><p>7. https://aws.amazon.com/about-aws/whats-new/2020/07/aws-app-mesh-achieves-hipaa-eligibility/ <a href="#fnref:7" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Workaround: CodePipeline for GitHub Enterprise</title>
      <link>https://cloudonaut.io/workaround-codepipeline-for-github-enterprise/</link>
      <description>
        <![CDATA[<p>There is no question that AWS has a strong focus on customer obsession. However, sometimes it takes forever until popular feature request]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/codepipeline/">codepipeline</category>
      <category domain="https://cloudonaut.io/tag/codebuild/">codebuild</category>
      <category domain="https://cloudonaut.io/tag/github/">github</category>
      <guid isPermaLink="true">https://cloudonaut.io/workaround-codepipeline-for-github-enterprise/</guid>
      <pubDate>Fri, 21 Aug 2020 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>There is no question that AWS has a strong focus on customer obsession. However, sometimes it takes forever until popular feature requests get implemented. A good example: CodePipeline - the continuous delivery service - does support all kinds of source code repositories: CodeCommit, GitHub, Bitbucket, S3, and ECR. Although a very popular option is missing: GitHub Enterprise.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/workaround@730w.webp 730w, /images/2020/08/workaround@730w2x.webp 1460w, /images/2020/08/workaround@610w.webp 610w, /images/2020/08/workaround@610w2x.webp 1220w, /images/2020/08/workaround@450w.webp 450w, /images/2020/08/workaround@450w2x.webp 900w, /images/2020/08/workaround@330w.webp 330w, /images/2020/08/workaround@330w2x.webp 660w, /images/2020/08/workaround@545w.webp 545w, /images/2020/08/workaround@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/workaround@730w.jpg 730w, /images/2020/08/workaround@730w2x.jpg 1460w, /images/2020/08/workaround@610w.jpg 610w, /images/2020/08/workaround@610w2x.jpg 1220w, /images/2020/08/workaround@450w.jpg 450w, /images/2020/08/workaround@450w2x.jpg 900w, /images/2020/08/workaround@330w.jpg 330w, /images/2020/08/workaround@330w2x.jpg 660w, /images/2020/08/workaround@545w.jpg 545w, /images/2020/08/workaround@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/workaround.jpg" alt="Workaround: CodePipeline for GitHub Enterprise" title="Workaround: CodePipeline for GitHub Enterprise"></picture></p><p>Luckily, there is a decent workaround to connect GitHub Enterprise with CodePipeline.</p><ol><li>A webhook from GitHub Enterprise triggers CodeBuild.</li><li>CodeBuild fetches the latest changes (Git over HTTPS or SSH), bundles them into a ZIP file, and uploads the archive to S3.</li><li>The S3 bucket with versioning enabled stores the latest version of the repository.</li><li>A CloudWatch event rule triggers the pipeline whenever the CodeBuild project succeeded.</li><li>The source action of CodePipeline downloads the ZIP file, unpacks the archive, and hands over the source code to the next stage.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/codepipeline-for-github-enterprise@730w.webp 730w, /images/2020/08/codepipeline-for-github-enterprise@730w2x.webp 1460w, /images/2020/08/codepipeline-for-github-enterprise@610w.webp 610w, /images/2020/08/codepipeline-for-github-enterprise@610w2x.webp 1220w, /images/2020/08/codepipeline-for-github-enterprise@450w.webp 450w, /images/2020/08/codepipeline-for-github-enterprise@450w2x.webp 900w, /images/2020/08/codepipeline-for-github-enterprise@330w.webp 330w, /images/2020/08/codepipeline-for-github-enterprise@330w2x.webp 660w, /images/2020/08/codepipeline-for-github-enterprise@545w.webp 545w, /images/2020/08/codepipeline-for-github-enterprise@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/codepipeline-for-github-enterprise@730w.png 730w, /images/2020/08/codepipeline-for-github-enterprise@730w2x.png 1460w, /images/2020/08/codepipeline-for-github-enterprise@610w.png 610w, /images/2020/08/codepipeline-for-github-enterprise@610w2x.png 1220w, /images/2020/08/codepipeline-for-github-enterprise@450w.png 450w, /images/2020/08/codepipeline-for-github-enterprise@450w2x.png 900w, /images/2020/08/codepipeline-for-github-enterprise@330w.png 330w, /images/2020/08/codepipeline-for-github-enterprise@330w2x.png 660w, /images/2020/08/codepipeline-for-github-enterprise@545w.png 545w, /images/2020/08/codepipeline-for-github-enterprise@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/codepipeline-for-github-enterprise.png" alt="The solution: GitHub Enterprise, CodeBuild, S3, and CodePipeline." title="The solution: GitHub Enterprise, CodeBuild, S3, and CodePipeline."></picture></p><h2 id="Code-Example"><a href="#Code-Example" class="headerlink" title="Code Example"></a>Code Example</h2><p>In the following, I will use Terraform to set up all the needed resources. First of all, we need to get Terraform up and running.</p><p>Make sure to fill in the following placeholders:</p><ul><li><code>&lt;GITHUB_ACCESS_TOKEN&gt;</code> a personal access token to access the GitHub Enterprise API. Please note, scope <code>admin:repo_hook</code> is required.</li><li><code>&lt;GITHUB_ORGANIZATON&gt;</code> the name of your GitHub Enterprise organization (e.g., <code>myorg</code>).</li><li><code>&lt;GITHUB_REPOSITORY_NAME&gt;</code> the name of your GitHub Enterprise repository (e.g., <code>myrepo</code>).</li><li><code>&lt;GITHUB_REPOSITORY_URL&gt;</code> the URL of your GitHub Enterprise repository (e.g., <code>https://git.example.com/myorg/myrepo.git</code>).</li><li><code>&lt;GITHUB_API_URL&gt;</code> the URL of the GitHub Enterprise API (e.g., <code>https://git.example.com/api/</code>).</li></ul><figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">terraform &#123;</span><br><span class="line">  required_version <span class="operator">=</span> <span class="string">&quot;&gt;= 0.12&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">provider <span class="string">&quot;aws&quot;</span> &#123;</span><br><span class="line">  region  <span class="operator">=</span> <span class="string">&quot;eu-central-1&quot;</span></span><br><span class="line">  version <span class="operator">=</span> <span class="string">&quot;~&gt; 3.0&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">provider <span class="string">&quot;github&quot;</span> &#123;</span><br><span class="line">  token        <span class="operator">=</span> <span class="string">&quot;&lt;GITHUB_ACCESS_TOKEN&gt;&quot;</span></span><br><span class="line">  organization <span class="operator">=</span> <span class="string">&quot;&lt;GITHUB_ORGANIZATON&gt;&quot;</span></span><br><span class="line">  base_url     <span class="operator">=</span> <span class="string">&quot;&lt;GITHUB_API_URL&gt;&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">data <span class="string">&quot;aws_kms_alias&quot;</span> <span class="string">&quot;s3&quot;</span> &#123;</span><br><span class="line">  name <span class="operator">=</span> <span class="string">&quot;alias/aws/s3&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Next, we configure the CodeBuild project that will fetch the source code from GitHub Enterprise and upload it to S3.</p><figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_codebuild_project&quot;</span> <span class="string">&quot;github_enterprise&quot;</span> &#123;</span><br><span class="line">  name          <span class="operator">=</span> <span class="string">&quot;github-enterprise&quot;</span></span><br><span class="line">  build_timeout <span class="operator">=</span> <span class="string">&quot;5&quot;</span></span><br><span class="line">  service_role  <span class="operator">=</span> aws_iam_role.github_enterprise.arn</span><br><span class="line"></span><br><span class="line">  artifacts &#123;</span><br><span class="line">    type <span class="operator">=</span> <span class="string">&quot;S3&quot;</span></span><br><span class="line">    name <span class="operator">=</span> <span class="string">&quot;source.zip&quot;</span></span><br><span class="line">    location <span class="operator">=</span> aws_s3_bucket.artifacts.id</span><br><span class="line">    packaging  <span class="operator">=</span> <span class="string">&quot;ZIP&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  cache &#123;</span><br><span class="line">    type  <span class="operator">=</span> <span class="string">&quot;LOCAL&quot;</span></span><br><span class="line">    modes <span class="operator">=</span> [<span class="string">&quot;LOCAL_SOURCE_CACHE&quot;</span>]</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  environment &#123;</span><br><span class="line">    compute_type                <span class="operator">=</span> <span class="string">&quot;BUILD_GENERAL1_LARGE&quot;</span></span><br><span class="line">    image                       <span class="operator">=</span> <span class="string">&quot;aws/codebuild/standard:2.0&quot;</span></span><br><span class="line">    type                        <span class="operator">=</span> <span class="string">&quot;LINUX_CONTAINER&quot;</span></span><br><span class="line">    privileged_mode   <span class="operator">=</span> false</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  source &#123;</span><br><span class="line">    type            <span class="operator">=</span> <span class="string">&quot;GITHUB_ENTERPRISE&quot;</span></span><br><span class="line">    location        <span class="operator">=</span> <span class="string">&quot;&lt;GITHUB_REPOSITORY_URL&gt;&quot;</span></span><br><span class="line">    buildspec       <span class="operator">=</span> <span class="string">&quot;github-enterprise/buildspec.yml&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_codebuild_source_credential&quot;</span> <span class="string">&quot;github_enterprise&quot;</span> &#123;</span><br><span class="line">  auth_type <span class="operator">=</span> <span class="string">&quot;PERSONAL_ACCESS_TOKEN&quot;</span></span><br><span class="line">  server_type <span class="operator">=</span> <span class="string">&quot;GITHUB_ENTERPRISE&quot;</span></span><br><span class="line">  token <span class="operator">=</span> <span class="string">&quot;&lt;GITHUB_ACCESS_TOKEN&gt;&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Next, we configure a webhook between GitHub Enterprise and CodeBuild.</p><figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_codebuild_webhook&quot;</span> <span class="string">&quot;github_enterprise&quot;</span> &#123;</span><br><span class="line">  project_name <span class="operator">=</span> aws_codebuild_project.github_enterprise.name</span><br><span class="line"></span><br><span class="line">  filter_group &#123;</span><br><span class="line">    filter &#123;</span><br><span class="line">      type <span class="operator">=</span> <span class="string">&quot;EVENT&quot;</span></span><br><span class="line">      pattern <span class="operator">=</span> <span class="string">&quot;PUSH&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    filter &#123;</span><br><span class="line">      type <span class="operator">=</span> <span class="string">&quot;HEAD_REF&quot;</span></span><br><span class="line">      pattern <span class="operator">=</span> <span class="string">&quot;master&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;github_repository_webhook&quot;</span> <span class="string">&quot;github_enterprise&quot;</span> &#123;</span><br><span class="line">  active     <span class="operator">=</span> true</span><br><span class="line">  events     <span class="operator">=</span> [<span class="string">&quot;push&quot;</span>]</span><br><span class="line">  repository <span class="operator">=</span> <span class="string">&quot;&lt;GITHUB_REPOSITORY_NAME&gt;&quot;</span></span><br><span class="line"></span><br><span class="line">  configuration &#123;</span><br><span class="line">    url          <span class="operator">=</span> aws_codebuild_webhook.github_enterprise.payload_url</span><br><span class="line">    secret       <span class="operator">=</span> aws_codebuild_webhook.github_enterprise.secret</span><br><span class="line">    content_type <span class="operator">=</span> <span class="string">&quot;json&quot;</span></span><br><span class="line">    insecure_ssl <span class="operator">=</span> false</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>In the following step, you will create an S3 bucket to store the source code artifact - a zip file named <code>source.zip</code>. I’m using KMS encryption with the default key here.</p><figure class="highlight fsharp"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_s3_bucket&quot;</span> <span class="string">&quot;artifacts&quot;</span> &#123;</span><br><span class="line">  <span class="keyword">versioning</span> &#123;</span><br><span class="line">    enabled <span class="operator">=</span> <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">server_side_encryption_configuration</span> &#123;</span><br><span class="line">    <span class="keyword">rule</span> &#123;</span><br><span class="line">      <span class="keyword">apply_server_side_encryption_by_default</span> &#123;</span><br><span class="line">        sse_algorithm <span class="operator">=</span> <span class="string">&quot;aws:kms&quot;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_s3_bucket_policy&quot;</span> <span class="string">&quot;artifacts&quot;</span> &#123;</span><br><span class="line">  bucket <span class="operator">=</span> aws_s3_bucket.artifacts.<span class="built_in">id</span></span><br><span class="line"></span><br><span class="line">  policy <span class="operator">=</span> <span class="operator">&lt;&lt;</span>POLICY</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;Version&quot;</span><span class="operator">:</span> <span class="string">&quot;2012-10-17&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Statement&quot;</span><span class="operator">:</span> [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;Effect&quot;</span><span class="operator">:</span> <span class="string">&quot;Deny&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Principal&quot;</span><span class="operator">:</span> <span class="string">&quot;*&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Action&quot;</span><span class="operator">:</span> <span class="string">&quot;s3:PutObject&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Resource&quot;</span><span class="operator">:</span> <span class="string">&quot;$&#123;aws_s3_bucket.artifacts.arn&#125;/*&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Condition&quot;</span><span class="operator">:</span> &#123;</span><br><span class="line">        <span class="string">&quot;StringNotEquals&quot;</span><span class="operator">:</span> &#123;</span><br><span class="line">          <span class="string">&quot;s3:x-amz-server-side-encryption&quot;</span><span class="operator">:</span> <span class="string">&quot;&quot;</span>,</span><br><span class="line">          <span class="string">&quot;s3:x-amz-server-side-encryption-aws-kms-key-id&quot;</span><span class="operator">:</span> <span class="string">&quot;$&#123;data.aws_kms_alias.s3.target_key_arn&#125;&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line">POLICY</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Nothing works without IAM. Let’s create the IAM role for the CodeBuild project to grant access to S3 and CloudWatch Logs.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_iam_role&quot; &quot;github_enterprise&quot; &#123;</span><br><span class="line">  assume_role_policy = &lt;&lt;<span class="keyword">POLICY</span></span><br><span class="line">&#123;</span><br><span class="line">  &quot;Version&quot;: &quot;2012-10-17&quot;,</span><br><span class="line">  &quot;Statement&quot;: [</span><br><span class="line">    &#123;</span><br><span class="line">      &quot;Effect&quot;: &quot;Allow&quot;,</span><br><span class="line">      &quot;Principal&quot;: &#123;</span><br><span class="line">        &quot;Service&quot;: &quot;codebuild.amazonaws.com&quot;</span><br><span class="line">      &#125;,</span><br><span class="line">      &quot;Action&quot;: &quot;sts:AssumeRole&quot;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">POLICY</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_iam_role_policy&quot; &quot;github_enterprise&quot; &#123;</span><br><span class="line">  <span class="keyword">role</span> = aws_iam_role.github_enterprise.name</span><br><span class="line"></span><br><span class="line">  <span class="keyword">policy</span> = &lt;&lt;<span class="keyword">POLICY</span></span><br><span class="line">&#123;</span><br><span class="line">  &quot;Version&quot;: &quot;2012-10-17&quot;,</span><br><span class="line">  &quot;Statement&quot;: [</span><br><span class="line">    &#123;</span><br><span class="line">      &quot;Effect&quot;: &quot;Allow&quot;,</span><br><span class="line">      &quot;Resource&quot;: &quot;*&quot;,</span><br><span class="line">      &quot;Action&quot;: [</span><br><span class="line">        &quot;logs:CreateLogGroup&quot;,</span><br><span class="line">        &quot;logs:CreateLogStream&quot;,</span><br><span class="line">        &quot;logs:PutLogEvents&quot;</span><br><span class="line">      ]</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      &quot;Effect&quot;: &quot;Allow&quot;,</span><br><span class="line">      &quot;Resource&quot;: &quot;$&#123;aws_s3_bucket.artifacts.arn&#125;/*&quot;,</span><br><span class="line">      &quot;Action&quot;: [</span><br><span class="line">        &quot;s3:GetObject&quot;,</span><br><span class="line">        &quot;s3:PutObject&quot;</span><br><span class="line">      ]</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">POLICY</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The event rule triggers the pipeline whenever the CodeBuild project finished fetching and uploading the latest source code.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_cloudwatch_event_rule&quot;</span> <span class="string">&quot;artifacts&quot;</span> &#123;</span><br><span class="line">  event_pattern = &lt;&lt;<span class="symbol">PATTERN</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;source&quot;</span>: [ </span><br><span class="line">    <span class="string">&quot;aws.codebuild&quot;</span></span><br><span class="line">  ], </span><br><span class="line">  <span class="string">&quot;detail-type&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;CodeBuild Build State Change&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;detail&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;build-status&quot;</span>: [</span><br><span class="line">      <span class="string">&quot;SUCCEEDED&quot;</span></span><br><span class="line">    ],</span><br><span class="line">    <span class="string">&quot;project-name&quot;</span>: [</span><br><span class="line">      <span class="string">&quot;$&#123;aws_codebuild_project.github_enterprise.name&#125;&quot;</span></span><br><span class="line">    ]</span><br><span class="line">  &#125;  </span><br><span class="line">&#125;</span><br><span class="line"><span class="symbol">PATTERN</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_cloudwatch_event_target&quot;</span> <span class="string">&quot;artifacts&quot;</span> &#123;</span><br><span class="line">  rule      = aws_cloudwatch_event_rule.artifacts.name</span><br><span class="line">  arn       = aws_codepipeline.pipeline.arn</span><br><span class="line">  role_arn  = aws_iam_role.events.arn</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>One more IAM role which allows the CloudWatch event to trigger the pipeline is needed.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_iam_role&quot; &quot;events&quot; &#123;</span><br><span class="line">  assume_role_policy = &lt;&lt;<span class="keyword">POLICY</span></span><br><span class="line">&#123;</span><br><span class="line">  &quot;Version&quot;: &quot;2012-10-17&quot;,</span><br><span class="line">  &quot;Statement&quot;: [</span><br><span class="line">    &#123;</span><br><span class="line">      &quot;Effect&quot;: &quot;Allow&quot;,</span><br><span class="line">      &quot;Principal&quot;: &#123;</span><br><span class="line">        &quot;Service&quot;: &quot;events.amazonaws.com&quot;</span><br><span class="line">      &#125;,</span><br><span class="line">      &quot;Action&quot;: &quot;sts:AssumeRole&quot;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">POLICY</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_iam_role_policy&quot; &quot;events&quot; &#123;</span><br><span class="line">  <span class="keyword">role</span> = aws_iam_role.events.id</span><br><span class="line"></span><br><span class="line">  <span class="keyword">policy</span> = &lt;&lt;<span class="keyword">POLICY</span></span><br><span class="line">&#123;</span><br><span class="line">  &quot;Version&quot;: &quot;2012-10-17&quot;,</span><br><span class="line">  &quot;Statement&quot;: [</span><br><span class="line">    &#123;</span><br><span class="line">      &quot;Effect&quot;:&quot;Allow&quot;,</span><br><span class="line">      &quot;Action&quot;: &quot;codepipeline:StartPipelineExecution&quot;,</span><br><span class="line">      &quot;Resource&quot;: &quot;$&#123;aws_codepipeline.pipeline.arn&#125;&quot;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">POLICY</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>And finally, the pipeline itself.</p><figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">resource <span class="string">&quot;aws_codepipeline&quot;</span> <span class="string">&quot;pipeline&quot;</span> &#123;</span><br><span class="line">  name     <span class="operator">=</span> <span class="string">&quot;mypipeline&quot;</span></span><br><span class="line">  role_arn <span class="operator">=</span> aws_iam_role.codepipeline.arn</span><br><span class="line"></span><br><span class="line">  artifact_store &#123;</span><br><span class="line">    location <span class="operator">=</span> aws_s3_bucket.artifacts.bucket</span><br><span class="line">    type     <span class="operator">=</span> <span class="string">&quot;S3&quot;</span></span><br><span class="line"></span><br><span class="line">    encryption_key &#123;</span><br><span class="line">      id   <span class="operator">=</span> data.aws_kms_alias.s3.target_key_arn</span><br><span class="line">      type <span class="operator">=</span> <span class="string">&quot;KMS&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  stage &#123;</span><br><span class="line">    name <span class="operator">=</span> <span class="string">&quot;Source&quot;</span></span><br><span class="line"></span><br><span class="line">    action &#123;</span><br><span class="line">      name             <span class="operator">=</span> <span class="string">&quot;Source&quot;</span></span><br><span class="line">      category         <span class="operator">=</span> <span class="string">&quot;Source&quot;</span></span><br><span class="line">      owner            <span class="operator">=</span> <span class="string">&quot;AWS&quot;</span></span><br><span class="line">      provider         <span class="operator">=</span> <span class="string">&quot;S3&quot;</span></span><br><span class="line">      version          <span class="operator">=</span> <span class="string">&quot;1&quot;</span></span><br><span class="line">      output_artifacts <span class="operator">=</span> [<span class="string">&quot;source&quot;</span>]</span><br><span class="line"></span><br><span class="line">      configuration <span class="operator">=</span> &#123;</span><br><span class="line">        S3Bucket  <span class="operator">=</span> aws_s3_bucket.artifacts.id</span><br><span class="line">        PollForSourceChanges   <span class="operator">=</span> <span class="string">&quot;false&quot;</span></span><br><span class="line">        S3ObjectKey <span class="operator">=</span> <span class="string">&quot;source.zip&quot;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  stage &#123;</span><br><span class="line">    name <span class="operator">=</span> <span class="string">&quot;Deploy&quot;</span></span><br><span class="line"></span><br><span class="line">    action &#123;</span><br><span class="line">    run_order        <span class="operator">=</span> <span class="string">&quot;1&quot;</span></span><br><span class="line">    name             <span class="operator">=</span> <span class="string">&quot;Approval&quot;</span></span><br><span class="line">    category         <span class="operator">=</span> <span class="string">&quot;Approval&quot;</span></span><br><span class="line">      owner            <span class="operator">=</span> <span class="string">&quot;AWS&quot;</span></span><br><span class="line">      provider         <span class="operator">=</span> <span class="string">&quot;Manual&quot;</span></span><br><span class="line">      version          <span class="operator">=</span> <span class="string">&quot;1&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Last but not least, an IAM role for CodePipeline granting access to the artifacts bucket as well.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">resource &quot;aws_iam_role&quot; &quot;codepipeline&quot; &#123;</span><br><span class="line">  assume_role_policy = &lt;&lt;<span class="keyword">POLICY</span></span><br><span class="line">&#123;</span><br><span class="line">  &quot;Version&quot;: &quot;2012-10-17&quot;,</span><br><span class="line">  &quot;Statement&quot;: [</span><br><span class="line">    &#123;</span><br><span class="line">      &quot;Effect&quot;: &quot;Allow&quot;,</span><br><span class="line">      &quot;Principal&quot;: &#123;</span><br><span class="line">        &quot;Service&quot;: &quot;codepipeline.amazonaws.com&quot;</span><br><span class="line">      &#125;,</span><br><span class="line">      &quot;Action&quot;: &quot;sts:AssumeRole&quot;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">POLICY</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource &quot;aws_iam_role_policy&quot; &quot;codepipeline&quot; &#123;</span><br><span class="line">  <span class="keyword">role</span> = aws_iam_role.codepipeline.id</span><br><span class="line"></span><br><span class="line">  <span class="keyword">policy</span> = &lt;&lt;<span class="keyword">POLICY</span></span><br><span class="line">&#123;</span><br><span class="line">  &quot;Version&quot;: &quot;2012-10-17&quot;,</span><br><span class="line">  &quot;Statement&quot;: [</span><br><span class="line">    &#123;</span><br><span class="line">      &quot;Effect&quot;:&quot;Allow&quot;,</span><br><span class="line">      &quot;Action&quot;: [</span><br><span class="line">        &quot;s3:GetObject&quot;,</span><br><span class="line">        &quot;s3:GetObjectVersion&quot;,</span><br><span class="line">        &quot;s3:PutObject&quot;</span><br><span class="line">      ],</span><br><span class="line">      &quot;Resource&quot;: [</span><br><span class="line">        &quot;$&#123;aws_s3_bucket.artifacts.arn&#125;/*&quot;</span><br><span class="line">      ]</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      &quot;Effect&quot;:&quot;Allow&quot;,</span><br><span class="line">      &quot;Action&quot;: [</span><br><span class="line">        &quot;s3:GetBucketVersioning&quot;</span><br><span class="line">      ],</span><br><span class="line">      &quot;Resource&quot;: [</span><br><span class="line">        &quot;$&#123;aws_s3_bucket.artifacts.arn&#125;&quot;</span><br><span class="line">      ]</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">POLICY</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>One more thing. You need to add the file <code>github-enterprise/buildspec.yml</code> to your repository. The file should contain the following configuration.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">0.2</span></span><br><span class="line"><span class="attr">phases:</span></span><br><span class="line">  <span class="attr">build:</span></span><br><span class="line">    <span class="attr">commands:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;echo &quot;$&#123;CODEBUILD_RESOLVED_SOURCE_VERSION&#125;&quot; &gt; SOURCE_VERSION&#x27;</span></span><br><span class="line"><span class="attr">artifacts:</span></span><br><span class="line">  <span class="attr">files:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&#x27;**/*&#x27;</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">source</span></span><br></pre></td></tr></table></figure><p>That’s it. You are ready to run <code>terraform apply</code> to set up CodePipeline for GitHub Enterprise.</p><h2 id="Limitations"><a href="#Limitations" class="headerlink" title="Limitations"></a>Limitations</h2><ul><li>The code example only works when GitHub Enterprise is available over the Internet. In theory, it is possible to access GitHub Enterprise over private networks only as well. Doing so requires to configure a network interface to establish access to a VPC for the CodeBuild project.</li><li>CodePipeline does not know about the commit hash. It shows the version of the <code>source.zip</code> S3 object instead. However, the example adds a file <code>SOURCE_VERSION</code> to the <code>source.zip</code> archive, which contains the original commit hash.</li><li>Copying the source code to S3 adds additional latency (about 1-3 minutes) to your deployment pipeline.</li></ul><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Unfortunately, CodePipeline does not support GitHub Enterprise yet. Using CodeBuild and S3 is a decent workaround to get CodePipeline running for your GitHub Enterprise repository.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>(Erratum) VPC Endpoint increases DynamoDB latency by 30%</title>
      <link>https://cloudonaut.io/vpc-endpoint-increases-dynamodb-latency-by-30-percent/</link>
      <description>
        <![CDATA[<p>Our reader Tom wrote in to tell me, that the latency for read requests to DynamoDB increased significantly after enabling a VPC endpoint]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/dynamodb/">dynamodb</category>
      <guid isPermaLink="true">https://cloudonaut.io/vpc-endpoint-increases-dynamodb-latency-by-30-percent/</guid>
      <pubDate>Thu, 20 Aug 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Our reader Tom wrote in to tell me, that the latency for read requests to DynamoDB increased significantly after enabling a VPC endpoint a few weeks ago. Someone else reported a <a href="https://forums.aws.amazon.com/thread.jspa?messageID=801535" target="_blank" rel="noopener">similar problem</a> in the AWS discussion forums as well. Therefore, I started to investigate to write this article. After many hours of benchmarking, I came to the conclusion that using a VPC endpoint to connect to DynamoDB increase latency by 30% compared to connections through a internet gateway or NAT gateway. Shortly, after publishing the article, Petar send me a message via Twitter to tell me that something must be wrong with my benchmark. Unfortunatly, that was correct. I made a mistake when measuring the read latency for DynamoDB from EC2.</p><p><strong>Therefore, I have depublished the original blog post. Instead you will find a post mortem analysis in the following.</strong></p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/snail@730w.webp 730w, /images/2020/08/snail@730w2x.webp 1460w, /images/2020/08/snail@610w.webp 610w, /images/2020/08/snail@610w2x.webp 1220w, /images/2020/08/snail@450w.webp 450w, /images/2020/08/snail@450w2x.webp 900w, /images/2020/08/snail@330w.webp 330w, /images/2020/08/snail@330w2x.webp 660w, /images/2020/08/snail@545w.webp 545w, /images/2020/08/snail@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/snail@730w.jpg 730w, /images/2020/08/snail@730w2x.jpg 1460w, /images/2020/08/snail@610w.jpg 610w, /images/2020/08/snail@610w2x.jpg 1220w, /images/2020/08/snail@450w.jpg 450w, /images/2020/08/snail@450w2x.jpg 900w, /images/2020/08/snail@330w.jpg 330w, /images/2020/08/snail@330w2x.jpg 660w, /images/2020/08/snail@545w.jpg 545w, /images/2020/08/snail@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/snail.jpg" alt="VPC Endpoint increases DynamoDB latency by 30%" title="VPC Endpoint increases DynamoDB latency by 30%"></picture></p><p>The problem starts with Node.js and the AWS SDK. I was using the following script to measure the read latency for DynamoDB.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> dynamodb = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">DynamoDB</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-08-10&#x27;</span>&#125;);</span><br><span class="line"><span class="variable constant_">AWS</span>.<span class="property">config</span>.<span class="property">maxRetries</span> = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">benchmark</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> index = <span class="number">0</span>; index &lt; <span class="number">10000</span>; index++) &#123;</span><br><span class="line">    <span class="keyword">let</span> start = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>();</span><br><span class="line">    <span class="keyword">let</span> result = <span class="keyword">await</span> dynamodb.<span class="title function_">getItem</span>(&#123;</span><br><span class="line">      <span class="title class_">Key</span>: &#123;</span><br><span class="line">       <span class="string">&quot;id&quot;</span>: &#123;</span><br><span class="line">         <span class="attr">S</span>: <span class="string">&quot;1&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="title class_">TableName</span>: <span class="string">&quot;benchmark&quot;</span></span><br><span class="line">    &#125;).<span class="title function_">promise</span>();</span><br><span class="line">    <span class="keyword">let</span> end = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">getTime</span>();</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`<span class="subst">$&#123;index&#125;</span>,<span class="subst">$&#123;end-start&#125;</span>`</span>);</span><br><span class="line">    <span class="keyword">await</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function"><span class="params">r</span> =&gt;</span> <span class="built_in">setTimeout</span>(r, <span class="number">50</span>));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">benchmark</span>();</span><br></pre></td></tr></table></figure><p>With that code, requests through a VPC endpoint took about 10 ms - about 30% - longer than when connecting through an Internet gateway.</p><p>The problem with that Node.js code? Each DynamoDB API request create a new TCP connection. Doing so adds latency, and it seems like establishing a new TCP connection is taking about 30% longer when using a VPC endpoint.</p><p>However, you should not use the Node.js default in production. Instead, you need to tell the AWS SDK to reuse exsisting connections. To do so, I modified my code as described in <a href="https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/node-reusing-connections.html" target="_blank" rel="noopener">Reusing Connections with Keep-Alive in Node.js</a>.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> https = <span class="built_in">require</span>(<span class="string">&#x27;https&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> agent = <span class="keyword">new</span> https.<span class="title class_">Agent</span>(&#123;</span><br><span class="line">  <span class="attr">keepAlive</span>: <span class="literal">true</span></span><br><span class="line">&#125;);</span><br><span class="line"><span class="keyword">const</span> dynamodb = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">DynamoDB</span>(&#123;<span class="attr">httpOptions</span>: &#123;agent&#125;&#125;);</span><br><span class="line"><span class="variable constant_">AWS</span>.<span class="property">config</span>.<span class="property">maxRetries</span> = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">benchmark</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> index = <span class="number">0</span>; index &lt; <span class="number">10000</span>; index++) &#123;</span><br><span class="line">    <span class="keyword">let</span> start = process.<span class="property">hrtime</span>.<span class="title function_">bigint</span>();</span><br><span class="line">    <span class="keyword">let</span> result = <span class="keyword">await</span> dynamodb.<span class="title function_">getItem</span>(&#123;</span><br><span class="line">      <span class="title class_">Key</span>: &#123;</span><br><span class="line">       <span class="string">&quot;id&quot;</span>: &#123;</span><br><span class="line">         <span class="attr">S</span>: <span class="string">&quot;1&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="title class_">TableName</span>: <span class="string">&quot;benchmark&quot;</span></span><br><span class="line">    &#125;).<span class="title function_">promise</span>();</span><br><span class="line">    <span class="keyword">let</span> end = process.<span class="property">hrtime</span>.<span class="title function_">bigint</span>();</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`<span class="subst">$&#123;index&#125;</span>,<span class="subst">$&#123;end-start&#125;</span>`</span>);</span><br><span class="line">    <span class="keyword">await</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function"><span class="params">r</span> =&gt;</span> <span class="built_in">setTimeout</span>(r, <span class="number">50</span>));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">benchmark</span>();</span><br></pre></td></tr></table></figure><p>With that modification I could no longer measure any significant differences between connecting to DynamoDB through an Internet gateway, NAT gateway, or VPC endpoint.</p><p>I’m sorry about my mistake. Michael and I are working hard on publishing high-quality content.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>
        <![CDATA[10 Obstacles for Lift & Shift Architectures]]>
      </title>
      <link>https://cloudonaut.io/10-obstacles-for-lift-and-shift-architectures/</link>
      <description>
        <![CDATA[<p>The cloud is not only about greenfield projects. Over the last few years I have accompanied several enterprises in large migration projec]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <category domain="https://cloudonaut.io/tag/transformation/">transformation</category>
      <guid isPermaLink="true">https://cloudonaut.io/10-obstacles-for-lift-and-shift-architectures/</guid>
      <pubDate>Thu, 13 Aug 2020 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The cloud is not only about greenfield projects. Over the last few years I have accompanied several enterprises in large migration projects from on-premises to the Amazon Web Services (AWS). This blog post gives an overview of typical obstacles for lift &amp; shift architectures and points out possible solutions.</p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/legacy@730w.webp 730w, /images/2020/08/legacy@730w2x.webp 1460w, /images/2020/08/legacy@610w.webp 610w, /images/2020/08/legacy@610w2x.webp 1220w, /images/2020/08/legacy@450w.webp 450w, /images/2020/08/legacy@450w2x.webp 900w, /images/2020/08/legacy@330w.webp 330w, /images/2020/08/legacy@330w2x.webp 660w, /images/2020/08/legacy@545w.webp 545w, /images/2020/08/legacy@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/legacy@730w.jpg 730w, /images/2020/08/legacy@730w2x.jpg 1460w, /images/2020/08/legacy@610w.jpg 610w, /images/2020/08/legacy@610w2x.jpg 1220w, /images/2020/08/legacy@450w.jpg 450w, /images/2020/08/legacy@450w2x.jpg 900w, /images/2020/08/legacy@330w.jpg 330w, /images/2020/08/legacy@330w2x.jpg 660w, /images/2020/08/legacy@545w.jpg 545w, /images/2020/08/legacy@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/legacy.jpg" alt="10 Obstacles for Lift &amp; Shift Architectures" title="10 Obstacles for Lift &amp; Shift Architectures"></picture></p><h2 id="Increase-reliability-of-a-single-EC2-instance"><a href="#Increase-reliability-of-a-single-EC2-instance" class="headerlink" title="Increase reliability of a single EC2 instance"></a>Increase reliability of a single EC2 instance</h2><p>Whenever possible, you should avoid running parts of your workload on a single EC2 instance. Instead, deploy your application to least two EC2 instances in two Availability Zones. Unfortunately, there are a lot of legacy applications that do not support running on two different machines in parallel. In that case, a single EC2 instance is your only option. But how do you make the best of this situation?</p><ol><li>Set up a CloudWatch alarm to automatically recover the single EC2 instance. Doing so will replace a failed machine within the same AZ automatically.</li><li>Configure AWS Backup to create snapshots of the attached EBS volume regularly (e.g., every 12 hours).</li></ol><p>I’ve created all the architecture diagrams for this blog post with <a href="https://cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft</a>. Enjoy!</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/ec2-single-instance@730w.webp 730w, /images/2020/08/ec2-single-instance@730w2x.webp 1460w, /images/2020/08/ec2-single-instance@610w.webp 610w, /images/2020/08/ec2-single-instance@610w2x.webp 1220w, /images/2020/08/ec2-single-instance@450w.webp 450w, /images/2020/08/ec2-single-instance@450w2x.webp 900w, /images/2020/08/ec2-single-instance@330w.webp 330w, /images/2020/08/ec2-single-instance@330w2x.webp 660w, /images/2020/08/ec2-single-instance@545w.webp 545w, /images/2020/08/ec2-single-instance@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/ec2-single-instance@730w.png 730w, /images/2020/08/ec2-single-instance@730w2x.png 1460w, /images/2020/08/ec2-single-instance@610w.png 610w, /images/2020/08/ec2-single-instance@610w2x.png 1220w, /images/2020/08/ec2-single-instance@450w.png 450w, /images/2020/08/ec2-single-instance@450w2x.png 900w, /images/2020/08/ec2-single-instance@330w.png 330w, /images/2020/08/ec2-single-instance@330w2x.png 660w, /images/2020/08/ec2-single-instance@545w.png 545w, /images/2020/08/ec2-single-instance@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/ec2-single-instance.png" alt="Single HA Instance" title="Single HA Instance"></picture></p><p>Using a shared file system that replicates data among multiple AZs is another option. In that case, using an Auto Scaling Group with minimum and maximum size of 1 allows you to recover from an instance and availability zone failure.</p><h2 id="Be-aware-of-burstable-performance"><a href="#Be-aware-of-burstable-performance" class="headerlink" title="Be aware of burstable performance"></a>Be aware of burstable performance</h2><p>I’ve been tasked to debug performance issues after a lift &amp; shift migration many times. Commonly, the performance decreased significantly a few hours after migrating the workload from on-premises to the cloud. The reason: a lot of resources come with burstable performance. After a while, the resources fall back to their baseline performance, which slows down your workload significantly.</p><p>When designing an architecture for a high-load scenario, make sure you are picking resources without burstable performance.</p><table class="table table-striped table-responsive"><thead><tr><th>Service</th><th>Aspect</th><th>Burstable Options</th><th>Constant Options</th></tr></thead><tbody><tr><td>EC2</td><td>CPU</td><td>The instance types t2, t3, t3a come with burstable CPU performance.</td><td>All other instance types offer constant CPU performance (e.g., m5).</td></tr><tr><td>EC2</td><td>Network</td><td>Most instance types come with burstable network performance.</td><td>Some instance types offer constant network performance (e.g., m5.8xlarge).</td></tr><tr><td>EC2</td><td>Storage I/O (EBS)</td><td>Many instance types come with burstable storage I/O throughput.</td><td>Some instance types offer constant EBS throughput (e.g., m5.4xlarge).</td></tr><tr><td>EBS</td><td>Storage I/O</td><td>An EBS volume with type gp2, st1, or sc1 comes with burstable I/O performance.</td><td>An EBS volume of type io1 guarantees a constant I/O performance.</td></tr></tbody></table><p>The same applies to services that are using EC2 instances under the hood. RDS, for example.</p><h2 id="Optimize-I-O-throughput-between-EC2-and-EBS"><a href="#Optimize-I-O-throughput-between-EC2-and-EBS" class="headerlink" title="Optimize I&#x2F;O throughput between EC2 and EBS"></a>Optimize I&#x2F;O throughput between EC2 and EBS</h2><p>Elastic Block Store (EBS) provides network-attached block storage for EC2. Important to note, there is a network connection between your virtual machine (ECS instance) and your volume (EBS volume). The maximum I&#x2F;O throughput depends on two factors: the instance type of your EC2 instance and the type and configuration of your EBS volume.</p><p>For example, an EC instance of type t3.small comes with a baseline performance of 1,000 IOPS (IO operations per second). Where as, a general purpose (gp2) EBS volume with 500 GiB comes with a baseline performance of 1,500 IOPS. Therefore, the EC2 instance cannot saturate the maximum I&#x2F;O throughput to the volume long-term.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/ec2-ebs-throughput@730w.webp 730w, /images/2020/08/ec2-ebs-throughput@730w2x.webp 1460w, /images/2020/08/ec2-ebs-throughput@610w.webp 610w, /images/2020/08/ec2-ebs-throughput@610w2x.webp 1220w, /images/2020/08/ec2-ebs-throughput@450w.webp 450w, /images/2020/08/ec2-ebs-throughput@450w2x.webp 900w, /images/2020/08/ec2-ebs-throughput@330w.webp 330w, /images/2020/08/ec2-ebs-throughput@330w2x.webp 660w, /images/2020/08/ec2-ebs-throughput@545w.webp 545w, /images/2020/08/ec2-ebs-throughput@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/ec2-ebs-throughput@730w.png 730w, /images/2020/08/ec2-ebs-throughput@730w2x.png 1460w, /images/2020/08/ec2-ebs-throughput@610w.png 610w, /images/2020/08/ec2-ebs-throughput@610w2x.png 1220w, /images/2020/08/ec2-ebs-throughput@450w.png 450w, /images/2020/08/ec2-ebs-throughput@450w2x.png 900w, /images/2020/08/ec2-ebs-throughput@330w.png 330w, /images/2020/08/ec2-ebs-throughput@330w2x.png 660w, /images/2020/08/ec2-ebs-throughput@545w.png 545w, /images/2020/08/ec2-ebs-throughput@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/ec2-ebs-throughput.png" alt="Optimize I/O throughput between EC2 and EBS" title="Optimize I/O throughput between EC2 and EBS"></picture></p><p>A mismatch between the instance type and the volume type and configuration leads to overspending or insufficient performance. Therefore, you should bookmark the following pages of the AWS documentation to do the math when picking an instance and volume type.</p><ul><li><a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-optimized.html" target="_blank" rel="noopener">Amazon EBS–optimized instances</a></li><li><a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html" target="_blank" rel="noopener">Amazon EBS Volume Types</a></li></ul><p>Please note that the same applies to RDS. At least, as long as you are not using Aurora.</p><h2 id="Limitations-of-shared-file-systems"><a href="#Limitations-of-shared-file-systems" class="headerlink" title="Limitations of shared file systems"></a>Limitations of shared file systems</h2><p>Adding a shared file system to your architecture allows multiple EC2 instances to access the same files concurrently. Also, a shared file system on AWS automatically replicates the data among multiple availability zones.</p><p>Two services offer shared file systems:</p><ul><li>Elastic File System (EFS), a simple, scalable, fully managed elastic NFS file system.</li><li>Amazon FSx for Windows File Server, a fully managed file storage built on Windows Server.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/efs-fsx@730w.webp 730w, /images/2020/08/efs-fsx@730w2x.webp 1460w, /images/2020/08/efs-fsx@610w.webp 610w, /images/2020/08/efs-fsx@610w2x.webp 1220w, /images/2020/08/efs-fsx@450w.webp 450w, /images/2020/08/efs-fsx@450w2x.webp 900w, /images/2020/08/efs-fsx@330w.webp 330w, /images/2020/08/efs-fsx@330w2x.webp 660w, /images/2020/08/efs-fsx@545w.webp 545w, /images/2020/08/efs-fsx@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/efs-fsx@730w.png 730w, /images/2020/08/efs-fsx@730w2x.png 1460w, /images/2020/08/efs-fsx@610w.png 610w, /images/2020/08/efs-fsx@610w2x.png 1220w, /images/2020/08/efs-fsx@450w.png 450w, /images/2020/08/efs-fsx@450w2x.png 900w, /images/2020/08/efs-fsx@330w.png 330w, /images/2020/08/efs-fsx@330w2x.png 660w, /images/2020/08/efs-fsx@545w.png 545w, /images/2020/08/efs-fsx@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/efs-fsx.png" alt="EFS and FSx" title="EFS and FSx"></picture></p><p>EFS distributes data among multiple availability zones by default and scales storage capacity on-demand. You have to pay for the consumed storage capacity and for the optionally provisioned throughput. EFS uses the NFSv4 protocol and is compatible with ECS instances running Linux only.</p><p>Amazon FSx for Windows File Server is a heavy weight solution, compared to EFS. For example, FSx requires a Microsoft Active Directory managed by you or AWS. However, as FSx is using the Service Message Block (SMB) protocol mounting the shared file system works on Windows, Linux and even MacOS. FSx is billed per consumed storage as well as throughput capacity.</p><h2 id="Fine-granular-control-with-Security-Groups"><a href="#Fine-granular-control-with-Security-Groups" class="headerlink" title="Fine granular control with Security Groups"></a>Fine granular control with Security Groups</h2><p>Back in the on-premises network firewall rules referenced IP address ranges. Typically, the network was thoughtfully divided into subnets and static IP addresses had been assigned to machines. The cloud is a more dynamic place. Therefore, I do not recommend using static private IP addresses within firewall rules any more.</p><p>Use security groups to control incoming and outgoing traffic to machines instead. Think of a security group as a host-level firewall for load balancers, EC2 instances, and RDS database instances.</p><p>A security group comes with a superpower: a security group can reference another security group within a rule instead of specifying an IP address range. Let’s have a look at a simple example:</p><ul><li>The Security Group “Load Balancer” allows incoming traffic on port 443 TCP from the Internet (0.0.0.0&#x2F;0).</li><li>The Security Group “Application” allows incoming traffic on port 443 TCP from the Security Group “Load Balancer”.</li><li>The Security Group “Database” allows incoming traffic on port 5432 TCP from the Security Group “Application”.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/security-groups@730w.webp 730w, /images/2020/08/security-groups@730w2x.webp 1460w, /images/2020/08/security-groups@610w.webp 610w, /images/2020/08/security-groups@610w2x.webp 1220w, /images/2020/08/security-groups@450w.webp 450w, /images/2020/08/security-groups@450w2x.webp 900w, /images/2020/08/security-groups@330w.webp 330w, /images/2020/08/security-groups@330w2x.webp 660w, /images/2020/08/security-groups@545w.webp 545w, /images/2020/08/security-groups@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/security-groups@730w.png 730w, /images/2020/08/security-groups@730w2x.png 1460w, /images/2020/08/security-groups@610w.png 610w, /images/2020/08/security-groups@610w2x.png 1220w, /images/2020/08/security-groups@450w.png 450w, /images/2020/08/security-groups@450w2x.png 900w, /images/2020/08/security-groups@330w.png 330w, /images/2020/08/security-groups@330w2x.png 660w, /images/2020/08/security-groups@545w.png 545w, /images/2020/08/security-groups@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/security-groups.png" alt="Security Groups" title="Security Groups"></picture></p><p>Keep your network architecture as simple as possible and use security groups to control traffic on a fine granular level, that hasn’t been seen on-premises before.</p><h2 id="Multicast-is-not-supported-by-default"><a href="#Multicast-is-not-supported-by-default" class="headerlink" title="Multicast is not supported by default"></a>Multicast is not supported by default</h2><p>Some legacy workloads, especially distributed application systems, rely on multicast. By default, AWS does not support multicast within your VPC (Virtual Private Cloud). How to migrate a legacy application that requires multicast?</p><ol><li>Try to get rid of the multicast requirement. Oftentimes, there is an alternative to relying on multicast.</li><li>Use AWS Transit Gateway to enable multicast within your VPCs. Please note, that the feature is currently only available in the US East (N. Virginia), Europe (Frankfurt), South America (São Paulo), Middle East (Bahrain), Asia Pacific (Hong Kong) and Asia Pacific (Seoul) and introduces some limitations to your architecture as well (see <a href="https://docs.aws.amazon.com/vpc/latest/tgw/tgw-multicast-overview.html" target="_blank" rel="noopener">Multicast on transit gateways</a>).</li></ol><h2 id="Load-Balancing-with-Sticky-Sessions"><a href="#Load-Balancing-with-Sticky-Sessions" class="headerlink" title="Load Balancing with Sticky Sessions"></a>Load Balancing with Sticky Sessions</h2><p>AWS offers fully-managed load balancers: Elastic Load Balancing (ELB). By default, a load balancer routes each incoming request to one of the registered EC2 instances. Therefore, sequential requests from the same client will not end up on the same machine.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/elb-sticky-sessions-enabled@730w.webp 730w, /images/2020/08/elb-sticky-sessions-enabled@730w2x.webp 1460w, /images/2020/08/elb-sticky-sessions-enabled@610w.webp 610w, /images/2020/08/elb-sticky-sessions-enabled@610w2x.webp 1220w, /images/2020/08/elb-sticky-sessions-enabled@450w.webp 450w, /images/2020/08/elb-sticky-sessions-enabled@450w2x.webp 900w, /images/2020/08/elb-sticky-sessions-enabled@330w.webp 330w, /images/2020/08/elb-sticky-sessions-enabled@330w2x.webp 660w, /images/2020/08/elb-sticky-sessions-enabled@545w.webp 545w, /images/2020/08/elb-sticky-sessions-enabled@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/elb-sticky-sessions-enabled@730w.png 730w, /images/2020/08/elb-sticky-sessions-enabled@730w2x.png 1460w, /images/2020/08/elb-sticky-sessions-enabled@610w.png 610w, /images/2020/08/elb-sticky-sessions-enabled@610w2x.png 1220w, /images/2020/08/elb-sticky-sessions-enabled@450w.png 450w, /images/2020/08/elb-sticky-sessions-enabled@450w2x.png 900w, /images/2020/08/elb-sticky-sessions-enabled@330w.png 330w, /images/2020/08/elb-sticky-sessions-enabled@330w2x.png 660w, /images/2020/08/elb-sticky-sessions-enabled@545w.png 545w, /images/2020/08/elb-sticky-sessions-enabled@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/elb-sticky-sessions-enabled.png" alt="Sticky Sessions Enabled" title="Sticky Sessions Enabled"></picture></p><p>However, many legacy applications do not follow the concept of a stateless server. Instead legacy applications store state in memory, or even worse on disk. Therefore, you need to make sure that all requests from one client get served by the same EC2 instance.</p><p>To do so, you need to enable sticky sessions. After doing so, the ELB will forward all requests from the same client to the same EC2 instance. The session sticks to a certain EC2 instance.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/elb-sticky-sessions-disabled@730w.webp 730w, /images/2020/08/elb-sticky-sessions-disabled@730w2x.webp 1460w, /images/2020/08/elb-sticky-sessions-disabled@610w.webp 610w, /images/2020/08/elb-sticky-sessions-disabled@610w2x.webp 1220w, /images/2020/08/elb-sticky-sessions-disabled@450w.webp 450w, /images/2020/08/elb-sticky-sessions-disabled@450w2x.webp 900w, /images/2020/08/elb-sticky-sessions-disabled@330w.webp 330w, /images/2020/08/elb-sticky-sessions-disabled@330w2x.webp 660w, /images/2020/08/elb-sticky-sessions-disabled@545w.webp 545w, /images/2020/08/elb-sticky-sessions-disabled@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/elb-sticky-sessions-disabled@730w.png 730w, /images/2020/08/elb-sticky-sessions-disabled@730w2x.png 1460w, /images/2020/08/elb-sticky-sessions-disabled@610w.png 610w, /images/2020/08/elb-sticky-sessions-disabled@610w2x.png 1220w, /images/2020/08/elb-sticky-sessions-disabled@450w.png 450w, /images/2020/08/elb-sticky-sessions-disabled@450w2x.png 900w, /images/2020/08/elb-sticky-sessions-disabled@330w.png 330w, /images/2020/08/elb-sticky-sessions-disabled@330w2x.png 660w, /images/2020/08/elb-sticky-sessions-disabled@545w.png 545w, /images/2020/08/elb-sticky-sessions-disabled@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/elb-sticky-sessions-disabled.png" alt="Sticky Sessions Disabled" title="Sticky Sessions Disabled"></picture></p><p>However, not all types of AWS’s load balancers do support sticky sessions. Also the available mechanisms for sticky sessions are very limited compared to what is typical on an on-premises load balancer.</p><table class="table table-striped table-responsive"><thead><tr><th>Load Balancer Type</th><th>Sticky Sessions?</th><th>Mechanism</th><th>Details</th></tr></thead><tbody><tr><td>Application Load Balancer (ALB)</td><td>✅</td><td>Cookie</td><td>The Load Balancer creates stickiness cookies named <code>AWSALB</code>. The duration is set with each request. Therefore, if the client sends a request before each duration period expires, the sticky session continues.</td></tr><tr><td>Network Load Balancer (NLB)</td><td>❌</td><td>n/a</td><td></td></tr><tr><td>Classic Load Balancer (CLB)</td><td>✅</td><td>Cookie</td><td>Only HTTP/HTTPS listeners support sticky sessions. Reuse existing session cookies or let CLB add its own cookies.</td></tr></tbody></table><h2 id="Load-Balancer-with-Static-IP-Addresses"><a href="#Load-Balancer-with-Static-IP-Addresses" class="headerlink" title="Load Balancer with Static IP Addresses"></a>Load Balancer with Static IP Addresses</h2><p>Do clients connect to your load balancer by using a static IP address? Or does a 3rd party rely on a static IP address to create a firewall rule to allow traffic from your load balancer? The Classic Load Balancer (CLB) and Application Load Balancer (ALB) do use a dynamic number of IP addresses. Therefore, a client uses a DNS name that resolves to those dynamic IP addresses when connecting to a CLB&#x2F;ALB.</p><p>There are two common options to implement static public IP addresses for your load balancer:</p><ol><li>Use a Network Load Balancer (NLB) which comes with a static IP address per availability zone.</li><li>Use Global Accelerator to get two static IP addresses and forward the traffic to your ALB or EC2 instances.</li></ol><h2 id="RDS-is-a-must-have-but-comes-with-limitations"><a href="#RDS-is-a-must-have-but-comes-with-limitations" class="headerlink" title="RDS is a must have but comes with limitations"></a>RDS is a must have but comes with limitations</h2><p>Are you migrating a workload that involves a relational database: PostgreSQL, MySQL, MariaDB, Oracle Database, or SQL Server? Use the Relational Database Service (RDS) instead of operating one of these database systems on top of EC2 yourself. RDS provides a high available database replicated among at least two availability zones out-of-the-box. On top of that, patching, resizing, and backing up the database system are solved problems as well. That’s a game changer and one of the biggest advantages you can get out of a lift &amp; shift migration.</p><p>To be able to provide a relational database as a service, AWS introduced some restrictions.</p><ul><li>You cannot log into the machine via SSH or RDP.</li><li>You might not be granted database administrator (DBA) access to 100%.</li><li>You cannot access the file system directly.</li><li>Some features are not supported (e.g., RDS Oracle does not support Database Vault, Flashback Database, and Real Application Testing)</li></ul><h2 id="Verify-that-RDS-failover-will-work"><a href="#Verify-that-RDS-failover-will-work" class="headerlink" title="Verify that RDS failover will work"></a>Verify that RDS failover will work</h2><p>When using RDS for a production-workload you should enable the “Multi-AZ” feature. RDS will spin up two machines in two different availability zones. One machine becomes the primary instance and answers all read and write requests. The other instance becomes the standby instance. RDS replicates data between the primary and the secondary instance synchronously.</p><p>A DNS name like <code>mydb.xyz.us-east-1.amazonaws.com</code> points to the IP address of the primary instance. In case of a failure, or during maintenance, the standby instance becomes the new primary instance. Therefore, RDS updates the DNS name.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/rds-failover@730w.webp 730w, /images/2020/08/rds-failover@730w2x.webp 1460w, /images/2020/08/rds-failover@610w.webp 610w, /images/2020/08/rds-failover@610w2x.webp 1220w, /images/2020/08/rds-failover@450w.webp 450w, /images/2020/08/rds-failover@450w2x.webp 900w, /images/2020/08/rds-failover@330w.webp 330w, /images/2020/08/rds-failover@330w2x.webp 660w, /images/2020/08/rds-failover@545w.webp 545w, /images/2020/08/rds-failover@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/rds-failover@730w.png 730w, /images/2020/08/rds-failover@730w2x.png 1460w, /images/2020/08/rds-failover@610w.png 610w, /images/2020/08/rds-failover@610w2x.png 1220w, /images/2020/08/rds-failover@450w.png 450w, /images/2020/08/rds-failover@450w2x.png 900w, /images/2020/08/rds-failover@330w.png 330w, /images/2020/08/rds-failover@330w2x.png 660w, /images/2020/08/rds-failover@545w.png 545w, /images/2020/08/rds-failover@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/rds-failover.png" alt="RDS Failover" title="RDS Failover"></picture></p><p>Make sure your application is capable of re-connecting and re-resolving the database’s DNS name. Not all applications, runtimes, libraries do so by default. Some are even not capable of doing so. Before going live, make sure you have triggered a failover to the standby instance and double check whether your application is capable of connecting to the database afterwards.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>All the best with migrating your workloads to the cloud. Watch out for the following obstacles and your lift &amp; shift project will become a huge success.</p><ol><li>Increase reliability of a single EC2 instance: configure an auto recovery alarm and snapshots.</li><li>Be aware of burstable performance: CPU, storage, and network.</li><li>Optimize I&#x2F;O throughput between EC2 and EBS: make sure your instance type provides sufficient I&#x2F;O throughput.</li><li>Limitations of shared file systems: EFS accessible from Linux systems only, FSx requires Active Directory.</li><li>Fine granular control with Security Groups: reference security groups of up-&#x2F;downstream components to avoid having to think about IP address ranges.</li><li>Multicast is not supported by default: try to modify your application to get rid of the multicast requirement or use Transit Gateway to enable multicast.</li><li>Load Balancing with Sticky Sessions: only Cookie based sticky sessions are supported by CLB and ALB.</li><li>Load Balancer with Static IP Addresses: use a NLB or Global Accelerator and ALB to get static IP addresses for your load balancer.</li><li>RDS is a must have but comes with limitations: no way to SSH&#x2F;RDP into your database server, no direct access to the file system. But: RDS makes the difference for lift &amp; shift projects.</li><li>Verify that RDS failover will work: applications need to be able to re-connect to the database as well as to re-resolve the database’s DNS name.</li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Month in Review: July 2020</title>
      <link>https://cloudonaut.io/aws-month-in-review-july-2020/</link>
      <description>
        <![CDATA[<p>The world of AWS changes fast. This review summarizes the most important news from July 2020. The roundup does not include version update]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/retrospective/">retrospective</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-month-in-review-july-2020/</guid>
      <pubDate>Mon, 03 Aug 2020 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The world of AWS changes fast. This review summarizes the most important news from July 2020. The roundup does not include version updates, region expansion news, and minor changes. Instead, we focus on the most important news. But not only that! Our monthly review does include our opinion and evaluation as well.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/month-in-review@730w.webp 730w, /images/2020/08/month-in-review@730w2x.webp 1460w, /images/2020/08/month-in-review@610w.webp 610w, /images/2020/08/month-in-review@610w2x.webp 1220w, /images/2020/08/month-in-review@450w.webp 450w, /images/2020/08/month-in-review@450w2x.webp 900w, /images/2020/08/month-in-review@330w.webp 330w, /images/2020/08/month-in-review@330w2x.webp 660w, /images/2020/08/month-in-review@545w.webp 545w, /images/2020/08/month-in-review@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/month-in-review@730w.jpg 730w, /images/2020/08/month-in-review@730w2x.jpg 1460w, /images/2020/08/month-in-review@610w.jpg 610w, /images/2020/08/month-in-review@610w2x.jpg 1220w, /images/2020/08/month-in-review@450w.jpg 450w, /images/2020/08/month-in-review@450w2x.jpg 900w, /images/2020/08/month-in-review@330w.jpg 330w, /images/2020/08/month-in-review@330w2x.jpg 660w, /images/2020/08/month-in-review@545w.jpg 545w, /images/2020/08/month-in-review@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/month-in-review.jpg" alt="AWS Month in Review" title="AWS Month in Review"></picture></p><h2 id="Performance-boost-for-EFS"><a href="#Performance-boost-for-EFS" class="headerlink" title="Performance boost for EFS"></a>Performance boost for EFS</h2><p>In theory, sharing a file system with multiple virtual machines, containers, or function invocations is a great thing. EFS is the go-to service in those scenarios (at least if your workload runs on a UNIX-based OS). However, I have struggled with high latencies when accessing files from EFS from time to time.</p><p>Therefore, the following announcement caught my eye: <a href="https://aws.amazon.com/about-aws/whats-new/2020/07/amazon-elastic-file-system-increases-per-client-throughput/" target="_blank" rel="noopener">Amazon Elastic File System increases per-client throughput by 100%</a>.</p><p>I need to run some performance tests on EFS shortly to find out more about the details.</p><h2 id="WAF-is-growing-up"><a href="#WAF-is-growing-up" class="headerlink" title="WAF is growing up"></a>WAF is growing up</h2><p>The AWS WAF allows you to block or throttle incoming HTTP requests to your CloudFront distributions or Application Load Balancers (ALB). I ran into the problem that AWS WAF was not capable of inspecting IP addresses when placed behind a 3rd party CDN or any other HTTP proxy. Unfortunately, there was no way to define a rule taking the original source IP address into account.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/waf-xff@730w.webp 730w, /images/2020/08/waf-xff@730w2x.webp 1460w, /images/2020/08/waf-xff@610w.webp 610w, /images/2020/08/waf-xff@610w2x.webp 1220w, /images/2020/08/waf-xff@450w.webp 450w, /images/2020/08/waf-xff@450w2x.webp 900w, /images/2020/08/waf-xff@330w.webp 330w, /images/2020/08/waf-xff@330w2x.webp 660w, /images/2020/08/waf-xff@545w.webp 545w, /images/2020/08/waf-xff@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/waf-xff@730w.png 730w, /images/2020/08/waf-xff@730w2x.png 1460w, /images/2020/08/waf-xff@610w.png 610w, /images/2020/08/waf-xff@610w2x.png 1220w, /images/2020/08/waf-xff@450w.png 450w, /images/2020/08/waf-xff@450w2x.png 900w, /images/2020/08/waf-xff@330w.png 330w, /images/2020/08/waf-xff@330w2x.png 660w, /images/2020/08/waf-xff@545w.png 545w, /images/2020/08/waf-xff@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/waf-xff.png" alt="WAF supports XFF header" title="WAF supports XFF header"></picture></p><p>Luckily, <a href="https://aws.amazon.com/about-aws/whats-new/2020/07/support-x-forwarded-for-header-available-aws-waf/" target="_blank" rel="noopener">support for X-Forwarded-For (XFF) header is now available for AWS WAF</a>.</p><h2 id="Containers-for-the-masses"><a href="#Containers-for-the-masses" class="headerlink" title="Containers for the masses?"></a>Containers for the masses?</h2><p>AWS and Docker announced a partnership: <a href="https://aws.amazon.com/about-aws/whats-new/2020/07/docker-and-aws-collaborate-to-help-deploy-applications-to-amazon-ecs-on-aws-fargate/" target="_blank" rel="noopener">Docker and AWS collaborate to help deploy applications to Amazon ECS on AWS Fargate</a>.</p><p>What’s behind the announcement? <em>Docker Compose</em> supports ECS. However, the feature is a beta release. The functionality is not that impressive. <code>docker ecs compose up</code> generates and executes a CloudFormation template. The CloudFormation template creates a load balancer, ECS cluster, ECS service, security groups, and IAM roles. However, there is only very little control over the details (e.g., IAM policy, …). In my opinion, integrating ECS into Docker Compose will only work for simple scenarios. Using CloudFormation, Terraform, or the CDK will give you much more flexibility, which will pay off in the long run for complex situations.</p><p>The partnership between Docker and AWS is probably not too strong as <a href="https://aws.amazon.com/about-aws/whats-new/2020/07/amazon-ecs-announces-aws-copilot/" target="_blank" rel="noopener">Amazon ECS announces AWS Copilot, a new CLI to deploy and operate containers in AWS</a> as well. Interestingly, AWS Copilot follows the same approach as Docker Compose: you define a manifest describing your container-based service, and the CLI tool generates and deploys CloudFormation templates - including a VPC, ECS cluster, ECS service, and so so - for you. The same problem as above, you have very little flexibility. But whenever you have to impress someone with being able to deploy a container within a few minutes: here you go!</p><h2 id="EKS-panting-after-K8s"><a href="#EKS-panting-after-K8s" class="headerlink" title="EKS panting after K8s"></a>EKS panting after K8s</h2><p>Kubernetes 1.17 was released on December 9th, 2019. It took AWS about nine months to get that version up and running on EKS: <a href="https://aws.amazon.com/about-aws/whats-new/2020/07/amazon-eks-supports-kubernetes-version-1-17/" target="_blank" rel="noopener">Amazon EKS now supports Kubernetes version 1.17</a>. Unluckily, K8s promises committed to support each version for about nine months. So the latest version of K8s available on EKS will soon lose support from the K8s community. That is annoying!</p><p>But there is good news as well. The <a href="https://aws.amazon.com/about-aws/whats-new/2020/07/amazon-efs-csi-driver-now-generally-available/" target="_blank" rel="noopener">Amazon EFS CSI Driver is now generally available</a>. The driver allows you to mount EFS file systems for your containers easily and comes with built-in support for in-transit encryption. The following code snippet shows an excerpt from the<br><a href="https://github.com/kubernetes-sigs/aws-efs-csi-driver/tree/master/examples/kubernetes/encryption_in_transit" target="_blank" rel="noopener">encryption_in_transit example</a>.</p><figure class="highlight dts"><table><tr><td class="code"><pre><span class="line"><span class="symbol">apiVersion:</span> v1</span><br><span class="line"><span class="symbol">kind:</span> PersistentVolume</span><br><span class="line"><span class="symbol">metadata:</span></span><br><span class="line"><span class="symbol">  name:</span> efs-pv</span><br><span class="line"><span class="symbol">spec:</span></span><br><span class="line"><span class="symbol">  capacity:</span></span><br><span class="line"><span class="symbol">    storage:</span> <span class="number">5</span>Gi</span><br><span class="line"><span class="symbol">  volumeMode:</span> Filesystem</span><br><span class="line"><span class="symbol">  accessModes:</span></span><br><span class="line">    - ReadWriteOnce</span><br><span class="line"><span class="symbol">  persistentVolumeReclaimPolicy:</span> Retain</span><br><span class="line"><span class="symbol">  storageClassName:</span> efs-sc</span><br><span class="line"><span class="symbol">  csi:</span></span><br><span class="line"><span class="symbol">    driver:</span> efs.csi.aws.com</span><br><span class="line"><span class="symbol">    volumeHandle:</span> fs<span class="number">-4</span>af69aab</span><br><span class="line"><span class="symbol">    volumeAttributes:</span></span><br><span class="line"><span class="symbol">      encryptInTransit:</span> <span class="string">&quot;true&quot;</span></span><br></pre></td></tr></table></figure><h2 id="Data-Privacy-Nightmares"><a href="#Data-Privacy-Nightmares" class="headerlink" title="Data Privacy Nightmares"></a>Data Privacy Nightmares</h2><p>In July, the Court of Justice of the European Union (CJEU) shocked the world of cloud computing and SaaS. The EU-US Privacy Shield is no longer valid for transferring personal data from the EU to the US. In summary, the CJEU said that the US data privacy level is far below the standards applicable within the EU mainly because government agencies have extensive rights to gain access to private data.</p><p>AWS responded with a <a href="https://aws.amazon.com/blogs/security/customer-update-aws-and-the-eu-us-privacy-shield/" target="_blank" rel="noopener">Customer update: AWS and the EU-US Privacy Shield</a>. In short, AWS says that we, as a customer, do not have to rely on the EU-US Privacy Shield when transferring personal data from the EU to AWS regions in the US. Instead, so-called Standard Contractual Clauses (SCCs) are part of the AWS’s service terms.</p><blockquote><p>Disclaimer: I’m not a lawyer. Instead, I summarize what I learned about data privacy within the last weeks.</p></blockquote><p>So far, in its judgment, the CJEU mentioned that SCCs remain valid for the time being. However, the underlying problems with the privacy shield are precisely the same as with SCCs. Therefore, data privacy experts expect that courts and public authorities might also stop the transfer of personal data under SCCs. Right now, we are in a very awkward position. One can be curious whether a new agreement will replace the Privacy Shield between the EU and the US.</p><p>Typically, AWS repeats the following mantra at every opportunity: data never leaves a region. However, the following story made the rounds in July: AWS Customers are Opting in to Sharing AI Data Sets with Amazon Outside their Chosen Regions and Many Didn’t Know. Many customers, including myself, have been caught off guard by this news. However, the Service Terms state that Amazon CodeGuru Profiler, Amazon Comprehend, Amazon Lex, Amazon Polly, Amazon Rekognition, Amazon Textract, Amazon Transcribe, and Amazon Translate might transfer data into another region to improve the underlying artificial intelligence.</p><p>AWS answered with a way to <a href="https://aws.amazon.com/about-aws/whats-new/2020/07/easily-manage-content-policies-ai-services-aws-organizations/" target="_blank" rel="noopener">Easily manage your content policies for AI services with AWS Organizations</a>.</p><p>The following steps are needed to opt-out of sharing your AI data. First of all, open <em>AWS Organizations</em> and switch to the <em>Policies</em> tab. Click the <em>AI services opt-out policies</em> link.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/ai-optout-1@730w.webp 730w, /images/2020/08/ai-optout-1@730w2x.webp 1460w, /images/2020/08/ai-optout-1@610w.webp 610w, /images/2020/08/ai-optout-1@610w2x.webp 1220w, /images/2020/08/ai-optout-1@450w.webp 450w, /images/2020/08/ai-optout-1@450w2x.webp 900w, /images/2020/08/ai-optout-1@330w.webp 330w, /images/2020/08/ai-optout-1@330w2x.webp 660w, /images/2020/08/ai-optout-1@545w.webp 545w, /images/2020/08/ai-optout-1@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/ai-optout-1@730w.png 730w, /images/2020/08/ai-optout-1@730w2x.png 1460w, /images/2020/08/ai-optout-1@610w.png 610w, /images/2020/08/ai-optout-1@610w2x.png 1220w, /images/2020/08/ai-optout-1@450w.png 450w, /images/2020/08/ai-optout-1@450w2x.png 900w, /images/2020/08/ai-optout-1@330w.png 330w, /images/2020/08/ai-optout-1@330w2x.png 660w, /images/2020/08/ai-optout-1@545w.png 545w, /images/2020/08/ai-optout-1@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/ai-optout-1.png" alt="Opt-out to share data for training AI: Step 1" title="Opt-out to share data for training AI: Step 1"></picture></p><p>Press the <em>Create policy</em> button.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/ai-optout-2@730w.webp 730w, /images/2020/08/ai-optout-2@730w2x.webp 1460w, /images/2020/08/ai-optout-2@610w.webp 610w, /images/2020/08/ai-optout-2@610w2x.webp 1220w, /images/2020/08/ai-optout-2@450w.webp 450w, /images/2020/08/ai-optout-2@450w2x.webp 900w, /images/2020/08/ai-optout-2@330w.webp 330w, /images/2020/08/ai-optout-2@330w2x.webp 660w, /images/2020/08/ai-optout-2@545w.webp 545w, /images/2020/08/ai-optout-2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/ai-optout-2@730w.png 730w, /images/2020/08/ai-optout-2@730w2x.png 1460w, /images/2020/08/ai-optout-2@610w.png 610w, /images/2020/08/ai-optout-2@610w2x.png 1220w, /images/2020/08/ai-optout-2@450w.png 450w, /images/2020/08/ai-optout-2@450w2x.png 900w, /images/2020/08/ai-optout-2@330w.png 330w, /images/2020/08/ai-optout-2@330w2x.png 660w, /images/2020/08/ai-optout-2@545w.png 545w, /images/2020/08/ai-optout-2@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/ai-optout-2.png" alt="Opt-out to share data for training AI: Step 2" title="Opt-out to share data for training AI: Step 2"></picture></p><p>The following JSON snippet contains a policy defining an opt-out for all AI services of all accounts within the organization.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;services&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;@@operators_allowed_for_child_policies&quot;</span>: [</span><br><span class="line">      <span class="string">&quot;@@none&quot;</span></span><br><span class="line">    ],</span><br><span class="line">    <span class="string">&quot;default&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;@@operators_allowed_for_child_policies&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;@@none&quot;</span></span><br><span class="line">      ],</span><br><span class="line">      <span class="string">&quot;opt_out_policy&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;@@operators_allowed_for_child_policies&quot;</span>: [</span><br><span class="line">          <span class="string">&quot;@@none&quot;</span></span><br><span class="line">        ],</span><br><span class="line">        <span class="string">&quot;@@assign&quot;</span>: <span class="string">&quot;optOut&quot;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/ai-optout-3@730w.webp 730w, /images/2020/08/ai-optout-3@730w2x.webp 1460w, /images/2020/08/ai-optout-3@610w.webp 610w, /images/2020/08/ai-optout-3@610w2x.webp 1220w, /images/2020/08/ai-optout-3@450w.webp 450w, /images/2020/08/ai-optout-3@450w2x.webp 900w, /images/2020/08/ai-optout-3@330w.webp 330w, /images/2020/08/ai-optout-3@330w2x.webp 660w, /images/2020/08/ai-optout-3@545w.webp 545w, /images/2020/08/ai-optout-3@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/ai-optout-3@730w.png 730w, /images/2020/08/ai-optout-3@730w2x.png 1460w, /images/2020/08/ai-optout-3@610w.png 610w, /images/2020/08/ai-optout-3@610w2x.png 1220w, /images/2020/08/ai-optout-3@450w.png 450w, /images/2020/08/ai-optout-3@450w2x.png 900w, /images/2020/08/ai-optout-3@330w.png 330w, /images/2020/08/ai-optout-3@330w2x.png 660w, /images/2020/08/ai-optout-3@545w.png 545w, /images/2020/08/ai-optout-3@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/ai-optout-3.png" alt="Opt-out to share data for training AI: Step 3" title="Opt-out to share data for training AI: Step 3"></picture></p><p>Last but not least, you need to attach the opt-out policy to your organization’s root node.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/08/ai-optout-4@730w.webp 730w, /images/2020/08/ai-optout-4@730w2x.webp 1460w, /images/2020/08/ai-optout-4@610w.webp 610w, /images/2020/08/ai-optout-4@610w2x.webp 1220w, /images/2020/08/ai-optout-4@450w.webp 450w, /images/2020/08/ai-optout-4@450w2x.webp 900w, /images/2020/08/ai-optout-4@330w.webp 330w, /images/2020/08/ai-optout-4@330w2x.webp 660w, /images/2020/08/ai-optout-4@545w.webp 545w, /images/2020/08/ai-optout-4@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/08/ai-optout-4@730w.png 730w, /images/2020/08/ai-optout-4@730w2x.png 1460w, /images/2020/08/ai-optout-4@610w.png 610w, /images/2020/08/ai-optout-4@610w2x.png 1220w, /images/2020/08/ai-optout-4@450w.png 450w, /images/2020/08/ai-optout-4@450w2x.png 900w, /images/2020/08/ai-optout-4@330w.png 330w, /images/2020/08/ai-optout-4@330w2x.png 660w, /images/2020/08/ai-optout-4@545w.png 545w, /images/2020/08/ai-optout-4@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/08/ai-optout-4.png" alt="Opt-out to share data for training AI: Step 4" title="Opt-out to share data for training AI: Step 4"></picture></p><p>That’s it. Select an account and use the <code>View effective AI services opt-out policy</code> action to verify the opt-out policy.</p><h2 id="Is-CDK-eating-Infrastructure-as-Code"><a href="#Is-CDK-eating-Infrastructure-as-Code" class="headerlink" title="Is CDK eating Infrastructure as Code?"></a>Is CDK eating Infrastructure as Code?</h2><p>The <a href="https://aws.amazon.com/cdk/" target="_blank" rel="noopener">Cloud Development Kit (CDK)</a> enables you to use the programming language of your choice to generate CloudFormation templates more efficiently. AWS and Hashicorp are <a href="https://aws.amazon.com/blogs/developer/introducing-the-cloud-development-kit-for-terraform-preview/" target="_blank" rel="noopener">Introducing the Cloud Development Kit for Terraform (Preview)</a>. The CDK for Terraform is marked <strong>experimental</strong> and is therefore not ready production yet. Also, the project contains low-level constructs (L1) mapping to Terraform resources one-on-one. More powerful high-level constructs will probably follow. You should watch out for what is going on with the <a href="https://github.com/hashicorp/terraform-cdk" target="_blank" rel="noopener">CDK for Terraform</a> project.</p><p>Creating deployment pipelines based on CodePipeline, CodeBuild, and CloudFormation can be challenging. You need to configure tens of resources and make sure the different steps in the pipeline are authorized to do their work. Luckily, AWS is <a href="https://aws.amazon.com/about-aws/whats-new/2020/07/announcing-cdk-pipelines-preview/" target="_blank" rel="noopener">Announcing CDK Pipelines Preview, continuous delivery for AWS CDK applications</a>. The high-level construct is still in developer preview, but will simplify creating deployment pipelines a lot in the future.</p><h2 id="New-features-for-CodeBuild"><a href="#New-features-for-CodeBuild" class="headerlink" title="New features for CodeBuild"></a>New features for CodeBuild</h2><p>The team responsible for CodeBuild shipped a bunch of features in July: </p><p>First, <a href="https://aws.amazon.com/about-aws/whats-new/2020/07/aws-codebuild-now-supports-accessing-build-environments-with-aws-session-manager/" target="_blank" rel="noopener">AWS CodeBuild now supports accessing Build Environments with AWS Session Manager</a>.</p><ol><li>Use <code>codebuild-breakpoint</code> to add a breakpoint to your buildspec.yml.</li><li>Check the <code>Enable session connection</code> option when starting the build.</li><li>Execute <code>aws codebuild batch-get-builds --ids ...</code> to get the session target.</li><li>Use <code>aws ssm start-session --target ...</code> to connect to the build run.</li><li>Type in <code>codebuild-resume</code> to resume the build.</li></ol><p>It took me more than 30 minutes to connect with a paused build run. The whole process is a little bit fiddly. However, being able to debug a build run might be worth it.</p><p>Second, <a href="https://aws.amazon.com/about-aws/whats-new/2020/07/aws-codebuild-now-supports-parallel-and-coordinated-executions-of-a-build-project/" target="_blank" rel="noopener">AWS CodeBuild now supports parallel and coordinated executions of a build project</a>. At first glance, I thought that this new feature allows us to restrict the maximum number of builds per CodeBuild project to 1. However, that is not the case. Instead, the feature will enable you to specify multiple build environments within the <code>buildspec.yml</code>. CodeBuild will execute the build in all those environments in parallel or order. As I’m typically using CodePipeline to orchestrate CodeBuild, I don’t have a use case for that, but your mileage may vary.</p><p>Last but not least, <a href="https://aws.amazon.com/about-aws/whats-new/2020/07/aws-codebuild-supports-code-coverage-reporting/" target="_blank" rel="noopener">AWS CodeBuild supports code coverage reporting</a>.</p><h2 id="ARM-all-the-things"><a href="#ARM-all-the-things" class="headerlink" title="ARM all the things!"></a>ARM all the things!</h2><p>AWS continues to ship new instance types based on its ARM processor. <a href="https://aws.amazon.com/about-aws/whats-new/2020/07/announcing-new-amazon-ec2-instances-powered-aws-graviton2-processors/" target="_blank" rel="noopener">Announcing new Amazon EC2 M6gd, C6gd, and R6gd instances powered by AWS Graviton2 processors</a>. It seems like, not only Apple but AWS is pushing to shift from Intel to ARM. It is time for us to take a closer look at the Graviton2.  Stay tuned for more insights!</p><h2 id="Streaming-video-made-easy"><a href="#Streaming-video-made-easy" class="headerlink" title="Streaming video made easy"></a>Streaming video made easy</h2><p>AWS is <a href="https://aws.amazon.com/about-aws/whats-new/2020/07/introducing-amazon-ivs/" target="_blank" rel="noopener">Introducing Amazon Interactive Video Service (Amazon IVS)</a>, which looks interesting. Michael already played around with the video streaming service. We are thinking about using the service to host some webinars and online events in the future.</p><h2 id="Feedback-welcome"><a href="#Feedback-welcome" class="headerlink" title="Feedback welcome!"></a>Feedback welcome!</h2><p>I hope you did enjoy this month in review. Did I miss anything important? Please let me know what you think about this format and whether you want to read such a summary monthly.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How do you choose the best storage option on AWS?</title>
      <link>https://cloudonaut.io/how-do-you-choose-the-best-storage-option-on-aws/</link>
      <description>
        <![CDATA[<p>Choosing storage service is critical when designing a cloud architecture. Read on to learn about the characteristics, limitations, typica]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/ebs/">ebs</category>
      <category domain="https://cloudonaut.io/tag/efs/">efs</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <category domain="https://cloudonaut.io/tag/fsx/">fsx</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-do-you-choose-the-best-storage-option-on-aws/</guid>
      <pubDate>Wed, 29 Jul 2020 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Choosing storage service is critical when designing a cloud architecture. Read on to learn about the characteristics, limitations, typical use cases, and a decision tree for the following options to store data on AWS:</p><ul><li><strong>Instance Store</strong> provides low latency and high throughput block storage for EC2 instances.</li><li><strong>EBS (Elastic Block Storage)</strong> provides persistent block storage for EC2 instances.</li><li><strong>EFS (Elastic File System)</strong> provides a scalable and fault-tolerant network file system (NFSv4).</li><li><strong>FSx (File System for Windows File Server)</strong> provides a fully-managed Windows File Server.</li><li><strong>S3 (Simple Storage Service)</strong> provides highly scalable and fault-tolerant object storage.</li></ul><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/spinning-disk@730w.webp 730w, /images/2020/07/spinning-disk@730w2x.webp 1460w, /images/2020/07/spinning-disk@610w.webp 610w, /images/2020/07/spinning-disk@610w2x.webp 1220w, /images/2020/07/spinning-disk@450w.webp 450w, /images/2020/07/spinning-disk@450w2x.webp 900w, /images/2020/07/spinning-disk@330w.webp 330w, /images/2020/07/spinning-disk@330w2x.webp 660w, /images/2020/07/spinning-disk@545w.webp 545w, /images/2020/07/spinning-disk@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/spinning-disk@730w.jpg 730w, /images/2020/07/spinning-disk@730w2x.jpg 1460w, /images/2020/07/spinning-disk@610w.jpg 610w, /images/2020/07/spinning-disk@610w2x.jpg 1220w, /images/2020/07/spinning-disk@450w.jpg 450w, /images/2020/07/spinning-disk@450w2x.jpg 900w, /images/2020/07/spinning-disk@330w.jpg 330w, /images/2020/07/spinning-disk@330w2x.jpg 660w, /images/2020/07/spinning-disk@545w.jpg 545w, /images/2020/07/spinning-disk@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/spinning-disk.jpg" alt="Storage on AWS" title="Storage on AWS"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/24-storage-on-aws/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>The following diagram was created with <a href="https://cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft</a>. The diagram shows the possibility to access all of the discussed storage options from an EC2 instance. EFS and FSx are accessible from on-premises as well. S3 is even accessible from the Internet.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/storage-on-aws@730w.webp 730w, /images/2020/07/storage-on-aws@730w2x.webp 1460w, /images/2020/07/storage-on-aws@610w.webp 610w, /images/2020/07/storage-on-aws@610w2x.webp 1220w, /images/2020/07/storage-on-aws@450w.webp 450w, /images/2020/07/storage-on-aws@450w2x.webp 900w, /images/2020/07/storage-on-aws@330w.webp 330w, /images/2020/07/storage-on-aws@330w2x.webp 660w, /images/2020/07/storage-on-aws@545w.webp 545w, /images/2020/07/storage-on-aws@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/storage-on-aws@730w.png 730w, /images/2020/07/storage-on-aws@730w2x.png 1460w, /images/2020/07/storage-on-aws@610w.png 610w, /images/2020/07/storage-on-aws@610w2x.png 1220w, /images/2020/07/storage-on-aws@450w.png 450w, /images/2020/07/storage-on-aws@450w2x.png 900w, /images/2020/07/storage-on-aws@330w.png 330w, /images/2020/07/storage-on-aws@330w2x.png 660w, /images/2020/07/storage-on-aws@545w.png 545w, /images/2020/07/storage-on-aws@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/storage-on-aws.png" alt="Storage on AWS" title="Storage on AWS"></picture></p><p>Before we start, I recommend answering the following questions for the workload that you have in mind when selecting a storage option.</p><ul><li>What are the durability and availability requirements for your workload?</li><li>How much data do you need to store?</li><li>What’s the baseline and burst I&#x2F;O throughput required by your workload?</li><li>What’s the interface your workload expects to read&#x2F;write data? A file system? Does your workload offer an S3 integration?</li><li>Does your workload rely on low latency when accessing data from storage? What level of latency is tolerable?</li></ul><p>Keep those questions in mind, when reading through the brief introductions of the storage options on AWS.</p><h2 id="Instance-Store"><a href="#Instance-Store" class="headerlink" title="Instance Store"></a>Instance Store</h2><p>EC2 Instance Store provides low latency and high throughput block storage for EC2 instances. The instance store offers access to HDDs or SSDs directly attached to the host machine of your virtual machine. Consider the instance store as ephemeral: data is lost whenever you or AWS terminates or stops the virtual machine.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/instance-store@730w.webp 730w, /images/2020/07/instance-store@730w2x.webp 1460w, /images/2020/07/instance-store@610w.webp 610w, /images/2020/07/instance-store@610w2x.webp 1220w, /images/2020/07/instance-store@450w.webp 450w, /images/2020/07/instance-store@450w2x.webp 900w, /images/2020/07/instance-store@330w.webp 330w, /images/2020/07/instance-store@330w2x.webp 660w, /images/2020/07/instance-store@545w.webp 545w, /images/2020/07/instance-store@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/instance-store@730w.png 730w, /images/2020/07/instance-store@730w2x.png 1460w, /images/2020/07/instance-store@610w.png 610w, /images/2020/07/instance-store@610w2x.png 1220w, /images/2020/07/instance-store@450w.png 450w, /images/2020/07/instance-store@450w2x.png 900w, /images/2020/07/instance-store@330w.png 330w, /images/2020/07/instance-store@330w2x.png 660w, /images/2020/07/instance-store@545w.png 545w, /images/2020/07/instance-store@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/instance-store.png" alt="Instance Store" title="Instance Store"></picture></p><p>AWS offers virtual machines of different instance types and groups those types into instance families. Some instance families do not only come with a specific amount of CPU and memory resources but with EC2 Instance Store as well.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">vCPU</th><th style="text-align:right">Memory</th><th>Instance Store</th></tr></thead><tbody><tr><td>m5.large</td><td style="text-align:right">2</td><td style="text-align:right">8.0 GiB</td><td>n/a</td></tr><tr><td>m5.large</td><td style="text-align:right">2</td><td style="text-align:right">8.0 GiB</td><td>75 GiB NVMe SSD</td></tr><tr><td>c5.large</td><td style="text-align:right">2</td><td style="text-align:right">5.0 GiB</td><td>n/a</td></tr><tr><td>c5d.large</td><td style="text-align:right">2</td><td style="text-align:right">5.0 GiB</td><td>50 GiB NVMe SSD</td></tr><tr><td>c5d.2xlarge</td><td style="text-align:right">8</td><td style="text-align:right">16.0 GiB</td><td>200 GiB NVMe SSD</td></tr><tr><td>i3en.24xlarge</td><td style="text-align:right">96</td><td style="text-align:right">768.0 GiB</td><td>60000 GiB (8 * 7500 GiB NVMe SSD)</td></tr></tbody></table><p>Use EC2 Instance Store when low latency and high throughput access to your data is essential. To avoid data loss, you should only store temporary data or make sure data gets replicated to a cluster of EC2 instances automatically. Typical use cases are swap volumes, distributed data stores (e.g., Elasticsearch, Kafka, …), all kinds of temporary data (e.g., caches, batch processing, …).</p><h2 id="EBS-Elastic-Block-Storage"><a href="#EBS-Elastic-Block-Storage" class="headerlink" title="EBS (Elastic Block Storage)"></a>EBS (Elastic Block Storage)</h2><p>Think of EBS as a SAN (Storage Area Network). Ony an EC2 instance can access data on an EBS volume. Typically, there is a one-to-one relationship between an EC2 instance and an EBS volume.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/ebs@730w.webp 730w, /images/2020/07/ebs@730w2x.webp 1460w, /images/2020/07/ebs@610w.webp 610w, /images/2020/07/ebs@610w2x.webp 1220w, /images/2020/07/ebs@450w.webp 450w, /images/2020/07/ebs@450w2x.webp 900w, /images/2020/07/ebs@330w.webp 330w, /images/2020/07/ebs@330w2x.webp 660w, /images/2020/07/ebs@545w.webp 545w, /images/2020/07/ebs@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/ebs@730w.png 730w, /images/2020/07/ebs@730w2x.png 1460w, /images/2020/07/ebs@610w.png 610w, /images/2020/07/ebs@610w2x.png 1220w, /images/2020/07/ebs@450w.png 450w, /images/2020/07/ebs@450w2x.png 900w, /images/2020/07/ebs@330w.png 330w, /images/2020/07/ebs@330w2x.png 660w, /images/2020/07/ebs@545w.png 545w, /images/2020/07/ebs@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/ebs.png" alt="EBS" title="EBS"></picture></p><p>Typical use cases for EBS are: operating database-like systems on EC2 instances (e.g., MS SQL) or migrating Lift &amp; Shift workloads, which require access to a file system and write data often.</p><p>EBS comes with the following advantages:</p><ul><li>The lifecycle of an EBS volume is independent of the EC2 instance. Stopping and terminating a virtual machine does not affect the data stored on the volume. Therefore, we consider an EBS volume as persistent storage.</li><li>EBS replicates data among multiple disks within an availability zone by default. AWS aims for data durability of 99.8% over a year.</li><li>Creating a snapshot of an EBS volume will backup the data among multiple availability zones.</li></ul><p>However, there are some limitations, as well:</p><ul><li>Compared to the EC2 Instance Store, the network connection between the EC2 instance and the EBS volume adds latency and limits the maximum throughput.</li><li>You need to provision the storage capacity of an EBS volume upfront. The capacity does not grow automatically at runtime. Therefore, you typically need to overprovision storage capacity.</li><li>An EBS volume replicates data within a single availability zone. The SLA for an EC2 instance running in a single availability zone is 90%. So, you need to find a way to replicate your data into another availability zone for business-critical workloads. Unfortunately, EBS snapshots are only of limited help: risk of inconsistent data, no RTO (Recovery Time Objective) guarantees, etc.</li></ul><p>Those limitations are good reasons to try to store your data elsewhere, for example, with one of the three storage services presented next.</p><h2 id="EFS-Elastic-File-System"><a href="#EFS-Elastic-File-System" class="headerlink" title="EFS (Elastic File System)"></a>EFS (Elastic File System)</h2><p>EFS is based on a protocol that is older than 35 years: NFS (Network File System). NFS allows multiple machines to access the same file system via the network. However, EFS is by no means obsolete but offers a network file system read for the cloud:</p><ul><li>The storage capacity grows on demand; you only pay for what you use.</li><li>Adjusting the maximum I&#x2F;O throughput to your needs is possible as well: provisioned capacity or burstable performance depending on storage size.</li><li>Data is replicated among multiple availability zones by default.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/efs@730w.webp 730w, /images/2020/07/efs@730w2x.webp 1460w, /images/2020/07/efs@610w.webp 610w, /images/2020/07/efs@610w2x.webp 1220w, /images/2020/07/efs@450w.webp 450w, /images/2020/07/efs@450w2x.webp 900w, /images/2020/07/efs@330w.webp 330w, /images/2020/07/efs@330w2x.webp 660w, /images/2020/07/efs@545w.webp 545w, /images/2020/07/efs@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/efs@730w.png 730w, /images/2020/07/efs@730w2x.png 1460w, /images/2020/07/efs@610w.png 610w, /images/2020/07/efs@610w2x.png 1220w, /images/2020/07/efs@450w.png 450w, /images/2020/07/efs@450w2x.png 900w, /images/2020/07/efs@330w.png 330w, /images/2020/07/efs@330w2x.png 660w, /images/2020/07/efs@545w.png 545w, /images/2020/07/efs@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/efs.png" alt="EFS" title="EFS"></picture></p><p>EFS is a perfect choice for workloads that you want or need to distribute among multiple machines and availability zones but require access to a shared file system. Typical examples are Content Management System (WordPress, Typo3), CI&#x2F;CD (Jenkins, GitLab), Legacy Web Applications, and many more.</p><p>Keep in mind that EFS is not designed for latency-critical workloads. For example, you should not think about using EFS for a database-like system. Another significant limitation is that EFS is only accessible from Linux machines as it requires NFSv4.</p><h2 id="FSx-File-System-for-Windows-File-Server"><a href="#FSx-File-System-for-Windows-File-Server" class="headerlink" title="FSx (File System for Windows File Server)"></a>FSx (File System for Windows File Server)</h2><p>Generally speaking, one can say that FSx is similar to EFS. However, FSx uses the SMB protocol supported by Windows, Linux, and macOS instead of NFSv4. Therefore, FSx is the perfect choice whenever you need a network file system in a non-Linux scenario.</p><p>I need to point out the differences between FSx and EFS:</p><ul><li>FSx is not making use of multiple availability zones by default. However, there is an option to operate a standby server in a 2nd availability zone. FSx promises availability of 99.9%. EFS replicates data among at least three availability zones and therefore comes with an availability objective of 99.99%.</li><li>FSx supports data deduplication, which reduces storage costs. EFS does not provide that functionality.</li></ul><p>What is right for EFS is true for FSx as well. The shared file system is not designed for latency-critical workloads.</p><h2 id="S3-Simple-Storage-Service"><a href="#S3-Simple-Storage-Service" class="headerlink" title="S3 (Simple Storage Service)"></a>S3 (Simple Storage Service)</h2><p>S3 is different. A REST API accessible from the Internet – so basically from anywhere – provides access to the object store. The Simple Storage Service distributes data among multiple availability zones by default. Also, the storage capacity and read&#x2F;write throughput scales on-demand.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/s3@730w.webp 730w, /images/2020/07/s3@730w2x.webp 1460w, /images/2020/07/s3@610w.webp 610w, /images/2020/07/s3@610w2x.webp 1220w, /images/2020/07/s3@450w.webp 450w, /images/2020/07/s3@450w2x.webp 900w, /images/2020/07/s3@330w.webp 330w, /images/2020/07/s3@330w2x.webp 660w, /images/2020/07/s3@545w.webp 545w, /images/2020/07/s3@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/s3@730w.png 730w, /images/2020/07/s3@730w2x.png 1460w, /images/2020/07/s3@610w.png 610w, /images/2020/07/s3@610w2x.png 1220w, /images/2020/07/s3@450w.png 450w, /images/2020/07/s3@450w2x.png 900w, /images/2020/07/s3@330w.png 330w, /images/2020/07/s3@330w2x.png 660w, /images/2020/07/s3@545w.png 545w, /images/2020/07/s3@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/s3.png" alt="S3" title="S3"></picture></p><p>Due to its accessibility and flexibility, it is hard to find typical use cases for S3. Therefore, I’m listing some examples of what I’ve used S3 for within the last months:</p><ul><li>Backing up my MacBook Air.</li><li>Hosting a static website.</li><li>Exchanging data for analytics and machine learning between different organizations.</li><li>Storing snapshots of Elasticsearch, a search engine and database.</li><li>Storing user-generated content accessed by mobile devices.</li><li>And more!</li></ul><p>As mentioned above, S3 is providing object storage. In general, object storage fits best for workloads with write seldomly and read often file access patterns. That’s because you have to upload the whole file whenever you want to modify it. Therefore, you should not use S3 to store active log files, that a process is appending new lines to regularly, for example. However, S3 is a perfect fit to archive your log files.</p><p>To read and write data from S3, your workload needs to interact with a REST API. Nowadays, many software vendors and open-source projects come with S3 integration. But, a legacy application will probably rely on a file system and does, therefore, not qualify for storing data on S3.</p><h2 id="Comparison"><a href="#Comparison" class="headerlink" title="Comparison"></a>Comparison</h2><p>The following table compares the different storage options that you have learned about so far. Most likely, you need to find the best compromise for the specific requirements of your workload.</p><div class="table-responsive"><table class="table table-striped"><thead><tr><th></th><th>Instance Store</th><th>EBS</th><th>EFS</th><th>FSx for Windows File Server</th><th>S3</th></tr></thead><tbody><tr><td>Type</td><td><p>Local</p></td><td><p>SAN</p></td><td><p>NAS</p></td><td><p>NAS</p></td><td><p>Web Service</p></td></tr><tr><td>Interface</td><td><p>Block Storage</p></td><td><p>Block Storage</p></td><td><p>NFSv4</p></td><td><p>SMB</p></td><td><p>REST API</p></td></tr><tr><td>Accessibility</td><td><p>EC2</p></td><td><p>EC2</p></td><td><p>EC2, On-Premises</p></td><td><p>EC2, On-Premises</p></td><td><p>Anywhere</p></td></tr><tr><td>Latency</td><td><p>Very Low</p></td><td><p>Low</p></td><td><p>High</p></td><td><p>Medium</p></td><td><p>High</p></td></tr><tr><td>Throughput</td><td><p>Up to ~5,500 MB&#x2F;s</p></td><td><p>Up to 2,375 MB&#x2F;s</p></td><td><p>Default limit is 1,000 MB&#x2F;s but can be increased</p></td><td><p>Hundreds of GB&#x2F;s</p></td><td><p>Very High</p></td></tr><tr><td>Durability</td><td><p>Ephemeral</p></td><td><p>99.8%</p></td><td><p>99.999999999%</p></td><td><p>Unspecified (probably &gt;99.8%)</p></td><td><p>99.999999999%</p></td></tr><tr><td>Availability</td><td><p>Medium</p></td><td><p>99.99%</p></td><td><p>99.99%</p></td><td><p>99.9%</p></td><td><p>99.99%</p></td></tr><tr><td>Replication</td><td><p>Single AZ</p></td><td><p>Single AZ</p></td><td><p>Multi-AZ (default), Single AZ</p></td><td><p>Multi-AZ (optional)</p></td><td><p>Multi-AZ (default), Multi-Region (optional)</p></td></tr><tr><td>Backups/Snapshots</td><td><p>n&#x2F;a</p></td><td><p>EBS Snapshots</p></td><td><p>AWS Backup</p></td><td><p>Volume Shadow Copy Service (VSS)</p></td><td><p>n&#x2F;a</p></td></tr><tr><td>Storage Costs (GB/month)</td><td><p>$0.25</p></td><td><p>$0.10</p></td><td><p>$0.30</p></td><td><p>$0.23</p></td><td><p>$0.023</p></td></tr></tbody></table></div><h2 id="Decision-Tree"><a href="#Decision-Tree" class="headerlink" title="Decision Tree"></a>Decision Tree</h2><p>Are you overwhelmed by the options and differences? The following decision tree will guide you in the right direction.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/decision-tree@730w.webp 730w, /images/2020/07/decision-tree@730w2x.webp 1460w, /images/2020/07/decision-tree@610w.webp 610w, /images/2020/07/decision-tree@610w2x.webp 1220w, /images/2020/07/decision-tree@450w.webp 450w, /images/2020/07/decision-tree@450w2x.webp 900w, /images/2020/07/decision-tree@330w.webp 330w, /images/2020/07/decision-tree@330w2x.webp 660w, /images/2020/07/decision-tree@545w.webp 545w, /images/2020/07/decision-tree@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/decision-tree@730w.png 730w, /images/2020/07/decision-tree@730w2x.png 1460w, /images/2020/07/decision-tree@610w.png 610w, /images/2020/07/decision-tree@610w2x.png 1220w, /images/2020/07/decision-tree@450w.png 450w, /images/2020/07/decision-tree@450w2x.png 900w, /images/2020/07/decision-tree@330w.png 330w, /images/2020/07/decision-tree@330w2x.png 660w, /images/2020/07/decision-tree@545w.png 545w, /images/2020/07/decision-tree@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/decision-tree.png" alt="Decision Tree" title="Decision Tree"></picture></p><p>Which storage services are you currently using? I’m keen to learn about your workloads and the storage options you have been choosing. Send me a message!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Run the AWS CLI v2 inside Docker</title>
      <link>https://cloudonaut.io/run-the-aws-cli-v2-inside-docker/</link>
      <description>
        <![CDATA[<p>The last time I bought a new laptop, I decided to install it from scratch. My goal: keep the installation as clean as possible. Run anyth]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cli/">cli</category>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <guid isPermaLink="true">https://cloudonaut.io/run-the-aws-cli-v2-inside-docker/</guid>
      <pubDate>Fri, 24 Jul 2020 07:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The last time I bought a new laptop, I decided to install it from scratch. My goal: keep the installation as clean as possible. Run anything inside Docker containers. Especially the development environments. I work for many clients. I often encounter situations where I need multiple versions of the same software. Docker is of great help. But one of my favorite tools, the AWS CLI v1, was not working perfectly inside Docker. I had issues with command completion and the CodeCommit credential helper for <code>git</code>. A tweet by <a href="https://x.com/nathankpeck/status/1281623448146640900" target="_blank" rel="noopener">@nathankpeck</a> motivated me to give the new AWS CLI v2 a try. In this post, I share my learnings and a working solution to run the AWS CLI v2 inside Docker without hassle.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/version2@730w.webp 730w, /images/2020/07/version2@730w2x.webp 1460w, /images/2020/07/version2@610w.webp 610w, /images/2020/07/version2@610w2x.webp 1220w, /images/2020/07/version2@450w.webp 450w, /images/2020/07/version2@450w2x.webp 900w, /images/2020/07/version2@330w.webp 330w, /images/2020/07/version2@330w2x.webp 660w, /images/2020/07/version2@545w.webp 545w, /images/2020/07/version2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/version2@730w.jpg 730w, /images/2020/07/version2@730w2x.jpg 1460w, /images/2020/07/version2@610w.jpg 610w, /images/2020/07/version2@610w2x.jpg 1220w, /images/2020/07/version2@450w.jpg 450w, /images/2020/07/version2@450w2x.jpg 900w, /images/2020/07/version2@330w.jpg 330w, /images/2020/07/version2@330w2x.jpg 660w, /images/2020/07/version2@545w.jpg 545w, /images/2020/07/version2@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/version2.jpg" alt="Version 2" title="Version 2"></picture></p><blockquote><p>I assume that you use macOS Catalina and <code>zsh</code> (the MacOS default). You should be able to port this to Linux and Windows.</p></blockquote><p>The fastest way to start the AWS CLI v2 inside Docker is this:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker run --<span class="built_in">rm</span> -v <span class="string">&quot;<span class="variable">$HOME</span>/.aws:/root/.aws:rw&quot;</span> amazon/aws-cli ec2 describe-images</span><br></pre></td></tr></table></figure><p>The good news, your AWS CLI config (stored in <code>~/.aws/</code>) is available inside the container because of the volume mount.</p><p>The bad news:</p><ul><li>The command is pretty long. You don’t want to type more than <code>aws</code>.</li><li>Command completion does not work.</li><li>Your files are not available inside the container. Moving something from&#x2F;to S3 is not going to work.</li><li>Environment variables from your shell are not available inside the container.</li><li>If you put this command in your git config as a credential helper, it will not work.</li></ul><p>Let’s see how we can fix this.</p><h2 id="Restore-the-aws-command"><a href="#Restore-the-aws-command" class="headerlink" title="Restore the aws command"></a>Restore the aws command</h2><ol><li>Create the file <code>/usr/local/bin/aws</code> with the following content:</li></ol><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/zsh</span></span><br><span class="line">docker run \</span><br><span class="line">  --<span class="built_in">rm</span> \</span><br><span class="line">  -v <span class="string">&quot;<span class="variable">$HOME</span>/.aws:/root/.aws:rw&quot;</span> \</span><br><span class="line">  amazon/aws-cli <span class="variable">$@</span></span><br></pre></td></tr></table></figure><ol start="2"><li>Make the file executable: <code>chmod +x /usr/local/bin/aws</code></li></ol><p>Your <code>aws</code> command will work again:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws ec2 describe-images</span><br></pre></td></tr></table></figure><p>Let’s add command completion.</p><h2 id="Adding-command-completion"><a href="#Adding-command-completion" class="headerlink" title="Adding command completion"></a>Adding command completion</h2><ol><li>Create the file <code>/usr/local/bin/aws_completer</code> with the following content:</li></ol><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/zsh</span></span><br><span class="line">docker run \</span><br><span class="line">  --<span class="built_in">rm</span> \</span><br><span class="line">  -i \</span><br><span class="line">  --entrypoint /usr/local/bin/aws_completer \</span><br><span class="line">  -e COMP_LINE -e COMP_POINT \</span><br><span class="line">  amazon/aws-cli <span class="variable">$@</span></span><br></pre></td></tr></table></figure><ol start="2"><li>Make the file executable: <code>chmod +x /usr/local/bin/aws_completer</code></li><li>Add the following lines to your <code>~/.zshrc</code>:</li></ol><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">autoload</span> -Uz compinit &amp;&amp; compinit</span><br><span class="line"><span class="built_in">autoload</span> -Uz bashcompinit &amp;&amp; bashcompinit</span><br><span class="line">complete -C <span class="string">&#x27;/usr/local/bin/aws_completer&#x27;</span> aws</span><br></pre></td></tr></table></figure><p>If you type <code>aws ec&lt;TAB&gt;</code>, you will see the available commands.</p><h2 id="Making-our-local-files-available"><a href="#Making-our-local-files-available" class="headerlink" title="Making our local files available"></a>Making our local files available</h2><p>If you run a command like <code>aws s3 cp local.file s3://my-bucket/file</code> you will get an error: “The user-provided path local.file does not exist.” This might seem strange at the beginning because you can see the file on your local machine. The problem: the file is not available inside the container. Let’s modify <code>/usr/local/bin/aws</code> slightly and mount the current working directory:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/zsh</span></span><br><span class="line">docker run \</span><br><span class="line">  --<span class="built_in">rm</span> \</span><br><span class="line">  -v <span class="string">&quot;<span class="variable">$HOME</span>/.aws:/root/.aws:rw&quot;</span> \</span><br><span class="line">  -v <span class="string">&quot;<span class="subst">$(pwd)</span>:/aws:rw&quot;</span> \</span><br><span class="line">  amazon/aws-cli <span class="variable">$@</span></span><br></pre></td></tr></table></figure><p><code>/aws</code> is <code>WORKDIR</code> of the Docker container. Therefore, we mount the local files to this directory. As long as you operate with relative paths inside your current folder (or subfolders), it works. Examples:</p><ul><li>working: <code>aws s3 cp local.file s3://my-bucket/file</code></li><li>not working: <code>aws s3 cp ../local.file s3://my-bucket/file</code></li><li>not working: <code>aws s3 cp /abs/local.file s3://my-bucket/file</code></li></ul><h2 id="Injecting-environment-variables"><a href="#Injecting-environment-variables" class="headerlink" title="Injecting environment variables"></a>Injecting environment variables</h2><p>Sometimes, you want to use the environment variables from your machine inside the container. E.g., to set the default profile.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> AWS_PROFILE=YOUR_PROFILE_NAME</span><br><span class="line">aws s3 <span class="built_in">ls</span> <span class="comment"># NOT WORKING!!!</span></span><br></pre></td></tr></table></figure><p>Unfortunately, this does not work. Because the <code>AWS_PROFILE</code> environment variable is not available inside the container. Let’s modify <code>/usr/local/bin/aws</code> to fix this:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/zsh</span></span><br><span class="line">docker run \</span><br><span class="line">  --<span class="built_in">rm</span> \</span><br><span class="line">  -v <span class="string">&quot;<span class="variable">$HOME</span>/.aws:/root/.aws:rw&quot;</span> \</span><br><span class="line">  -v <span class="string">&quot;<span class="subst">$(pwd)</span>:/aws:rw&quot;</span> \</span><br><span class="line">  -e AWS_ACCESS_KEY_ID \</span><br><span class="line">  -e AWS_CA_BUNDLE \</span><br><span class="line">  -e AWS_CLI_FILE_ENCODING \</span><br><span class="line">  -e AWS_CONFIG_FILE \</span><br><span class="line">  -e AWS_DEFAULT_OUTPUT \</span><br><span class="line">  -e AWS_DEFAULT_REGION \</span><br><span class="line">  -e AWS_PAGER \</span><br><span class="line">  -e AWS_PROFILE \</span><br><span class="line">  -e AWS_ROLE_SESSION_NAME \</span><br><span class="line">  -e AWS_SECRET_ACCESS_KEY \</span><br><span class="line">  -e AWS_SESSION_TOKEN \</span><br><span class="line">  -e AWS_SHARED_CREDENTIALS_FILE \</span><br><span class="line">  -e AWS_STS_REGIONAL_ENDPOINTS \</span><br><span class="line">  amazon/aws-cli <span class="variable">$@</span></span><br></pre></td></tr></table></figure><p>I added all <a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html#envvars-list" target="_blank" rel="noopener">environment variables that control the AWS CLI v2</a>.</p><h2 id="Connecting-with-CodeCommit-via-a-git-credential-helper"><a href="#Connecting-with-CodeCommit-via-a-git-credential-helper" class="headerlink" title="Connecting with CodeCommit via a git credential helper"></a>Connecting with CodeCommit via a git credential helper</h2><p>To get access to your CodeCommit repositories, <code>git</code> needs to become aware of your AWS credentials.</p><p>In your project’s <code>.git/config</code>, add:</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[remote &quot;origin&quot;]</span></span><br><span class="line">  <span class="attr">url</span> = https://git-codecommit.us-east-<span class="number">1</span>.amazonaws.com/v1/repos/YOUR_REPO_NAME</span><br><span class="line">  <span class="attr">fetch</span> = +refs/heads/*:refs/remotes/origin/*</span><br><span class="line"><span class="section">[credential]</span></span><br><span class="line">  <span class="attr">helper</span> = </span><br><span class="line">  <span class="attr">helper</span> = !aws --profile YOUR_PROFILE_NAME codecommit credential-helper $@</span><br><span class="line">  <span class="attr">UseHttpPath</span> = <span class="literal">true</span></span><br></pre></td></tr></table></figure><blockquote><p>The empty <code>helper =</code> line is needed on Macs to avoid the system’s keychain to get active!</p></blockquote><p>One last change to <code>/usr/local/bin/aws</code> is required:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/zsh</span></span><br><span class="line">docker run \</span><br><span class="line">  --<span class="built_in">rm</span> \</span><br><span class="line">  -i \</span><br><span class="line">  -v <span class="string">&quot;<span class="variable">$HOME</span>/.aws:/root/.aws:rw&quot;</span> \</span><br><span class="line">  -v <span class="string">&quot;<span class="subst">$(pwd)</span>:/aws:rw&quot;</span> \</span><br><span class="line">  -e AWS_ACCESS_KEY_ID \</span><br><span class="line">  -e AWS_CA_BUNDLE \</span><br><span class="line">  -e AWS_CLI_FILE_ENCODING \</span><br><span class="line">  -e AWS_CONFIG_FILE \</span><br><span class="line">  -e AWS_DEFAULT_OUTPUT \</span><br><span class="line">  -e AWS_DEFAULT_REGION \</span><br><span class="line">  -e AWS_PAGER \</span><br><span class="line">  -e AWS_PROFILE \</span><br><span class="line">  -e AWS_ROLE_SESSION_NAME \</span><br><span class="line">  -e AWS_SECRET_ACCESS_KEY \</span><br><span class="line">  -e AWS_SESSION_TOKEN \</span><br><span class="line">  -e AWS_SHARED_CREDENTIALS_FILE \</span><br><span class="line">  -e AWS_STS_REGIONAL_ENDPOINTS \</span><br><span class="line">  amazon/aws-cli <span class="variable">$@</span></span><br></pre></td></tr></table></figure><p>You are ready to go! I have one more highlight for you.</p><h2 id="Exploring-the-new-features"><a href="#Exploring-the-new-features" class="headerlink" title="Exploring the new features"></a>Exploring the new features</h2><p>The main reason why I switched to the AWS CLI v2 is the <a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html" target="_blank" rel="noopener">support for AWS SSO</a>. With the following command, I have access to all of my AWS accounts!</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws --profile YOUR_PROFILE_NAME sso login</span><br><span class="line">aws --profile YOUR_PROFILE_NAME ec2 describe-instances</span><br></pre></td></tr></table></figure><p>Your <code>~/.aws/config</code> should look similar to this:</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[profile YOUR_PROFILE_NAME]</span></span><br><span class="line"><span class="attr">region</span> = eu-west-<span class="number">1</span></span><br><span class="line"><span class="attr">sso_start_url</span> = https://YOUR_NAME.awsapps.com/start</span><br><span class="line"><span class="attr">sso_region</span> = YOUR_REGION</span><br><span class="line"><span class="attr">sso_account_id</span> = YOUR_ACCOUNT_ID</span><br><span class="line"><span class="attr">sso_role_name</span> = YOUR_SSO_ROLE_NAME</span><br></pre></td></tr></table></figure><p>As you can see, there is no <code>~/.aws/credentials</code> file anymore. I don’t need to keep any AWS credentials on my machine anymore!</p><p>PS: I recommend to read through the <a href="https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration.html" target="_blank" rel="noopener">list of breaking changes from v1 to v2</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Use multiple AWS accounts, but keep it simple!</title>
      <link>https://cloudonaut.io/use-multiple-aws-accounts-but-keep-it-simple/</link>
      <description>
        <![CDATA[<p>Getting started with AWS is a challenge. Unlimited possibilities, competing solutions, and distractions. Even the first step to create an]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <category domain="https://cloudonaut.io/tag/cloudtrail/">cloudtrail</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/use-multiple-aws-accounts-but-keep-it-simple/</guid>
      <pubDate>Wed, 22 Jul 2020 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Getting started with AWS is a challenge. Unlimited possibilities, competing solutions, and distractions. Even the first step to create an AWS account requires careful planning. AWS marketers promote the <strong>AWS Landing Zone</strong> solution. Consulting partners add their offerings to the table. But wait, you could just sign up for an AWS account yourself! Let’s calm down, understand the concepts and requirements, and work out a solution. I promise you don’t need a landing zone. What you need is at least 10x less complex.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/modern-aws-baseline@730w.webp 730w, /images/2020/07/modern-aws-baseline@730w2x.webp 1460w, /images/2020/07/modern-aws-baseline@610w.webp 610w, /images/2020/07/modern-aws-baseline@610w2x.webp 1220w, /images/2020/07/modern-aws-baseline@450w.webp 450w, /images/2020/07/modern-aws-baseline@450w2x.webp 900w, /images/2020/07/modern-aws-baseline@330w.webp 330w, /images/2020/07/modern-aws-baseline@330w2x.webp 660w, /images/2020/07/modern-aws-baseline@545w.webp 545w, /images/2020/07/modern-aws-baseline@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/modern-aws-baseline@730w.jpg 730w, /images/2020/07/modern-aws-baseline@730w2x.jpg 1460w, /images/2020/07/modern-aws-baseline@610w.jpg 610w, /images/2020/07/modern-aws-baseline@610w2x.jpg 1220w, /images/2020/07/modern-aws-baseline@450w.jpg 450w, /images/2020/07/modern-aws-baseline@450w2x.jpg 900w, /images/2020/07/modern-aws-baseline@330w.jpg 330w, /images/2020/07/modern-aws-baseline@330w2x.jpg 660w, /images/2020/07/modern-aws-baseline@545w.jpg 545w, /images/2020/07/modern-aws-baseline@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/modern-aws-baseline.jpg" alt="Modern AWS Baseline" title="Modern AWS Baseline"></picture></p><p>If you get started with AWS, you create an AWS account and spin up resources like EC2 instances, RDS databases, ELB load balancers, etc. You also create IAM users and roles to grant permissions to humans and machines in a restrictive manner. A typical setup with two environments (<code>dev</code> and <code>prod</code>) might look like this:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/aws-baseline-single-account@730w.webp 730w, /images/2020/07/aws-baseline-single-account@730w2x.webp 1460w, /images/2020/07/aws-baseline-single-account@610w.webp 610w, /images/2020/07/aws-baseline-single-account@610w2x.webp 1220w, /images/2020/07/aws-baseline-single-account@450w.webp 450w, /images/2020/07/aws-baseline-single-account@450w2x.webp 900w, /images/2020/07/aws-baseline-single-account@330w.webp 330w, /images/2020/07/aws-baseline-single-account@330w2x.webp 660w, /images/2020/07/aws-baseline-single-account@545w.webp 545w, /images/2020/07/aws-baseline-single-account@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/aws-baseline-single-account@730w.png 730w, /images/2020/07/aws-baseline-single-account@730w2x.png 1460w, /images/2020/07/aws-baseline-single-account@610w.png 610w, /images/2020/07/aws-baseline-single-account@610w2x.png 1220w, /images/2020/07/aws-baseline-single-account@450w.png 450w, /images/2020/07/aws-baseline-single-account@450w2x.png 900w, /images/2020/07/aws-baseline-single-account@330w.png 330w, /images/2020/07/aws-baseline-single-account@330w2x.png 660w, /images/2020/07/aws-baseline-single-account@545w.png 545w, /images/2020/07/aws-baseline-single-account@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/aws-baseline-single-account.png" alt="Single AWS account" title="Single AWS account"></picture></p><p>The setup comes with the following challenges:</p><ul><li>You have to be careful to separate the <code>dev</code> from the <code>prod</code> environment.<ul><li>The <code>dev</code> EC2 instances must only have access to the <code>dev</code> S3 bucket (same for <code>prod</code>).</li><li>The <code>dev</code> EC2 instances must only allow traffic from the <code>dev</code> load balancer (same for <code>prod</code>).</li><li>The <code>dev</code> database must only allow traffic from the <code>dev</code> EC2 instances (same for <code>prod</code>).</li><li>The <code>dev</code> load balancer might only be accessible from your office.</li></ul></li><li>Devs have access to <code>dev</code> and <code>prod</code>.</li><li>You can not easily see how much you pay for <code>dev</code> and <code>prod</code> (unless you tag consistently).</li></ul><p>As you can see, a lot of work is needed to isolate <code>dev</code> from <code>prod</code>. You can get this isolation for free by using two AWS accounts. One account for your <code>dev</code> environment, the other account for your <code>prod</code> environment. By default, you can not access resources from another AWS account. Isolation complete! You can also track expenses for each AWS account individually out of the box.</p><h2 id="The-drawback-when-dealing-with-multiple-AWS-accounts"><a href="#The-drawback-when-dealing-with-multiple-AWS-accounts" class="headerlink" title="The drawback when dealing with multiple AWS accounts"></a>The drawback when dealing with multiple AWS accounts</h2><p>But managing multiple AWS accounts comes with new challenges. </p><ul><li>How do you manage multiple accounts?</li><li>How do you manage your users and access to those accounts?</li><li>How do you access resources from another AWS account?</li></ul><p>A convenient approach to deal with multiple AWS accounts is by using AWS Organizations in combination with AWS Single Sign-On (SSO) to manage user access, as the following figure demonstrates.</p><blockquote><p>A migration path is available: If you started with a single AWS account, you could add the account to your AWS organization later! </p></blockquote><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/aws-baseline-organization-with-sso@730w.webp 730w, /images/2020/07/aws-baseline-organization-with-sso@730w2x.webp 1460w, /images/2020/07/aws-baseline-organization-with-sso@610w.webp 610w, /images/2020/07/aws-baseline-organization-with-sso@610w2x.webp 1220w, /images/2020/07/aws-baseline-organization-with-sso@450w.webp 450w, /images/2020/07/aws-baseline-organization-with-sso@450w2x.webp 900w, /images/2020/07/aws-baseline-organization-with-sso@330w.webp 330w, /images/2020/07/aws-baseline-organization-with-sso@330w2x.webp 660w, /images/2020/07/aws-baseline-organization-with-sso@545w.webp 545w, /images/2020/07/aws-baseline-organization-with-sso@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/aws-baseline-organization-with-sso@730w.png 730w, /images/2020/07/aws-baseline-organization-with-sso@730w2x.png 1460w, /images/2020/07/aws-baseline-organization-with-sso@610w.png 610w, /images/2020/07/aws-baseline-organization-with-sso@610w2x.png 1220w, /images/2020/07/aws-baseline-organization-with-sso@450w.png 450w, /images/2020/07/aws-baseline-organization-with-sso@450w2x.png 900w, /images/2020/07/aws-baseline-organization-with-sso@330w.png 330w, /images/2020/07/aws-baseline-organization-with-sso@330w2x.png 660w, /images/2020/07/aws-baseline-organization-with-sso@545w.png 545w, /images/2020/07/aws-baseline-organization-with-sso@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/aws-baseline-organization-with-sso.png" alt="AWS Organization with two AWS accounts and SSO" title="AWS Organization with two AWS accounts and SSO"></picture></p><p>AWS SSO provides a convenient way for your users (Alice, Bob, Clair) to access AWS accounts and for your admins (Clair) to define who has access to which AWS account. You can create the users either in AWS SSO directly, or connect with an Identity Provider (IdP).</p><p>The following screenshot shows my AWS account selection screen provided by AWS SSO. With a single click, you get access to the AWS Management Console. On top of that, AWS SSO works with the AWS CLI v2 as well!</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/aws-baseline-sso@730w.webp 730w, /images/2020/07/aws-baseline-sso@730w2x.webp 1460w, /images/2020/07/aws-baseline-sso@610w.webp 610w, /images/2020/07/aws-baseline-sso@610w2x.webp 1220w, /images/2020/07/aws-baseline-sso@450w.webp 450w, /images/2020/07/aws-baseline-sso@450w2x.webp 900w, /images/2020/07/aws-baseline-sso@330w.webp 330w, /images/2020/07/aws-baseline-sso@330w2x.webp 660w, /images/2020/07/aws-baseline-sso@545w.webp 545w, /images/2020/07/aws-baseline-sso@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/aws-baseline-sso@730w.png 730w, /images/2020/07/aws-baseline-sso@730w2x.png 1460w, /images/2020/07/aws-baseline-sso@610w.png 610w, /images/2020/07/aws-baseline-sso@610w2x.png 1220w, /images/2020/07/aws-baseline-sso@450w.png 450w, /images/2020/07/aws-baseline-sso@450w2x.png 900w, /images/2020/07/aws-baseline-sso@330w.png 330w, /images/2020/07/aws-baseline-sso@330w2x.png 660w, /images/2020/07/aws-baseline-sso@545w.png 545w, /images/2020/07/aws-baseline-sso@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/aws-baseline-sso.png" alt="AWS SSO account selection screen" title="AWS SSO account selection screen"></picture></p><p>Every time you need to access resources owned by another AWS account, you should question your architecture. Should those pieces be separated? If you have good reasons, you can either use resource policies (e.g., S3 bucket policy, SQS queue policy, …) to grant access, or you create an IAM role in the AWS account that owns the resource with a trust policy to the other AWS account. With a single <a href="https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html" target="_blank" rel="noopener">sts:AssumeRole</a> call, you have access to the resource.</p><h2 id="Education"><a href="#Education" class="headerlink" title="Education"></a>Education</h2><p>If you reached this step, you likely have a few workloads running on AWS. Sooner or later, things get messy: IAM roles provide excessive permissions, EC2 instances miss tags, and your AWS bill is out of control. Now, it’s time to take a breath. Allow your team to master AWS. Review all your IAM policies, security groups, encryption settings, and backup strategy. Question your architecture and improve. A decent understanding of the technology will enable you to make educated guesses when you continue your AWS journey. You will not regret this investment. As Andy Jassy (CEO AWS) says: “There is no compression algorithm for experience.”</p><h2 id="Improving-your-AWS-Organization-setup"><a href="#Improving-your-AWS-Organization-setup" class="headerlink" title="Improving your AWS Organization setup"></a>Improving your AWS Organization setup</h2><p>It’s time to add some governance. But wait a minute, before you research tools and solutions, step back and focus on the goals first. What do you really need to control?</p><p>Goals and controls that I recommend:</p><ul><li>✅ Isolate applications from each other (network layer and AWS API layer)</li><li>✅ Grant users (e.g., devs, admins) access to specific applications</li><li>✅ Allocate costs to applications</li><li>Well defined communication paths between applications (network layer and AWS API layer)</li><li>Record all AWS API calls (audit trail)</li><li>Optional: Allow only certain AWS regions</li></ul><blockquote><p>I recommend limiting the governance policies to a minimum. Too many rules can create frustration. I also recommend <a href="/advanved-aws-networking-pitfalls-that-you-should-avoid/">avoiding a complicated private network</a> if possible.</p></blockquote><p>So how can we implement the missing guardrails?</p><h3 id="Securely-connect-applications"><a href="#Securely-connect-applications" class="headerlink" title="Securely connect applications"></a>Securely connect applications</h3><p>If applications talk to each other, you want to whitelist each communication path. The following figure shows a system landscape where multiple applications talk to each other using the AWS platform’s capabilities.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/aws-baseline-service-to-service@730w.webp 730w, /images/2020/07/aws-baseline-service-to-service@730w2x.webp 1460w, /images/2020/07/aws-baseline-service-to-service@610w.webp 610w, /images/2020/07/aws-baseline-service-to-service@610w2x.webp 1220w, /images/2020/07/aws-baseline-service-to-service@450w.webp 450w, /images/2020/07/aws-baseline-service-to-service@450w2x.webp 900w, /images/2020/07/aws-baseline-service-to-service@330w.webp 330w, /images/2020/07/aws-baseline-service-to-service@330w2x.webp 660w, /images/2020/07/aws-baseline-service-to-service@545w.webp 545w, /images/2020/07/aws-baseline-service-to-service@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/aws-baseline-service-to-service@730w.png 730w, /images/2020/07/aws-baseline-service-to-service@730w2x.png 1460w, /images/2020/07/aws-baseline-service-to-service@610w.png 610w, /images/2020/07/aws-baseline-service-to-service@610w2x.png 1220w, /images/2020/07/aws-baseline-service-to-service@450w.png 450w, /images/2020/07/aws-baseline-service-to-service@450w2x.png 900w, /images/2020/07/aws-baseline-service-to-service@330w.png 330w, /images/2020/07/aws-baseline-service-to-service@330w2x.png 660w, /images/2020/07/aws-baseline-service-to-service@545w.png 545w, /images/2020/07/aws-baseline-service-to-service@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/aws-baseline-service-to-service.png" alt="Securely connect applications" title="Securely connect applications"></picture></p><p>A couple of techniques are available to help you with that:</p><ul><li><a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-resource-policies.html" target="_blank" rel="noopener">Amazon API Gateway REST API</a> with resource policies.</li><li><a href="https://docs.aws.amazon.com/vpc/latest/userguide/endpoint-service.html" target="_blank" rel="noopener">AWS PrivateLink</a> and VPC Interface Endpoints.</li><li><a href="https://docs.aws.amazon.com/de_de/vpc/latest/peering/what-is-vpc-peering.html" target="_blank" rel="noopener">VPC Peering</a> and Security Groups.</li><li><a href="https://aws.amazon.com/sqs/" target="_blank" rel="noopener">Amazon SQS</a> and IAM roles for asynchronous communication.</li></ul><h3 id="Record-all-API-calls"><a href="#Record-all-API-calls" class="headerlink" title="Record all API calls"></a>Record all API calls</h3><p>Enable <a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/creating-trail-organization.html" target="_blank" rel="noopener">CloudTrail for your entire organization</a> to have an audit trail of activities inside your AWS accounts. I encourage you to enable the organization-wide CloudTrail in your organization master account. This also prevents anyone (except the organization master) from accidentally disabling CloudTrail. Create an additional AWS account for the <a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/create-s3-bucket-policy-for-cloudtrail.html#w25aac16c21c27c41" target="_blank" rel="noopener">S3 bucket that stores the raw data</a>. </p><blockquote><p>Unfortunately, you can not create an organization-wide CloudTrail with CloudFormation.</p></blockquote><h3 id="Allow-only-certain-AWS-regions-Restrict-IAM-with-SCPs"><a href="#Allow-only-certain-AWS-regions-Restrict-IAM-with-SCPs" class="headerlink" title="Allow only certain AWS regions: Restrict IAM with SCPs"></a>Allow only certain AWS regions: Restrict IAM with SCPs</h3><p>You can (at least in theory) enforce that only specific AWS regions are used. E.g., if your company is based in the EU, it gets complicated if you process personally identifiable information (PII) outside of the EU. Therefore, it makes sense only to allow the regions within the EU. AWS Organizations provide a useful feature called Service Control Policy (SCP). SCPs are evaluated when the AWS API is called before IAM policies are evaluated. If you deny an action using an SCP, that action is denied under any circumstances. No IAM policy can change this. AWS <a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_example-scps.html#example-scp-deny-region" target="_blank" rel="noopener">provides a sample SCP to whitelist regions</a>, but they also mention that this policy is likely not complete. I warned you! :)</p><blockquote><p>Permissions errors caused by an SCP are not distinguishable from permissions errors caused by IAM policies. If an AWS user is not aware of SCPs, this can lead to frustration. Educate your AWS users about the SCPs that are in place!</p></blockquote><p>You can also use SCPs to disable specific features (API calls) like creating an IAM user (<code>iam:CreateUser</code>).</p><h3 id="Further-governance"><a href="#Further-governance" class="headerlink" title="Further governance"></a>Further governance</h3><p>If your team agrees that additional guardrails are needed, I recommend to check if one of the following services can satisfy your needs:</p><ul><li>Use <a href="https://docs.aws.amazon.com/organizations/latest/userguide/services-that-can-integrate-cloudformation.html" target="_blank" rel="noopener">CloudFormation StackSets</a> to deploy CloudFormation stack into each AWS account that joins your organization.</li><li><a href="https://aws.amazon.com/premiumsupport/technology/trusted-advisor/" target="_blank" rel="noopener">AWS Trusted Advisor</a> provides you guidance on your AWS configuration.</li><li><a href="https://aws.amazon.com/config/" target="_blank" rel="noopener">AWS Config</a> allows you to implement specific rules to check your AWS configuration.</li><li><a href="https://aws.amazon.com/guardduty/" target="_blank" rel="noopener">Amazon GuardDuty</a> tries to understand if suspicious activity is going on in your AWS accounts.</li></ul><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Using multiple AWS accounts to isolate workload is a good practice. If you use AWS Organizations, the management overhead is minimal. AWS SSO is ready for prime time and simplifies access to your AWS accounts. AWS Organizations provide you with a bunch of additional features that you can use to roll out global controls to each AWS account. Keep in mind that you keep the global controls to a minimum to avoid frustration. If you work in a large organization (&gt;100 AWS accounts), we recommend using multiple AWS Organizations.</p><p>PS: If you ever wondered if you need an AWS Landing Zone, I hope you found a better answer by reading this post.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>ECS Deployment Options - From rolling updates to blue-green and canary</title>
      <link>https://cloudonaut.io/ecs-deployment-options/</link>
      <description>
        <![CDATA[<p>“How often do you deploy to production?” - This is an important question as the best application is useless if you can’t deploy it. And b]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <guid isPermaLink="true">https://cloudonaut.io/ecs-deployment-options/</guid>
      <pubDate>Tue, 14 Jul 2020 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>“How often do you deploy to production?” - This is an important question as the best application is useless if you can’t deploy it. And being able to deploy regularly and automated is <a href="https://itrevolution.com/book/accelerate/" target="_blank" rel="noopener">quite important</a>.<br>The Elastic Container Service (ECS) is used by many AWS customers and offers different ways to deploy your containers. In this article, I’ll explain the different deployment options of ECS, show how they work, and when to use them.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/ecs-deployment-options@730w.webp 730w, /images/2020/07/ecs-deployment-options@730w2x.webp 1460w, /images/2020/07/ecs-deployment-options@610w.webp 610w, /images/2020/07/ecs-deployment-options@610w2x.webp 1220w, /images/2020/07/ecs-deployment-options@450w.webp 450w, /images/2020/07/ecs-deployment-options@450w2x.webp 900w, /images/2020/07/ecs-deployment-options@330w.webp 330w, /images/2020/07/ecs-deployment-options@330w2x.webp 660w, /images/2020/07/ecs-deployment-options@545w.webp 545w, /images/2020/07/ecs-deployment-options@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/ecs-deployment-options@730w.jpg 730w, /images/2020/07/ecs-deployment-options@730w2x.jpg 1460w, /images/2020/07/ecs-deployment-options@610w.jpg 610w, /images/2020/07/ecs-deployment-options@610w2x.jpg 1220w, /images/2020/07/ecs-deployment-options@450w.jpg 450w, /images/2020/07/ecs-deployment-options@450w2x.jpg 900w, /images/2020/07/ecs-deployment-options@330w.jpg 330w, /images/2020/07/ecs-deployment-options@330w2x.jpg 660w, /images/2020/07/ecs-deployment-options@545w.jpg 545w, /images/2020/07/ecs-deployment-options@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/ecs-deployment-options.jpg" alt="ECS Deployment Options" title="ECS Deployment Options"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/23-ecs-deployment-options/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>The different options can be set in the <a href="https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DeploymentController.html" target="_blank" rel="noopener">DeploymentController</a> property of an ECS service. Let’s have a look:</p><h3 id="ECS"><a href="#ECS" class="headerlink" title="ECS"></a>ECS</h3><p>The first option is <em>ECS</em> itself. The ECS service scheduler is responsible for performing a rolling update of newer versions. The current version of running tasks is replaced by newer versions. The optional <a href="https://docs.aws.amazon.com/de_de/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-service.html#cfn-ecs-service-deploymentconfiguration" target="_blank" rel="noopener">DeploymentConfiguration</a> parameter defines how many tasks should run during the deployment. <em>MinimumHealthyPercent</em> indicates the percentage of tasks that must remain in RUNNING status, and <em>MaximumHealthyPercent</em> parameter represents an upper limit on the number of your service’s tasks that are allowed in the RUNNING or PENDING state during a deployment. Both values define a percentage of the current desiredCount.</p><p>The configuration is easy and straight forward but also limits the possibilities. It’s not possible to cancel or rollback a deployment. And due to the limited configuration, it does not allow us to do a blue&#x2F;green or canary deployment. </p><p>Another issue comes in combination with CloudFormation (see <a href="https://garbe.io/blog/2020/03/04/deep-dive-ecs-deployment/" target="_blank" rel="noopener">Deep dive on load balanced ECS Service deployments with CloudFormation</a> for more details):</p><ul><li>A CloudFormation stack creation will always complete successfully even with failing health checks and independent of any deploymentConfiguration</li><li>A CloudFormation stack update will fail only if minimumHealthyPercent is 100%, and the container health check is unhealthy (no other combination). And it takes CloudFormation 3 hours before it triggers the rollback.</li><li>CloudFormation rollback means that it triggers a new ECS deployment with the former taskdefinition (which could lead to some troubles as well)</li></ul><h3 id="Code-Deploy"><a href="#Code-Deploy" class="headerlink" title="Code Deploy"></a>Code Deploy</h3><p>The next option is <em>CODE_DEPLOY</em>, where the deployment of an ECS service is orchestrated by CodeDeploy. During deployment, it creates tasks of the new version in parallel to existing tasks and then shifts the traffic over. The traffic can be <a href="https://docs.aws.amazon.com/codedeploy/latest/userguide/deployment-configurations.html#deployment-configuration-ecs" target="_blank" rel="noopener">shifted</a> <em>all-at-once</em>, <em>linear</em>, or as <em>canary</em> (small percentage at the beginning and then the rest). </p><p><a href="https://docs.aws.amazon.com/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html#appspec-hooks-ecs" target="_blank" rel="noopener">Hooks</a> (basically lambda functions) that are executed before and after the traffic is shifted can ensure that the application works as expected. Failures in the hooks lead to automated rollbacks. In addition, CloudWatch alarms can be set that are watched during the deployment, and if they go on a rollback is triggered. The deployment is also “baked” for some time to allow a fast rollback in case something went wrong after the new version has been rolled out completely.</p><blockquote><p>See also <a href="https://x.com/clare_liguori" target="_blank" rel="noopener">Clare Liguori</a>‘s twitter <a href="https://x.com/clare_liguori/status/1225566939306590208" target="_blank" rel="noopener">thread</a>, where she explains it in detail. </p></blockquote><p>To set up ECS deployments with CodeDeploy a certain order must be followed:</p><ol><li>Create your infrastructure (like IAM roles, security groups, and load balancer)</li><li>Set up an ECS Task Definition and Service (and optional a Cluster)</li><li>Create a CodeDeploy Application and DeploymentGroup (referencing ECS service and cluster and two target groups)</li><li>Set up a CodePipeline which builds the image, creates a taskdefinition.json and appsepc.yaml and calls CodeDeploy</li></ol><p>Unfortunately, not everything can be configured in CloudFormation, as DeploymentGroup does not support ECS blue&#x2F;green deployments. </p><p>Another issue is that the CodeDeploy action needs not only an <em>appspec.yaml</em> but also a <em>taskdefinition.json</em> that contains our task definition (Although AWS states in their <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-service.html#cfn-ecs-service-taskdefinition" target="_blank" rel="noopener">docs</a> that TaskDefinition is optional for an ECS Service, I couldn’t deploy a service without a referenced task definition). With those dependencies, it’s not possible to create a pipeline first and deploy your application afterward. It must be the other way around. First, create your infrastructure and then your pipeline. Further updates to your task definition are deployed with CodeDeploy and not CloudFormation.</p><blockquote><p>One advantage of CDK is that it can fill the gaps of CloudFormation and abstract complicated things away. Although blue&#x2F;green or canary deployments for ECS in CodeDeploy are not part of the official CDK library, there is a community project (<a href="https://github.com/cloudcomponents/cdk-constructs/tree/master/packages/cdk-blue-green-container-deployment" target="_blank" rel="noopener">cloudcomponents&#x2F;cdk-constructs</a>) that does the heavy lifting for you.</p></blockquote><h3 id="External-Deployments"><a href="#External-Deployments" class="headerlink" title="External Deployments"></a>External Deployments</h3><p>The last option is <em>EXTERNAL</em>. External deployments allow anyone to perform deployments by using TaskSets. A TaskSet is a set of ECS tasks that are part of a deployment. They are also used internally by CodeDeploy. </p><p>There is an <a href="https://aws.amazon.com/blogs/containers/blue-green-deployments-with-the-ecs-external-deployment-controller/" target="_blank" rel="noopener">example for Jenkins</a> that performs the following steps: </p><ol><li>Create a green task set with the new task definition</li><li>Shift test traffic to the green task set</li><li>Shift an increment of prod traffic to the green task set</li><li>Shift all prod traffic to the green task set</li><li>Remove blue task set with the old task definition</li></ol><p>But also, CloudFormation <a href="https://aws.amazon.com/about-aws/whats-new/2020/05/aws-cloudformation-now-supports-blue-green-deployments-for-amazon-ecs/" target="_blank" rel="noopener">supports blue&#x2F;green deployments for Amazon ECS</a>, which is good as you can define your whole infrastructure as code. But how is it different from CodeDeploy, and how does that work? </p><p>CloudFormation also uses the external deployment option. To perform the steps mentioned above, CloudFormation introduced a new transformation (“AWS::CodeDeployBlueGreen”) and a new “hooks” section (and an “AWS::CodeDeploy::BlueGreen” hook). Some resources need to be created for blue and green in CloudFormation (e.g., load balancer target group), and for some resources, only the “blue” part needs to be created (e.g., task definition or task set). During a stack creation or update, CloudFormation does multiple transformations of the template to perform the steps above.</p><blockquote><p>I can’t link to any docs as they don’t exist yet. Just have a look at the <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/blue-green.html" target="_blank" rel="noopener">example</a>. And <a href="https://x.com/clare_liguori" target="_blank" rel="noopener">Clare Liguori</a>‘s twitter <a href="https://x.com/clare_liguori/status/1265645392038776834" target="_blank" rel="noopener">thread</a> explains it in more detail.</p></blockquote><p>But the CloudFormation solution comes with a lot of <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/blue-green.html#blue-green-considerations" target="_blank" rel="noopener">limitations</a>. Just a few:</p><ul><li>SSM parameters can’t be resolved</li><li>Importing existing resources is not supported</li><li>Doesn’t work in combination with nested stacks</li><li>No visibility or management of in-progress deployments like in CodeDeploy.</li></ul><h3 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h3><p>The one who has the choice is in agony. Every option has it’s advantages but also drawbacks. Unfortunately, there’s no simple answer to what option you should use. I hope I could shed some light on it and help you with your decision.</p><table class="table table-striped table-responsive"><thead><tr><th><strong>Criteria</strong></th><th><strong>ECS</strong></th><th><strong>CodeDeploy</strong></th><th><strong>External (CloudFormation)</strong></th></tr></thead><tbody><tr><td><em>Simple configuration</em></td><td>✅</td><td>⚠️</td><td>❌</td></tr><tr><td><em>CloudFormation support</em></td><td>✅</td><td>❌</td><td>⚠️</td></tr><tr><td><em>Blue/Green deployments</em></td><td>❌</td><td>✅</td><td>✅</td></tr><tr><td><em>Canary deployments</em></td><td>❌</td><td>✅</td><td>✅</td></tr><tr><td><em>Automatic rollback</em></td><td>❌</td><td>✅</td><td>⚠️</td></tr><tr><td><em>Transparency</em></td><td>✅</td><td>✅</td><td>❌</td></tr></tbody></table><p>Despite the variety, I don’t believe that this is what the customers want. Too many cooks spoil the broth: ECS, CodeDeploy, and CloudFormation offer you different ways to deploy an ECS service. But what I’d like to have is the simplicity of ECS rolling updates, the powerful configuration of CodeDeploy, and the infrastructure as code support of the CloudFormation solution. </p><p>PS: And a nice integration in CDK :) </p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Resilient event-driven Serverless architectures: Isolate your dependencies</title>
      <link>https://cloudonaut.io/resilient-event-driven-serverless-architectures-isolate-your-dependencies/</link>
      <description>
        <![CDATA[<p>Most systems grow over time. Dependencies are added, and availability suffers. How can we design an event-driven serverless architecture]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <guid isPermaLink="true">https://cloudonaut.io/resilient-event-driven-serverless-architectures-isolate-your-dependencies/</guid>
      <pubDate>Fri, 10 Jul 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Most systems grow over time. Dependencies are added, and availability suffers. How can we design an event-driven serverless architecture that stays resilient if we add dependencies? In this blog post, I walk you through a real-world application that isolates dependencies using Kinesis data streams.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/isolate@730w.webp 730w, /images/2020/07/isolate@730w2x.webp 1460w, /images/2020/07/isolate@610w.webp 610w, /images/2020/07/isolate@610w2x.webp 1220w, /images/2020/07/isolate@450w.webp 450w, /images/2020/07/isolate@450w2x.webp 900w, /images/2020/07/isolate@330w.webp 330w, /images/2020/07/isolate@330w2x.webp 660w, /images/2020/07/isolate@545w.webp 545w, /images/2020/07/isolate@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/isolate@730w.jpg 730w, /images/2020/07/isolate@730w2x.jpg 1460w, /images/2020/07/isolate@610w.jpg 610w, /images/2020/07/isolate@610w2x.jpg 1220w, /images/2020/07/isolate@450w.jpg 450w, /images/2020/07/isolate@450w2x.jpg 900w, /images/2020/07/isolate@330w.jpg 330w, /images/2020/07/isolate@330w2x.jpg 660w, /images/2020/07/isolate@545w.jpg 545w, /images/2020/07/isolate@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/isolate.jpg" alt="Isolate your dependencies" title="Isolate your dependencies"></picture></p><p>Early this year, Andreas and I decided to research how we can integrate Microsoft Teams with our <a href="http://marbot.io/" target="_blank" rel="noopener">ChatOps product marbot</a> to increase our customer base. At the end of April, we launched a private beta with a couple of early adopters. On June 19, we launched <a href="https://marbot.io/microsoft-teams.html" target="_blank" rel="noopener">marbot for Microsoft Teams</a>.</p><p>I shared the <a href="/lessons-learned-serverless-chatbot-architecture-for-marbot/">first version of marbot’s architecture</a> three years ago. Followed by my <a href="/evolutionary-serverless-architecture/">Evolutionary Serverless Architecture</a> talk in 2019. Today, I provide an update on the marbot architecture and show you how we added Microsoft Teams support without disrupting our Slack customers.</p><p>The following figure shows the current marbot architecture. I explain the details in the following (starting from the bottom left).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/marbot-architecture-v4@730w.webp 730w, /images/2020/07/marbot-architecture-v4@730w2x.webp 1460w, /images/2020/07/marbot-architecture-v4@610w.webp 610w, /images/2020/07/marbot-architecture-v4@610w2x.webp 1220w, /images/2020/07/marbot-architecture-v4@450w.webp 450w, /images/2020/07/marbot-architecture-v4@450w2x.webp 900w, /images/2020/07/marbot-architecture-v4@330w.webp 330w, /images/2020/07/marbot-architecture-v4@330w2x.webp 660w, /images/2020/07/marbot-architecture-v4@545w.webp 545w, /images/2020/07/marbot-architecture-v4@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/marbot-architecture-v4@730w.png 730w, /images/2020/07/marbot-architecture-v4@730w2x.png 1460w, /images/2020/07/marbot-architecture-v4@610w.png 610w, /images/2020/07/marbot-architecture-v4@610w2x.png 1220w, /images/2020/07/marbot-architecture-v4@450w.png 450w, /images/2020/07/marbot-architecture-v4@450w2x.png 900w, /images/2020/07/marbot-architecture-v4@330w.png 330w, /images/2020/07/marbot-architecture-v4@330w2x.png 660w, /images/2020/07/marbot-architecture-v4@545w.png 545w, /images/2020/07/marbot-architecture-v4@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/marbot-architecture-v4.png" alt="marbot architecture v1" title="marbot architecture v1"></picture></p><p>Most of our customers use <a href="https://marbot.io/" target="_blank" rel="noopener">marbot to connect to over 50 AWS sources to get notified if things go wrong</a>. Depending on the AWS service, an EventBridge Rule, CloudWatch Alarm, or more service-specific features are used to subscribe to the events. All events are published to an SNS topic and forwarded to marbot via HTTPS.</p><p> If marbot receives an event, we:</p><ol><li>Parse the event.</li><li>Extract important information (AWS account, region, instance id, etc.).</li><li>Classify the event (alert or notification).</li><li>Check if similar events are available to aggregate them (deduplication).</li><li>Generate links to the AWS Management Console.</li><li>Start an escalation chain and send out the first message to Slack or Microsoft Teams.</li></ol><p>We <a href="/lessons-learned-serverless-chatbot-architecture-for-marbot/">learned the hard way</a> that external dependencies (e.g., Slack API) are unreliable. Therefore, our processing happens asynchronously and in an idempotent way. This allows us to receive all events from our customers while we retry until the external dependency becomes available again to deliver each alert eventually. marbot’s architecture is designed never to lose alerts from our customers.</p><p>With the introduction of a new external dependency, we asked one question: Will this lower our availability? If our processing logic relies on Slack and Microsoft Teams, the availability of marbot will decrease. Let’s look at one example:</p><figure class="highlight gml"><table><tr><td class="code"><pre><span class="line">Availability = Kinesis Data Stream Availability <span class="variable language_">x</span> Lambda Availability <span class="variable language_">x</span> DynamoDB availability <span class="variable language_">x</span> Slack Availability <span class="variable language_">x</span> Microsoft Teams Availability</span><br></pre></td></tr></table></figure><p>We can simplify and concentrate on the non-AWS dependencies. Before:</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="attr">Availability</span> = Slack Availability</span><br><span class="line"><span class="attr">0.99</span> = <span class="number">0.99</span></span><br></pre></td></tr></table></figure><p>After adding Microsoft Teams:</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">Availability</span> = Slack Availability x Microsoft Teams Availability</span><br><span class="line"><span class="attribute">0</span>.<span class="number">98</span> = <span class="number">0</span>.<span class="number">99</span> x <span class="number">0</span>.<span class="number">99</span></span><br></pre></td></tr></table></figure><p>As you can see, the availability is reduced if we add a dependency. To avoid this problem, we need to isolate the processing of Slack and Microsoft Teams customers. If Slack is down, we don’t want to interrupt our Microsoft Teams customers and vice versa. </p><p>We already had one Kinesis stream. We decided that the existing stream is used only for Slack customers. A second Kinesis stream was added to deal with Microsoft Teams customers. The existing code was changed to route messages to the appropriate stream. With this design, our Microsoft Teams customers will not notice if Slack is down! If our new Microsoft Teams code has bugs, we will not impact our happy Slack customers.</p><p>Besides our logic that runs behind Kinesis streams, we also have logic that runs behind a DynamoDB stream. This logic is not impacting the availability of marbot. Therefore, we have not introduced an isolation layer.</p><p>We also have a bunch of Step Function state machines to implement:</p><ul><li>Escalation chains.</li><li>Onboarding functionality to <a href="/engaging-your-users-with-aws-step-functions/">welcome new users</a>.</li><li>Clean up tasks to delete data if marbot is uninstalled.</li></ul><p>The cool thing about a Step Function execution is that each execution is isolated by default. Therefore, we use some of the state machines for Slack and Microsoft Teams customers. But we also have state machines that are needed only in the context of Slack or Microsoft Teams.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>If you add additional dependencies to your architecture, you likely decrease your system’s availability. If possible, try to isolate the dependencies not to lower the availability of your system.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Messaging on AWS</title>
      <link>https://cloudonaut.io/messaging-on-aws/</link>
      <description>
        <![CDATA[<p><a href="/databases-on-aws/">Previously, I compared all database options offered by AWS for you</a>. In this post, I compare the availabl]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/sns/">sns</category>
      <category domain="https://cloudonaut.io/tag/sqs/">sqs</category>
      <category domain="https://cloudonaut.io/tag/iot/">iot</category>
      <category domain="https://cloudonaut.io/tag/kinesis/">kinesis</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <category domain="https://cloudonaut.io/tag/msk/">msk</category>
      <category domain="https://cloudonaut.io/tag/eventbridge/">eventbridge</category>
      <guid isPermaLink="true">https://cloudonaut.io/messaging-on-aws/</guid>
      <pubDate>Wed, 01 Jul 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><a href="/databases-on-aws/">Previously, I compared all database options offered by AWS for you</a>. In this post, I compare the available messaging options. The goal of messaging on AWS is to decouple the producers of messages from consumers.</p><p>The messaging pattern allows us to process the messages asynchronously. This has several advantages. You can roll out a new version of consumers of messages while the producers can continue to send new messages at full speed. You can also scale the consumers independently from the producers. You get a buffer in your system that can absorb spikes without overloading it. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/messaging@730w.webp 730w, /images/2020/07/messaging@730w2x.webp 1460w, /images/2020/07/messaging@610w.webp 610w, /images/2020/07/messaging@610w2x.webp 1220w, /images/2020/07/messaging@450w.webp 450w, /images/2020/07/messaging@450w2x.webp 900w, /images/2020/07/messaging@330w.webp 330w, /images/2020/07/messaging@330w2x.webp 660w, /images/2020/07/messaging@545w.webp 545w, /images/2020/07/messaging@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/messaging@730w.jpg 730w, /images/2020/07/messaging@730w2x.jpg 1460w, /images/2020/07/messaging@610w.jpg 610w, /images/2020/07/messaging@610w2x.jpg 1220w, /images/2020/07/messaging@450w.jpg 450w, /images/2020/07/messaging@450w2x.jpg 900w, /images/2020/07/messaging@330w.jpg 330w, /images/2020/07/messaging@330w2x.jpg 660w, /images/2020/07/messaging@545w.jpg 545w, /images/2020/07/messaging@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/messaging.jpg" alt="Messaging on AWS" title="Messaging on AWS"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/22-messaging-on-aws/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>In this blog post, I introduce all the messaging options that AWS offers. Afterward, I end with a comparison table of the options.</p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><h2 id="Amazon-SQS-Standard"><a href="#Amazon-SQS-Standard" class="headerlink" title="Amazon SQS Standard"></a>Amazon SQS Standard</h2><p><a href="https://aws.amazon.com/sqs/" target="_blank" rel="noopener">Amazon Simple Queue Service (SQS)</a> is a fully managed service. There is zero operational overhead, and you pay per message. </p><p>SQS comes in two flavors: Standard queues and FIFO queues. I’ll focus on standard queues in this section. The next section covers FIFO queues.</p><p>SQS standard queues are the most convenient option you can dream of. You can send nearly unlimited messages and read them at any rate you wish. A message is stored in the queue for up to 14 days. Once you read and delete the message from the queue, it will disappear. Usually, one message is received by one consumer only.<br>The following figure shows a typical SQS scenario. I’ve created the cloud diagram with <a href="https://cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/sqs-standard@730w.webp 730w, /images/2020/07/sqs-standard@730w2x.webp 1460w, /images/2020/07/sqs-standard@610w.webp 610w, /images/2020/07/sqs-standard@610w2x.webp 1220w, /images/2020/07/sqs-standard@450w.webp 450w, /images/2020/07/sqs-standard@450w2x.webp 900w, /images/2020/07/sqs-standard@330w.webp 330w, /images/2020/07/sqs-standard@330w2x.webp 660w, /images/2020/07/sqs-standard@545w.webp 545w, /images/2020/07/sqs-standard@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/sqs-standard@730w.png 730w, /images/2020/07/sqs-standard@730w2x.png 1460w, /images/2020/07/sqs-standard@610w.png 610w, /images/2020/07/sqs-standard@610w2x.png 1220w, /images/2020/07/sqs-standard@450w.png 450w, /images/2020/07/sqs-standard@450w2x.png 900w, /images/2020/07/sqs-standard@330w.png 330w, /images/2020/07/sqs-standard@330w2x.png 660w, /images/2020/07/sqs-standard@545w.png 545w, /images/2020/07/sqs-standard@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/sqs-standard.png" alt="SQS Standard" title="SQS Standard"></picture></p><p>Two limitations might surprise you. Firstly, SQS does not preserve the order of messages. It tries to, but there are no guarantees about message ordering. There is nothing that you can do to fix it so, you rely on order, SQS standard queues may not be for you.</p><p>Limitation number two: A message may be delivered more than once (at least once delivery). There are multiple reasons for that. A producer wants to send a message but receives an error (e.g., a network timeout). There is no way for the sender to know if the message was delivered or not. If the message is sent again to retry, the message could be created twice.</p><p>Consumers can also fail. If you read a message from SQS, the consumer has a certain amount of time to delete the message from the queue to acknowledge that it was processed successfully. If that acknowledgment comes too late, or not at all, because of a network error, the message will become available for a consumer again.</p><p>Last but not least, the SQS service itself can also be the cause of unwanted redelivery of a message. The best way to deal with the at least once semantics is an <a href="https://aws.amazon.com/premiumsupport/knowledge-center/lambda-function-idempotent/" target="_blank" rel="noopener">idempotent consumer</a>.</p><h2 id="Amazon-SQS-FIFO"><a href="#Amazon-SQS-FIFO" class="headerlink" title="Amazon SQS FIFO"></a>Amazon SQS FIFO</h2><p>As mentioned in the previous section, FIFO queues guarantee order. They also provide a way to ensure that a message is not created twice if a consumer retries the sending. The downside is: FIFO queues can only handle 300 messages per second or 3000 messages if you send messages in batches of ten.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/sqs-fifo@730w.webp 730w, /images/2020/07/sqs-fifo@730w2x.webp 1460w, /images/2020/07/sqs-fifo@610w.webp 610w, /images/2020/07/sqs-fifo@610w2x.webp 1220w, /images/2020/07/sqs-fifo@450w.webp 450w, /images/2020/07/sqs-fifo@450w2x.webp 900w, /images/2020/07/sqs-fifo@330w.webp 330w, /images/2020/07/sqs-fifo@330w2x.webp 660w, /images/2020/07/sqs-fifo@545w.webp 545w, /images/2020/07/sqs-fifo@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/sqs-fifo@730w.png 730w, /images/2020/07/sqs-fifo@730w2x.png 1460w, /images/2020/07/sqs-fifo@610w.png 610w, /images/2020/07/sqs-fifo@610w2x.png 1220w, /images/2020/07/sqs-fifo@450w.png 450w, /images/2020/07/sqs-fifo@450w2x.png 900w, /images/2020/07/sqs-fifo@330w.png 330w, /images/2020/07/sqs-fifo@330w2x.png 660w, /images/2020/07/sqs-fifo@545w.png 545w, /images/2020/07/sqs-fifo@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/sqs-fifo.png" alt="SQS FIFO" title="SQS FIFO"></picture></p><p>To consume messages in order, you have to use a single consumer. If you only need an order within a subset of messages, you can define so-called groups of messages where the order is only guaranteed within a group (e.g., the customer id could form a group).</p><blockquote><p>Pro tip: Only use FIFO queues if you can ensure that no more than 300 messages are produced per second, even during rare traffic spikes.</p></blockquote><h2 id="Amazon-SNS-Standard"><a href="#Amazon-SNS-Standard" class="headerlink" title="Amazon SNS Standard"></a>Amazon SNS Standard</h2><p><a href="https://aws.amazon.com/sns/" target="_blank" rel="noopener">Amazon SNS</a> is a fully managed, publish&#x2F;subscribe system. You send a message to a topic, and all subscribers to that topic will receive a copy of the message. SNS is similar to SQS: zero operational effort but no order guarantee and at least once delivery of messages.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/sns@730w.webp 730w, /images/2020/07/sns@730w2x.webp 1460w, /images/2020/07/sns@610w.webp 610w, /images/2020/07/sns@610w2x.webp 1220w, /images/2020/07/sns@450w.webp 450w, /images/2020/07/sns@450w2x.webp 900w, /images/2020/07/sns@330w.webp 330w, /images/2020/07/sns@330w2x.webp 660w, /images/2020/07/sns@545w.webp 545w, /images/2020/07/sns@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/sns@730w.png 730w, /images/2020/07/sns@730w2x.png 1460w, /images/2020/07/sns@610w.png 610w, /images/2020/07/sns@610w2x.png 1220w, /images/2020/07/sns@450w.png 450w, /images/2020/07/sns@450w2x.png 900w, /images/2020/07/sns@330w.png 330w, /images/2020/07/sns@330w2x.png 660w, /images/2020/07/sns@545w.png 545w, /images/2020/07/sns@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/sns.png" alt="SNS" title="SNS"></picture></p><p>SNS can be used to implement message fanout. Keep in mind that if a topic has zero subscribers, the message is sent into a black hole and disappears.</p><p>SNS uses soft limits to throttle message producers. The <a href="https://docs.aws.amazon.com/general/latest/gr/sns.html#limits_sns_api_throttles_soft" target="_blank" rel="noopener">default limits depend on the region</a>. For example, in eu-west-1, the default limit is 9000 msg&#x2F;sec. The hard limit is not disclosed.</p><h2 id="Amazon-SNS-FIFO"><a href="#Amazon-SNS-FIFO" class="headerlink" title="Amazon SNS FIFO"></a>Amazon SNS FIFO</h2><p>An SNS topic with guarantee order. SNS FIFO also provides a way to ensure that a message is not created twice if a producer retries in case of failures. Therefore, exactly once delivery is possible.</p><p>The downsides are:</p><ul><li>FIFO topics can only handle 300 messages per second or 3000 messages if you send messages in batches of ten but no more than 10 MB per second.</li><li>The only possible subscriber type for a FIFO topic is an SQS FIFO queue.</li></ul><h2 id="Amazon-EventBridge"><a href="#Amazon-EventBridge" class="headerlink" title="Amazon EventBridge"></a>Amazon EventBridge</h2><p><a href="https://aws.amazon.com/eventbridge/" target="_blank" rel="noopener">Amazon EventBridge</a> (formerly CloudWatch Events) is a fully managed, publish&#x2F;subscribe system. The publisher sends an event to an event bus. If you want to subscribe to events, you create a rule in an event bus. If the published event matches with a rule, the event is routed to up to five targets. More than 15 target types are supported (including SQS, SNS, Lambda). EventBridge guarantees are similar to SNS and SQS: zero operational effort but no order guarantee and at least once delivery of messages.</p><p>EventBridge can be used to implement message fanout. Keep in mind that if an event does not match with a rule, it disappears unnoticed. You can optionally archive all events delivered to an event bus. Archived events can be replayed at any time. </p><p>EventBridge uses soft limits to throttle message producers. The <a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/cloudwatch-limits-eventbridge.html#putevents-limits" target="_blank" rel="noopener">default limits depend on the region</a>. For example, in eu-west-1, the default limit is 10,000 msg&#x2F;sec. The hard limit is not disclosed.</p><h2 id="Amazon-Kinesis-Data-Streams"><a href="#Amazon-Kinesis-Data-Streams" class="headerlink" title="Amazon Kinesis Data Streams"></a>Amazon Kinesis Data Streams</h2><p><a href="https://aws.amazon.com/kinesis/" target="_blank" rel="noopener">Amazon Kinesis</a> provides capabilities related to real-time data. I’ll focus primarily on data streams in this section. </p><p>If you send a message to a stream, it is appended to the end of the stream (similar to a queue). The difference comes when you read the messages. A message does not disappear from the stream when you read it. Instead, the consumer reads through a stream and keeps track of its position in the stream. You can, at any time, start reading from the beginning of the stream. The only limitation: Kinesis drops the data when it gets older than 365 days. Kinesis guarantees to keep messages in order (FIFO). However, message ordering is only consistent within a shard. A shard is capable of handling up to 1 MB&#x2F;s or 1000 messages&#x2F;sec. You have to add&#x2F;remove shards as needed and you pay for shards per hour.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/kinesis-data-stream@730w.webp 730w, /images/2020/07/kinesis-data-stream@730w2x.webp 1460w, /images/2020/07/kinesis-data-stream@610w.webp 610w, /images/2020/07/kinesis-data-stream@610w2x.webp 1220w, /images/2020/07/kinesis-data-stream@450w.webp 450w, /images/2020/07/kinesis-data-stream@450w2x.webp 900w, /images/2020/07/kinesis-data-stream@330w.webp 330w, /images/2020/07/kinesis-data-stream@330w2x.webp 660w, /images/2020/07/kinesis-data-stream@545w.webp 545w, /images/2020/07/kinesis-data-stream@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/kinesis-data-stream@730w.png 730w, /images/2020/07/kinesis-data-stream@730w2x.png 1460w, /images/2020/07/kinesis-data-stream@610w.png 610w, /images/2020/07/kinesis-data-stream@610w2x.png 1220w, /images/2020/07/kinesis-data-stream@450w.png 450w, /images/2020/07/kinesis-data-stream@450w2x.png 900w, /images/2020/07/kinesis-data-stream@330w.png 330w, /images/2020/07/kinesis-data-stream@330w2x.png 660w, /images/2020/07/kinesis-data-stream@545w.png 545w, /images/2020/07/kinesis-data-stream@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/kinesis-data-stream.png" alt="Kinesis Data Stream" title="Kinesis Data Stream"></picture></p><p>There is no way for a producer to avoid the resend problem mentioned in the SQS standard queue section. If a producer has to retry sending a message, it could end up twice in the stream. Keep in mind that the consumer has to keep track of the position in the stream while reading, which can also lead to reading a message twice. I use Kinesis data streams in scenarios where SQS standard queues are not an option because I have to rely on some order within a subset of my data (e.g., customer id mapped to shards).</p><h2 id="Amazon-MSK"><a href="#Amazon-MSK" class="headerlink" title="Amazon MSK"></a>Amazon MSK</h2><p><a href="https://aws.amazon.com/msk/" target="_blank" rel="noopener">Amazon Managed Streaming for Apache Kafka (MSK)</a> offers Apache Kafka as a Service. You get a managed cluster and can start working with Kafka without the operation complexity. Kafka works in a similar way than Kinesis data streams.</p><p>The main benefits:</p><ul><li>Kafka is open source, and you can use it outside of AWS.</li><li>Kafka topics can store your data forever if you wish.</li><li>Kafka can scale horizontally by adding brokers. Topics are divided into partitions, and you will find order only within a partition (the same as with Kinesis shards).</li></ul><h2 id="Amazon-MQ"><a href="#Amazon-MQ" class="headerlink" title="Amazon MQ"></a>Amazon MQ</h2><p><a href="https://aws.amazon.com/amazon-mq/" target="_blank" rel="noopener">Amazon MQ</a> offers Apache ActiveMQ as a Service. MQ is somehow similar to how RDS deploys databases. Two instances are running in two availability zones. One of them is active and used by producers and consumers while all data is replicated to a standby broker. In the case where the active broker fails, the producers and consumers will reconnect to the standby broker.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/07/mq@730w.webp 730w, /images/2020/07/mq@730w2x.webp 1460w, /images/2020/07/mq@610w.webp 610w, /images/2020/07/mq@610w2x.webp 1220w, /images/2020/07/mq@450w.webp 450w, /images/2020/07/mq@450w2x.webp 900w, /images/2020/07/mq@330w.webp 330w, /images/2020/07/mq@330w2x.webp 660w, /images/2020/07/mq@545w.webp 545w, /images/2020/07/mq@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/07/mq@730w.png 730w, /images/2020/07/mq@730w2x.png 1460w, /images/2020/07/mq@610w.png 610w, /images/2020/07/mq@610w2x.png 1220w, /images/2020/07/mq@450w.png 450w, /images/2020/07/mq@450w2x.png 900w, /images/2020/07/mq@330w.png 330w, /images/2020/07/mq@330w2x.png 660w, /images/2020/07/mq@545w.png 545w, /images/2020/07/mq@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/07/mq.png" alt="MQ" title="MQ"></picture></p><p>The problem with this architecture is obvious: The throughput of the system is limited to what a single broker (and storage) can provide. In this case, the storage layer is backed by <a href="https://aws.amazon.com/efs/" target="_blank" rel="noopener">Amazon EFS</a>, which limits the throughput to 80 messages per second. You can choose other storage layers, but you will risk losing messages. Luckily, you can operate ActiveMQ in a network of brokers mode to increase the throughput. The most significant benefit is that ActiveMQ supports a wide range of protocols such as JMS, AMQP, MQTT, STOMP, OpenWire.</p><h2 id="AWS-IoT-Core"><a href="#AWS-IoT-Core" class="headerlink" title="AWS IoT Core"></a>AWS IoT Core</h2><p><a href="https://aws.amazon.com/iot-core/" target="_blank" rel="noopener">AWS IoT Core</a> is mostly used for IoT workloads, where a scalable MQTT broker is the foundation of the architecture. The order of messages is not guaranteed. MQTT QoS levels at most once and at least once are supported. The nice benefit of IoT Core is that you can not only access an MQTT broker, you can also define rules inside the system to react upon messages. E.g., you can trigger a Lambda function if a message is published on a topic.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>There are many options with a broad range of capabilities. I compiled the following table to help you make your selection.</p><div class="table-responsive"><table class="table table-striped table-sm"><thead><tr><th></th><th>Amazon SQS Standard</th><th>Amazon SQS FIFO</th><th>Amazon SNS Standard</th><th>Amazon SNS FIFO</th><th>Amazon EventBridge (formerly CloudWatch Events)</th><th>Amazon Kinesis Data Streams</th><th>Amazon MSK</th><th>Amazon MQ</th><th>AWS IoT Core</th></tr></thead><tbody><tr><td>Scaling</td><td><p>nearly unlimited</p></td><td><p>3000 msg&#x2F;sec (batch write)</p></td><td><p>not disclosed (default soft limit depends on region; e.g., 9000 msg&#x2F;sec in eu-west-1)</p></td><td><p>3000 msg&#x2F;sec (batch write) or 10 MB per second</p></td><td><p>not disclosed (default soft limit depends on region; e.g., 10,000 msg&#x2F;sec in eu-west-1)</p></td><td><p>1 MB or 1000 msg&#x2F;sec per shard; up to 500 shards; you need to manually add&#x2F;remove shards</p></td><td><p>30 brokers per cluster; you need add&#x2F;remove brokers and reassign partitions manually</p></td><td><p>80 msg&#x2F;sec; can be increased with a <a href="https://docs.aws.amazon.com/amazon-mq/latest/developer-guide/network-of-brokers.html" target="_blank" rel="noopener">network of brokers</a></p></td><td><p>not disclosed</p></td></tr><tr><td>Max. message size</td><td><p>256 KB</p></td><td><p>256 KB</p></td><td><p>256 KB</p></td><td><p>256 KB</p></td><td><p>256 KB</p></td><td><p>1 MB</p></td><td><p>configurable (default 1 MB)</p></td><td><p> limited by disk space</p></td><td><p>128 KB</p></td></tr><tr><td>Persistence</td><td><p>up to 14 days</p></td><td><p>up to 14 days</p></td><td><p>no</p></td><td><p>no</p></td><td><p>archiving is possible</p></td><td><p>up to 365 days</p></td><td><p>forever (up to 16384 GiB per broker)</p></td><td><p>forever (up to 200 GB)</p></td><td><p>up to 1 hour</p></td></tr><tr><td>Replication</td><td><p>Multi-AZ</p></td><td><p>Multi-AZ</p></td><td><p>Multi-AZ</p></td><td><p>Multi-AZ</p></td><td><p>Multi-AZ</p></td><td><p>Multi-AZ</p></td><td><p>Multi-AZ (optional)</p></td><td><p>Multi-AZ (optional)</p></td><td><p>Multi-AZ</p></td></tr><tr><td>Order guarantee</td><td><p>no</p></td><td><p>yes</p></td><td><p>no</p></td><td><p>yes</p></td><td><p>no</p></td><td><p>within a shard</p></td><td><p>within a partition</p></td><td><p>yes</p></td><td><p>no</p></td></tr><tr><td>Delivery guarantee</td><td><p>at least once</p></td><td><p><a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html#FIFO-queues-exactly-once-processing" target="_blank" rel="noopener">exactly-once possible</a></p></td><td><p>at least once</p></td><td><p><a href="https://docs.aws.amazon.com/sns/latest/dg/fifo-message-dedup.html" target="_blank" rel="noopener">exactly-once possible</a></p></td><td><p>at least once</p></td><td><p>at least once</p></td><td><p><a href="https://kafka.apache.org/documentation/#semantics" target="_blank" rel="noopener">up to the consumer</a></p></td><td><p>exactly once; supports distributed (XA) transactions</p></td><td><p>at least once &#x2F; at most once</p></td></tr><tr><td>Pricing</td><td><p>per message</p></td><td><p>per message</p></td><td><p>per message</p></td><td><p>per message</p></td><td><p>per message</p></td><td><p>per shard hour</p></td><td><p>per broker hour + provisioned storage</p></td><td><p>per broker hour + used storage</p></td><td><p>per message + connection duration</p></td></tr><tr><td>Protocols</td><td><p>AWS Rest API</p></td><td><p>AWS Rest API</p></td><td><p>AWS Rest API</p></td><td><p>AWS Rest API</p></td><td><p>AWS Rest API</p></td><td><p>AWS Rest API</p></td><td><p><a href="https://kafka.apache.org/protocol" target="_blank" rel="noopener">Kafka protocol</a></p></td><td><p>JMS, AMQP, MQTT, STOMP, OpenWire</p></td><td><p>MQTT, AWS Rest API</p></td></tr><tr><td>AWS Integrations</td><td><p>Lambda</p></td><td><p>Lambda</p></td><td><p>Lambda, SQS, webhook</p></td><td><p>SQS FIFO</p></td><td><p>Lambda, SQS, SNS, and <a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-targets.html" target="_blank" rel="noopener">many more</a></p></td><td><p>Lambda</p></td><td><p>Lambda</p></td><td><p>Lambda</p></td><td><p>Lambda, SQS, SNS, and <a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-rules.html" target="_blank" rel="noopener">many more</a></p></td></tr><tr><td>License</td><td><p>AWS only</p></td><td><p>AWS only</p></td><td><p>AWS only</p></td><td><p>AWS only</p></td><td><p>AWS only</p></td><td><p>AWS only</p></td><td><p>open source (<a href="https://kafka.apache.org/" target="_blank" rel="noopener">Apache Kafka</a>)</p></td><td><p>open source (<a href="https://activemq.apache.org/" target="_blank" rel="noopener">Apache ActiveMQ</a>)</p></td><td><p>AWS only</p></td></tr><tr><td>Encryption at rest</td><td><p>yes</p></td><td><p>yes</p></td><td><p>yes</p></td><td><p>yes</p></td><td><p>yes</p></td><td><p>yes</p></td><td><p>yes</p></td><td><p>yes</p></td><td><p>no</p></td></tr><tr><td>Encryption in transit</td><td><p>yes</p></td><td><p>yes</p></td><td><p>yes</p></td><td><p>yes</p></td><td><p>yes</p></td><td><p>yes</p></td><td><p>yes</p></td><td><p>yes</p></td><td><p>yes</p></td></tr></tbody></table></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Containers vs. Serverless: Thoughts About Your Cloud Strategy</title>
      <link>https://cloudonaut.io/containers-vs-serverless-cloud-strategy/</link>
      <description>
        <![CDATA[<p>There are many ways to build on AWS. When shaping the strategy for your organization, the following two options should be on your shortli]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <category domain="https://cloudonaut.io/tag/transformation/">transformation</category>
      <guid isPermaLink="true">https://cloudonaut.io/containers-vs-serverless-cloud-strategy/</guid>
      <pubDate>Wed, 24 Jun 2020 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>There are many ways to build on AWS. When shaping the strategy for your organization, the following two options should be on your shortlist.</p><ul><li>Containers: Package your source code into containers and leverage AWS’s fully-managed services, providing the compute and storage capacity you need.</li><li>Serverless: Divide the business logic into small functions and deploy them to highly distributed systems providing compute and storage capacity.</li></ul><p>But which option fits better? In the following, I provide my criteria to decide on the C-level level.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/06/containers-vs-serverless@730w.webp 730w, /images/2020/06/containers-vs-serverless@730w2x.webp 1460w, /images/2020/06/containers-vs-serverless@610w.webp 610w, /images/2020/06/containers-vs-serverless@610w2x.webp 1220w, /images/2020/06/containers-vs-serverless@450w.webp 450w, /images/2020/06/containers-vs-serverless@450w2x.webp 900w, /images/2020/06/containers-vs-serverless@330w.webp 330w, /images/2020/06/containers-vs-serverless@330w2x.webp 660w, /images/2020/06/containers-vs-serverless@545w.webp 545w, /images/2020/06/containers-vs-serverless@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/06/containers-vs-serverless@730w.jpg 730w, /images/2020/06/containers-vs-serverless@730w2x.jpg 1460w, /images/2020/06/containers-vs-serverless@610w.jpg 610w, /images/2020/06/containers-vs-serverless@610w2x.jpg 1220w, /images/2020/06/containers-vs-serverless@450w.jpg 450w, /images/2020/06/containers-vs-serverless@450w2x.jpg 900w, /images/2020/06/containers-vs-serverless@330w.jpg 330w, /images/2020/06/containers-vs-serverless@330w2x.jpg 660w, /images/2020/06/containers-vs-serverless@545w.jpg 545w, /images/2020/06/containers-vs-serverless@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/06/containers-vs-serverless.jpg" alt="CContainers vs. Serverless: Thoughts About Your Cloud Strategy" title="CContainers vs. Serverless: Thoughts About Your Cloud Strategy"></picture></p><p>The modern way to run containers on AWS makes use of the following services:</p><ul><li>Load Balancer (Application Load Balancer) distributes incoming requests among containers.</li><li>Container Cluster (ECS and Fargate) executes containers.</li><li>Relational Database, based on Aurora, a MySQL&#x2F;PostgreSQL-compatible database optimized for the cloud.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/06/containers@730w.webp 730w, /images/2020/06/containers@730w2x.webp 1460w, /images/2020/06/containers@610w.webp 610w, /images/2020/06/containers@610w2x.webp 1220w, /images/2020/06/containers@450w.webp 450w, /images/2020/06/containers@450w2x.webp 900w, /images/2020/06/containers@330w.webp 330w, /images/2020/06/containers@330w2x.webp 660w, /images/2020/06/containers@545w.webp 545w, /images/2020/06/containers@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/06/containers@730w.png 730w, /images/2020/06/containers@730w2x.png 1460w, /images/2020/06/containers@610w.png 610w, /images/2020/06/containers@610w2x.png 1220w, /images/2020/06/containers@450w.png 450w, /images/2020/06/containers@450w2x.png 900w, /images/2020/06/containers@330w.png 330w, /images/2020/06/containers@330w2x.png 660w, /images/2020/06/containers@545w.png 545w, /images/2020/06/containers@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/06/containers.png" alt="Containers: Load Balancer, Containers, Relational Database" title="Containers: Load Balancer, Containers, Relational Database"></picture></p><p>A typical Serverless architecture consists of the following building blocks:</p><ul><li>API Gateway providing a REST interface and forwards the incoming request to Lambda.</li><li>Lambda, a compute runtime for small functions.</li><li>DynamoDB, a document database (NoSQL).</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/06/serverless@730w.webp 730w, /images/2020/06/serverless@730w2x.webp 1460w, /images/2020/06/serverless@610w.webp 610w, /images/2020/06/serverless@610w2x.webp 1220w, /images/2020/06/serverless@450w.webp 450w, /images/2020/06/serverless@450w2x.webp 900w, /images/2020/06/serverless@330w.webp 330w, /images/2020/06/serverless@330w2x.webp 660w, /images/2020/06/serverless@545w.webp 545w, /images/2020/06/serverless@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/06/serverless@730w.png 730w, /images/2020/06/serverless@730w2x.png 1460w, /images/2020/06/serverless@610w.png 610w, /images/2020/06/serverless@610w2x.png 1220w, /images/2020/06/serverless@450w.png 450w, /images/2020/06/serverless@450w2x.png 900w, /images/2020/06/serverless@330w.png 330w, /images/2020/06/serverless@330w2x.png 660w, /images/2020/06/serverless@545w.png 545w, /images/2020/06/serverless@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/06/serverless.png" alt="Serverless: API Gateway, Lambda, NoSQL" title="Serverless: API Gateway, Lambda, NoSQL"></picture></p><h2 id="Transformative"><a href="#Transformative" class="headerlink" title="Transformative"></a>Transformative</h2><p>How to transform an organization from developing traditional software for on-premises to building cloud-native solutions?</p><p><strong>The evolution:</strong> level up by using containers instead of virtual machines and a cloud-native relational database instead of an out-of-date database system. Modernize your three-tier architecture by making use of the <em>ALB + Fargate + RDS Aurora</em> stack. The programming paradigms, frameworks, and procedures hardly change. That’s a good option if the majority of developers are very effective with their toolbox and have little desire for change. Still, you benefit from managed services that do a good bit of work for you.</p><p><strong>The revolution:</strong> turn everything upside down by replacing long-running processes with an event-driven approach, and a SQL database with a NoSQL database. Moving to a cutting-edge technology stack based on <em>API Gateway + Lambda + DynamoDB</em> is a statement. A perfect match to motivate existing developers who love to learn about new technologies. It helps to attract new talent with a cloud mindset as well. Such a change can shake up an organization in both a positive and negative sense.</p><p>The investment for starting the revolution and leading it to success will be much higher than the more straightforward way of evolution.</p><h2 id="Cloud-Native"><a href="#Cloud-Native" class="headerlink" title="Cloud-Native"></a>Cloud-Native</h2><p>AWS offers a wide variety of services. Some services map on-premises technologies 1:1 in the cloud. For example, the Relational Database Service (RDS) offers fully-managed Oracle, Microsoft SQL Server, PostgreSQL, and MySQL databases in the cloud. The underlying technology for a container-based architecture, as described above, is more or less the same as what you are using on-premises.</p><p>Other services add a layer of abstraction on top of highly distributed systems. For example, DynamoDB spreads the database between tens or hundreds of machines scaling storage and throughput without the need for provisioning capacity or monitoring resources. A Serverless architecture is based on services with a high level of abstraction and highly distributed systems underneath.</p><p>Doing so comes with the following advantages:</p><ul><li>Horizontal scaling allows adapting capacity from zero to (almost) infinity.</li><li>Fault tolerance with the ability to recover from outages automatically without a downtime.</li><li>Fine granular pricing models that do not charge for unused capacity.</li><li>No expenses for operation and maintenance.</li></ul><p>But, abstractions and highly distributed systems are complicated. Even when AWS abstracts away a lot of that complexity, your developers have to deal with new challenges like eventual consistency. Also, things like monitoring and debugging are challenging between highly distributed systems.</p><h2 id="Rapid"><a href="#Rapid" class="headerlink" title="Rapid"></a>Rapid</h2><p>When building a Serverless architecture on AWS, you are putting together building blocks offered by AWS. Think of it as a toolbox that allows you to solve everyday challenges. Your developers contribute the business logic as well as glue code to wire the building blocks.</p><p>This approach removes the friction of choosing a programming model, application framework, or open-source module. Therefore, you will hardly find a faster way to implement and deploy a new application.</p><p>However, developing and deploying an application with containers can be rapid and straightforward as well. Most important, avoid all the distractions from Kubernetes and OpenShift.</p><h2 id="Flexible"><a href="#Flexible" class="headerlink" title="Flexible"></a>Flexible</h2><p>But you can’t have everything. The Serverless approach makes many assumptions and comes with some severe limitations. For example, Lambda cannot execute a process that runs longer than 15 minutes. However, in some cases, it is tough to break a problem into such small pieces that it can be processed in 15 minutes. Or, there is almost no alternative to building a single page application (SPA), when going Serverless. Server-side rendering is out of the picture.</p><p>An architecture based on containers is much more flexible. Your developers choose the programming models, application frameworks, or open-source modules of their choice. Only a few limitations apply.</p><h2 id="Cost-Effective"><a href="#Cost-Effective" class="headerlink" title="Cost-Effective"></a>Cost-Effective</h2><p>From a cost perspective, the most crucial difference between Containers and Serverless is the billing model:</p><ul><li>A Container architecture is billed by the resources you provision (CPU, memory, and storage capacity). A minimum of resources is required 24&#x2F;7 to be able to answer incoming requests.</li><li>All components of a Serverless architecture are billed per request. The system can scale down to zero.</li></ul><p>A rule of thumb:</p><ul><li>A Serverless architecture is cost-effective for systems that are used by less than 1,000 users, especially if there are times during the week when the system is not used at all.</li><li>A Container architecture comes with baseline costs. You are paying for idle resources, especially for systems with low to medium load. However, a Container architecture can be more cost-effective in high throughput scenarios with steady workloads.</li></ul><p>Probably, infrastructure costs are not your biggest problem. Consider the costs for maintenance and operation as well as know-how development.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>I’d like to summarize this discussion with the following radar chart. Serverless is an excellent choice for building modern applications. But Containers bring a lot to the table as well. The flexibility of a Container architecture should not be underestimated, especially when it comes to solving complex problems.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/06/radar-containers-serverless@730w.webp 730w, /images/2020/06/radar-containers-serverless@730w2x.webp 1460w, /images/2020/06/radar-containers-serverless@610w.webp 610w, /images/2020/06/radar-containers-serverless@610w2x.webp 1220w, /images/2020/06/radar-containers-serverless@450w.webp 450w, /images/2020/06/radar-containers-serverless@450w2x.webp 900w, /images/2020/06/radar-containers-serverless@330w.webp 330w, /images/2020/06/radar-containers-serverless@330w2x.webp 660w, /images/2020/06/radar-containers-serverless@545w.webp 545w, /images/2020/06/radar-containers-serverless@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/06/radar-containers-serverless@730w.png 730w, /images/2020/06/radar-containers-serverless@730w2x.png 1460w, /images/2020/06/radar-containers-serverless@610w.png 610w, /images/2020/06/radar-containers-serverless@610w2x.png 1220w, /images/2020/06/radar-containers-serverless@450w.png 450w, /images/2020/06/radar-containers-serverless@450w2x.png 900w, /images/2020/06/radar-containers-serverless@330w.png 330w, /images/2020/06/radar-containers-serverless@330w2x.png 660w, /images/2020/06/radar-containers-serverless@545w.png 545w, /images/2020/06/radar-containers-serverless@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/06/radar-containers-serverless.png" alt="Comparing Containers with Serverless" title="Comparing Containers with Serverless"></picture></p><p>One more thing, you might not have to decide between Containers and Serverless. Why not adding both options to your cloud strategy? Let developers choose the one that fits best for a specific scenario.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Review: API Gateway HTTP APIs - Cheaper and Faster REST APIs?</title>
      <link>https://cloudonaut.io/review-api-gateway-http-apis/</link>
      <description>
        <![CDATA[<p>An API gateway acts as an API front-end that receives API requests from clients and forwards them to back-end services. Typically, an API]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/apigateway/">apigateway</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/review-api-gateway-http-apis/</guid>
      <pubDate>Fri, 19 Jun 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>An API gateway acts as an API front-end that receives API requests from clients and forwards them to back-end services. Typically, an API gateway offers the following features:</p><ul><li>Throttling</li><li>Billing</li><li>Authentication and authorization</li><li>Request validation</li><li>Request and response transformation</li></ul><p>AWS offers different types of API gateways as a managed service. This review takes a closer look at the new service API Gateway <strong>HTTP APIs</strong>  announced in December 2019 and generally since available in March 2020. The cloud provider promises that HTTP APIs are faster and cheaper than it’s predecessor. We will look at hard technical facts instead of flowery marketing promises.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/06/magnifier@730w.webp 730w, /images/2020/06/magnifier@730w2x.webp 1460w, /images/2020/06/magnifier@610w.webp 610w, /images/2020/06/magnifier@610w2x.webp 1220w, /images/2020/06/magnifier@450w.webp 450w, /images/2020/06/magnifier@450w2x.webp 900w, /images/2020/06/magnifier@330w.webp 330w, /images/2020/06/magnifier@330w2x.webp 660w, /images/2020/06/magnifier@545w.webp 545w, /images/2020/06/magnifier@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/06/magnifier@730w.jpg 730w, /images/2020/06/magnifier@730w2x.jpg 1460w, /images/2020/06/magnifier@610w.jpg 610w, /images/2020/06/magnifier@610w2x.jpg 1220w, /images/2020/06/magnifier@450w.jpg 450w, /images/2020/06/magnifier@450w2x.jpg 900w, /images/2020/06/magnifier@330w.jpg 330w, /images/2020/06/magnifier@330w2x.jpg 660w, /images/2020/06/magnifier@545w.jpg 545w, /images/2020/06/magnifier@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/06/magnifier.jpg" alt="Review: API Gateway HTTP APIs" title="Review: API Gateway HTTP APIs"></picture></p><p>A summary of Amazon’s API gateways to avoid confusion:</p><ul><li>API Gateway <strong>REST APIs</strong> is the full-feature flagship service to build REST APIs announced in 2015.</li><li>API Gateway <strong>HTTP APIs</strong> is the fast and straightforward alternative to build REST APIs announced in 2019.</li><li>API Gateway <strong>WebSocket APIs</strong> was announced in 2018 and allows you to build a real-time API using WebSockets.</li><li>Application Load Balancer (<strong>ALB</strong>) is a layer-7 load balancer with similarities with an API gateway.</li></ul><p>This review focuses on <strong>HTTP APIs</strong>.</p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/21-review-api-gateway-http-apis/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="How-It-Works"><a href="#How-It-Works" class="headerlink" title="How It Works"></a>How It Works</h2><p>An <strong>HTTP API</strong> allows you to specify a REST API. For example, by specifying your REST API in the OpenAPI 3.0 specification. After defining resources and methods, you are ready to deploy your HTTP API, which will result in a publicly available HTTPS endpoint.</p><p>An <strong>HTTP API</strong> forwards incoming requests to the following back-end endpoints:</p><ul><li>AWS Lambda function</li><li>Any publicly availably HTTP endpoint</li><li>Internal ALB or Cloud Map.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/06/http_api@730w.webp 730w, /images/2020/06/http_api@730w2x.webp 1460w, /images/2020/06/http_api@610w.webp 610w, /images/2020/06/http_api@610w2x.webp 1220w, /images/2020/06/http_api@450w.webp 450w, /images/2020/06/http_api@450w2x.webp 900w, /images/2020/06/http_api@330w.webp 330w, /images/2020/06/http_api@330w2x.webp 660w, /images/2020/06/http_api@545w.webp 545w, /images/2020/06/http_api@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/06/http_api@730w.png 730w, /images/2020/06/http_api@730w2x.png 1460w, /images/2020/06/http_api@610w.png 610w, /images/2020/06/http_api@610w2x.png 1220w, /images/2020/06/http_api@450w.png 450w, /images/2020/06/http_api@450w2x.png 900w, /images/2020/06/http_api@330w.png 330w, /images/2020/06/http_api@330w2x.png 660w, /images/2020/06/http_api@545w.png 545w, /images/2020/06/http_api@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/06/http_api.png" alt="HTTP API" title="HTTP API"></picture></p><p>On top of that, HTTP APIs come with the following features:</p><ul><li>Authentication based on JWT tokens (OpenID Connect or OAuth 2.0)</li><li>Cross-origin resource sharing (CORS)</li><li>Stages to deploy different versions of a REST API in parallel (e.g., a test environment)</li><li>Custom domain names</li></ul><p>Next, we look at whether AWS can deliver on the promise of lower cost and lower latency than a REST API.</p><h2 id="Pricing"><a href="#Pricing" class="headerlink" title="Pricing"></a>Pricing</h2><p>In general, it is correct that HTTP APIs are cheaper than REST APIs.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">HTTP API</th><th style="text-align:right">REST API</th><th style="text-align:right">Savings</th></tr></thead><tbody><tr><td>Requests (1 Million )</td><td style="text-align:right">$1.00</td><td style="text-align:right">$3.50</td><td style="text-align:right">70%</td></tr><tr><td>Outgoin Data Transfer (per GB)</td><td style="text-align:right">$0.09</td><td style="text-align:right">$0.09</td><td style="text-align:right">0%</td></tr></tbody></table><p>A cost reduction by 70% is an essential argument for HTTP APIs.</p><p>But there’s a catch to it: HTTP APIs meters a request in 512 KB increments. REST APIs don’t. Therefore, when you have to transfer more than 1536 KB per request and response, an HTTP API will be more expensive than a REST API.</p><h2 id="Latency"><a href="#Latency" class="headerlink" title="Latency"></a>Latency</h2><p>To verify that HTTP APIs do reduce latency compared to REST APIs, I set up the following test infrastructure. An HTTP API with a Lambda integration, a REST API with a Lambda integration, a Lambda function returning a static response, an EC2 instance <code>m5.large</code> running <a href="https://k6.io/" target="_blank" rel="noopener">k6</a> to run a simple load test.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">HTTP API</th><th style="text-align:right">REST API</th><th style="text-align:right">Savings</th></tr></thead><tbody><tr><td>Average (ms)</td><td style="text-align:right">14.13</td><td style="text-align:right">16.83</td><td style="text-align:right">16%</td></tr><tr><td>90th Percentile (ms)</td><td style="text-align:right">15.97</td><td style="text-align:right">18.58</td><td style="text-align:right">14%</td></tr><tr><td>95th Percentile (ms)</td><td style="text-align:right">18.63</td><td style="text-align:right">23.05</td><td style="text-align:right">19%</td></tr></tbody></table><p>Reducing latency by 2.5 ms is excellent. However, it will most likely not reduce the overall latency by 16%, as in my example. Because most likely, your Serverless application will spend the most time waiting for a database or calculating results. So I don’t think that latency is the deciding argument for HTTP APIs.</p><p>REST APIs come with a built-in cache. Caching for REST APIs is <a href="https://aws.amazon.com/api-gateway/pricing/" target="_blank" rel="noopener">charged by provisioned memory size and hour</a> with prices starting from $14 for 0.5 GB up to $2,736 for 237.0 GB per month. HTTP APIs do not provide a built-in caching layer. However, you could use CloudFront to cache requests. CloudFront does not come with a base fee, instead you <a href="https://aws.amazon.com/cloudfront/pricing/" target="_blank" rel="noopener">pay per request and outgoing data transfer</a>. Of course, you could use CloudFront in front of your REST API with disabled caching as well.</p><h2 id="Developer-Experience"><a href="#Developer-Experience" class="headerlink" title="Developer Experience"></a>Developer Experience</h2><p>Configuring a REST API is tricky with HTTP APIs AWS focused on simplifying the developer experience.</p><p>The good news, it is quite simple to create an HTTP API that forwards all requests to a single Lambda function, or any other supported endpoint. The feature is called <em>Quick Create</em>. The following code snippet shows how to create an HTTP API forwarding all requests to a Lambda function with CloudFormation. The same functionality is available with Terraform as well.</p><figure class="highlight elixir"><table><tr><td class="code"><pre><span class="line"><span class="symbol">Resources:</span></span><br><span class="line">  <span class="symbol">HttpApi:</span></span><br><span class="line">    <span class="symbol">Type:</span> <span class="title class_">AWS</span>::<span class="title class_">ApiGatewayV2</span>::<span class="title class_">Api</span></span><br><span class="line">    <span class="symbol">Properties:</span></span><br><span class="line">      <span class="symbol">Name:</span> <span class="string">&#x27;cloudonaut API&#x27;</span></span><br><span class="line">      <span class="symbol">Description:</span> <span class="string">&#x27;Lambda Proxy&#x27;</span></span><br><span class="line">      <span class="symbol">ProtocolType:</span> <span class="title class_">HTTP</span></span><br><span class="line">      <span class="symbol">Target:</span> <span class="symbol">arn:</span><span class="symbol">aws:</span><span class="symbol">lambda:</span>us-west<span class="number">-1</span><span class="symbol">:</span><span class="number">111111111111</span><span class="symbol">:function</span><span class="symbol">:cloudonaut-api</span></span><br></pre></td></tr></table></figure><p>Besides that, importing and exporting OpenAPI 3.0 is possible as well. AWS defined some <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions.html" target="_blank" rel="noopener">extensions</a> to the OpenAPI specification <code>x-amazon-*</code>. Those extensions are available for HTTP APIs as well for REST APIs.</p><p>The following snippet shows an OpenAPI specification for an API forwarding requests to a Lambda function.</p><figure class="highlight nix"><table><tr><td class="code"><pre><span class="line"><span class="params">openapi:</span> <span class="string">&quot;3.0.1&quot;</span></span><br><span class="line"><span class="params">info:</span></span><br><span class="line">  <span class="params">title:</span> <span class="string">&quot;cloudonaut&quot;</span></span><br><span class="line"><span class="params">paths:</span></span><br><span class="line">  <span class="operator">/</span><span class="params">account:</span></span><br><span class="line">    <span class="params">get:</span></span><br><span class="line">      <span class="params">responses:</span></span><br><span class="line">        <span class="params">default:</span></span><br><span class="line">          <span class="params">description:</span> <span class="string">&quot;Default response for GET /account&quot;</span></span><br><span class="line">      <span class="params">x-amazon-apigateway-integration:</span></span><br><span class="line">        <span class="params">payloadFormatVersion:</span> <span class="string">&quot;2.0&quot;</span></span><br><span class="line">        <span class="params">type:</span> <span class="string">&quot;aws_proxy&quot;</span></span><br><span class="line">        <span class="params">httpMethod:</span> <span class="string">&quot;POST&quot;</span></span><br><span class="line">        <span class="params">uri:</span> <span class="string">&quot;arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-1:111111111111:function:cloudonaut-api/invocations&quot;</span></span><br><span class="line">        <span class="params">connectionType:</span> <span class="string">&quot;INTERNET&quot;</span></span><br></pre></td></tr></table></figure><p>I’m not a big fan of mixing infrastructure code into the API specification. It comes with all kinds of compatibility issues. I prefer the way AWS AppSync separates the API specification (GraphQL) and the AWS resource configuration.</p><p>Overall, the experience for developers building simple APIs improved with HTTP APIs.</p><h2 id="Missing-Features"><a href="#Missing-Features" class="headerlink" title="Missing Features"></a>Missing Features</h2><p>AWS itself does not offer any API without rate limiting. Except the ones where we have to pay per request. It is, therefore, somewhat surprising that HTTP APIs do not provide user&#x2F;account-based throttling. Not being able to throttle requests from your customers will sooner or later result in an outage due to a bottleneck in your infrastructure or an enormous AWS bill. There is always that customer that sends you an extreme amount of requests, whether knowingly or by accident. In my opinion, an API without the ability to throttle neither per user&#x2F;account nor per client is not production-ready.</p><p>Another option, to throttle or deny requests would be to use AWS WAF (Web Application Firewall). However, HTTP APIs do not support AWS WAF yet.</p><p>Getting insights into the health and performance of your API is crucial. Besides analyzing logs, it is beneficial to use a monitoring tool for distributed applications. AWS X-Ray is an option here. X-Ray does integrate nicely with REST APIs but not with HTTP APIs, unfortunately.</p><h2 id="HTTP-API-vs-API-Gateway-vs-ALB"><a href="#HTTP-API-vs-API-Gateway-vs-ALB" class="headerlink" title="HTTP API vs. API Gateway vs. ALB"></a>HTTP API vs. API Gateway vs. ALB</h2><p>AWS announced HTTP APIs in December 2019 and promised to close the feature gap between HTTP APIs and REST APIs in the future. However, I cannot say that there has been much progress in the last six months.</p><p>The following table compares the available features between HTTP APIs (API Gateway), REST APIs (API Gateway), and ALB (Application Load Balancer).</p><table class="table table-striped table-responsive"><thead><tr><th>Criteria</th><th>HTTP APIs</th><th>REST APIs</th><th>ALB</th></tr></thead><tbody><tr><td>Optimized for Global Traffic</td><td>❌</td><td>✅</td><td>❌</td></tr><tr><td>Public Endpoint</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>Private Endpoint</td><td>❌</td><td>✅</td><td>✅</td></tr><tr><td>Lambda Integration</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>Public HTTP Integration</td><td>✅</td><td>✅</td><td>❌</td></tr><tr><td>Private HTTP Integration</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>AWS Service Integration</td><td>❌</td><td>✅</td><td>❌</td></tr><tr><td>OpenID Connect Authentication</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>OAuth 2.0 Authentication</td><td>✅</td><td>❌</td><td>❌</td></tr><tr><td>SAML Authentication</td><td>❌</td><td>✅</td><td>✅</td></tr><tr><td>Social Authentication (Google, ...)</td><td>❌</td><td>✅</td><td>✅</td></tr><tr><td>Custom Authentication</td><td>❌</td><td>✅</td><td>❌</td></tr><tr><td>User/Account-level Throttling</td><td>❌</td><td>✅</td><td>❌</td></tr><tr><td>WAF</td><td>❌</td><td>✅</td><td>✅</td></tr><tr><td>Request Validation</td><td>❌</td><td>✅</td><td>❌</td></tr></tbody></table><p>In summary, it is fair to say that REST APIs offer the broadest set of features and integrations.</p><h2 id="Limitations"><a href="#Limitations" class="headerlink" title="Limitations"></a>Limitations</h2><p>One of the most important pages within the documentation of an AWS is <em>Quotas</em>. That’s where AWS lists the soft limits (might be increased by AWS depending on your scenario) and hard limits (cannot be increased).</p><p>Some critical hard limits for HTTP APIs are:</p><ul><li>Integration timeout: 30 seconds</li><li>Payload size: 10 MB</li><li>Request line and header values: 10240 bytes</li></ul><p>The same limitations apply to REST APIs as well.</p><p>Check out <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html" target="_blank" rel="noopener">Amazon API Gateway quotas and important notes</a> to learn more.</p><h2 id="Service-Maturity-Table"><a href="#Service-Maturity-Table" class="headerlink" title="Service Maturity Table"></a>Service Maturity Table</h2><p>The following table summarizes the maturity of the service:</p><table class="table table-striped table-responsive"><thead><tr><th>Criteria</th><th>Support</th><th style="text-align:right">Score</th></tr></thead><tbody><tr><td>Feature Completeness</td><td>⚠️</td><td style="text-align:right">2</td></tr><tr><td>Documentation Detailedness</td><td>⚠️</td><td style="text-align:right">2</td></tr><tr><td>Tags (Grouping + Billing)</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>CloudFormation + Terraform support</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>IAM granularity</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>Integrated with AWS Config</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>Auditing via AWS CloudTrail</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>Available in all commercial regions</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>SLA</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td><strong>Total Maturity Score (0-10)</strong></td><td>✅</td><td style="text-align:right"><strong>7.1</strong></td></tr></tbody></table><p>Our maturity score for API Gateway HTTP APIs is 7.1 on a scale from 0 to 10.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>HTTP APIs are becoming the new standard for API Gateways on AWS. Lower costs and lower latency are essential arguments to trade in the feature-complete REST API with a shiny new HTTP API. However, you should keep in mind that HTTP APIs are missing some critical features. To me, user&#x2F;account-level throttling is the most crucial feature that is still missing and a show stopper for most scenarios.</p><p><em>Did I miss something? Do you disagree with my review? Message me!</em></p><h2 id="P-S-marbot-available-for-Microsoft-Teams"><a href="#P-S-marbot-available-for-Microsoft-Teams" class="headerlink" title="P.S. marbot available for Microsoft Teams"></a>P.S. marbot available for Microsoft Teams</h2><p>We are happy to announce, that <a href="https://marbot.io/" target="_blank" rel="noopener">marbot</a>, our chatbot monitoring your AWS infrastructure is now <a href="https://marbot.io/blog/marbot-for-microsoft-teams-beta.html" target="_blank" rel="noopener">available not only for Slack but for Microsoft Teams as well</a>. marbot sets up CloudWatch Alarms and EventBridge Rules for all parts of your AWS infrastructure. CloudFormation and Terraform are supported. Your team receives alerts as direct messages or channel messages via Slack or Microsoft Teams. The alert includes relevant details to understand and solve the problem.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/06/marbot-microsoft-teams-configure-monitoring@730w.webp 730w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@730w2x.webp 1460w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@610w.webp 610w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@610w2x.webp 1220w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@450w.webp 450w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@450w2x.webp 900w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@330w.webp 330w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@330w2x.webp 660w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@545w.webp 545w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/06/marbot-microsoft-teams-configure-monitoring@730w.png 730w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@730w2x.png 1460w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@610w.png 610w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@610w2x.png 1220w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@450w.png 450w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@450w2x.png 900w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@330w.png 330w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@330w2x.png 660w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@545w.png 545w, /images/2020/06/marbot-microsoft-teams-configure-monitoring@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/06/marbot-microsoft-teams-configure-monitoring.png" alt="marbot configures monitoring for AWS" title="marbot configures monitoring for AWS"></picture></p><p><a href="https://teams.microsoft.com/l/app/1dd5785b-d8b3-400d-8e1f-af7c939cd70f" target="_blank" rel="noopener">Give marbot for Micrsoft Teams a try!</a></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>End-user monitoring of your website with CloudWatch Synthetics</title>
      <link>https://cloudonaut.io/monitor-your-website-with-cloudwatch-synthetics/</link>
      <description>
        <![CDATA[<p>There are countless reasons why your website is not working as your users expect. From a technical point of view, you can monitor your lo]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/monitor-your-website-with-cloudwatch-synthetics/</guid>
      <pubDate>Thu, 04 Jun 2020 17:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>There are countless reasons why your website is not working as your users expect. From a technical point of view, you can monitor your load balancers, your web servers, and your database. But what if that external script that you embed is breaking your site? Expired TLS certificate? Something wrong with DNS? How can you test that your website works for real users?</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/06/end-user-monitoring@730w.webp 730w, /images/2020/06/end-user-monitoring@730w2x.webp 1460w, /images/2020/06/end-user-monitoring@610w.webp 610w, /images/2020/06/end-user-monitoring@610w2x.webp 1220w, /images/2020/06/end-user-monitoring@450w.webp 450w, /images/2020/06/end-user-monitoring@450w2x.webp 900w, /images/2020/06/end-user-monitoring@330w.webp 330w, /images/2020/06/end-user-monitoring@330w2x.webp 660w, /images/2020/06/end-user-monitoring@545w.webp 545w, /images/2020/06/end-user-monitoring@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/06/end-user-monitoring@730w.jpg 730w, /images/2020/06/end-user-monitoring@730w2x.jpg 1460w, /images/2020/06/end-user-monitoring@610w.jpg 610w, /images/2020/06/end-user-monitoring@610w2x.jpg 1220w, /images/2020/06/end-user-monitoring@450w.jpg 450w, /images/2020/06/end-user-monitoring@450w2x.jpg 900w, /images/2020/06/end-user-monitoring@330w.jpg 330w, /images/2020/06/end-user-monitoring@330w2x.jpg 660w, /images/2020/06/end-user-monitoring@545w.jpg 545w, /images/2020/06/end-user-monitoring@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/06/end-user-monitoring.jpg" alt="End-user perspective" title="End-user perspective"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/20-monitor-your-website-with-cloudwatch-synthetics/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>Browsers can be used in an automated way controlled by a script. Wait for an element to become visible. Click on a link. Enter a form field.  <a href="https://github.com/puppeteer/puppeteer" target="_blank" rel="noopener">puppeteer</a> allows you to remote control a headless Chrome browser in Node.js and is maintained by Google. We can monitor the user experience on our website if we can find a way to run a puppeteer script at regular intervals and record the results.</p><p>And that’s where <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries.html" target="_blank" rel="noopener">Amazon CloudWatch Synthetics</a> enters the stage. It allows you to create canaries to execute puppeteer scripts on a schedule. Each run creates detailed logs, screenshots, and a record of all network calls in <a href="https://en.wikipedia.org/wiki/HAR_(file_format)" target="_blank" rel="noopener">HAR</a> format uploaded to S3. All you need to do is provide a script that performs the test, or use one of the blueprints. The following Node.js script opens <code>https://marbot.io</code>, waits for an <code>&lt;h1&gt;</code> HTML element, checks the title and status code, and performs a screenshot.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> synthetics = <span class="built_in">require</span>(<span class="string">&#x27;Synthetics&#x27;</span>); <span class="comment">// CloudWatch Synthetics lib</span></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">handler</span> = <span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> page = <span class="keyword">await</span> synthetics.<span class="title function_">getPage</span>();</span><br><span class="line">  <span class="keyword">const</span> response = <span class="keyword">await</span> page.<span class="title function_">goto</span>(<span class="string">&#x27;https://marbot.io&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">waitUntil</span>: <span class="string">&#x27;domcontentloaded&#x27;</span>,</span><br><span class="line">    <span class="attr">timeout</span>: <span class="number">30000</span></span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">await</span> page.<span class="title function_">waitFor</span>(<span class="string">&#x27;h1&#x27;</span>, &#123;<span class="attr">timeout</span>: <span class="number">15000</span>&#125;); <span class="comment">// &lt;h1&gt; element expected</span></span><br><span class="line">    <span class="keyword">const</span> title = <span class="keyword">await</span> page.<span class="title function_">title</span>();</span><br><span class="line">    <span class="keyword">if</span> (!title.<span class="title function_">includes</span>(<span class="string">&#x27;marbot&#x27;</span>)) &#123; <span class="comment">// title must contain marbot</span></span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;title not as expected&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (response.<span class="title function_">status</span>() !== <span class="number">200</span>) &#123; <span class="comment">// 200 status code expected</span></span><br><span class="line">      <span class="keyword">throw</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;Failed to load page!&#x27;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    <span class="keyword">await</span> synthetics.<span class="title function_">takeScreenshot</span>(<span class="string">&#x27;loaded&#x27;</span>, <span class="string">&#x27;result&#x27;</span>); <span class="comment">// always create a screenshot</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>The rest is taken care of by CloudWatch Synthetics and is presented like this:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/06/cloudwatch-synthetics-canary-run@730w.webp 730w, /images/2020/06/cloudwatch-synthetics-canary-run@730w2x.webp 1460w, /images/2020/06/cloudwatch-synthetics-canary-run@610w.webp 610w, /images/2020/06/cloudwatch-synthetics-canary-run@610w2x.webp 1220w, /images/2020/06/cloudwatch-synthetics-canary-run@450w.webp 450w, /images/2020/06/cloudwatch-synthetics-canary-run@450w2x.webp 900w, /images/2020/06/cloudwatch-synthetics-canary-run@330w.webp 330w, /images/2020/06/cloudwatch-synthetics-canary-run@330w2x.webp 660w, /images/2020/06/cloudwatch-synthetics-canary-run@545w.webp 545w, /images/2020/06/cloudwatch-synthetics-canary-run@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/06/cloudwatch-synthetics-canary-run@730w.png 730w, /images/2020/06/cloudwatch-synthetics-canary-run@730w2x.png 1460w, /images/2020/06/cloudwatch-synthetics-canary-run@610w.png 610w, /images/2020/06/cloudwatch-synthetics-canary-run@610w2x.png 1220w, /images/2020/06/cloudwatch-synthetics-canary-run@450w.png 450w, /images/2020/06/cloudwatch-synthetics-canary-run@450w2x.png 900w, /images/2020/06/cloudwatch-synthetics-canary-run@330w.png 330w, /images/2020/06/cloudwatch-synthetics-canary-run@330w2x.png 660w, /images/2020/06/cloudwatch-synthetics-canary-run@545w.png 545w, /images/2020/06/cloudwatch-synthetics-canary-run@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/06/cloudwatch-synthetics-canary-run.png" alt="CloudWatch Synthetics canary run" title="CloudWatch Synthetics canary run"></picture></p><p>Want to get notified about failed canary runs? Create a CloudWatch Alarm that watches the metrics of the canary to alert you if things go wrong. I created a CloudFormation template to help you with the setup (don’t forget to resolve the <code>TODO</code>s in the template!).</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&#x27;CloudWatch Synthetics website monitoring&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Topic:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::SNS::Topic&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Subscription:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Endpoint:</span> <span class="string">&#x27;mail@site.com&#x27;</span></span><br><span class="line">        <span class="attr">Protocol:</span> <span class="string">email</span></span><br><span class="line">  <span class="attr">TopicPolicy:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::SNS::TopicPolicy&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Id:</span> <span class="string">Id1</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">Sid1</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">AWS:</span> <span class="string">&#x27;*&#x27;</span> <span class="comment"># Allow CloudWatch Alarms</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;sns:Publish&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Ref</span> <span class="string">Topic</span></span><br><span class="line">          <span class="attr">Condition:</span></span><br><span class="line">            <span class="attr">StringEquals:</span></span><br><span class="line">              <span class="attr">&#x27;AWS:SourceOwner&#x27;:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::AccountId&#x27;</span></span><br><span class="line">      <span class="attr">Topics:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Topic</span></span><br><span class="line">  <span class="attr">CanaryBucket:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::S3::Bucket&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">CanaryRole:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">Service:</span> <span class="string">&#x27;lambda.amazonaws.com&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">      <span class="attr">Policies:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">execution</span></span><br><span class="line">        <span class="attr">PolicyDocument:</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">          <span class="attr">Statement:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">            <span class="attr">Action:</span> <span class="string">&#x27;s3:ListAllMyBuckets&#x27;</span></span><br><span class="line">            <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">            <span class="attr">Action:</span> <span class="string">&#x27;s3:PutObject&#x27;</span></span><br><span class="line">            <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;CanaryBucket.Arn&#125;/*&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">            <span class="attr">Action:</span> <span class="string">&#x27;s3:GetBucketLocation&#x27;</span></span><br><span class="line">            <span class="attr">Resource:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CanaryBucket.Arn&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">            <span class="attr">Action:</span> <span class="string">&#x27;cloudwatch:PutMetricData&#x27;</span></span><br><span class="line">            <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">            <span class="attr">Condition:</span></span><br><span class="line">              <span class="attr">StringEquals:</span></span><br><span class="line">                <span class="attr">&#x27;cloudwatch:namespace&#x27;:</span> <span class="string">CloudWatchSynthetics</span></span><br><span class="line">  <span class="attr">CanaryLogGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Logs::LogGroup&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">LogGroupName:</span> <span class="type">!Sub</span> <span class="string">&#x27;/aws/lambda/cwsyn-$&#123;Canary&#125;-$&#123;Canary.Id&#125;&#x27;</span></span><br><span class="line">      <span class="attr">RetentionInDays:</span> <span class="number">14</span></span><br><span class="line">  <span class="attr">CanaryPolicy:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Policy&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogStream&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CanaryLogGroup.Arn&#x27;</span></span><br><span class="line">      <span class="attr">PolicyName:</span> <span class="string">logs</span></span><br><span class="line">      <span class="attr">Roles:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">CanaryRole</span></span><br><span class="line">  <span class="attr">Canary:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Synthetics::Canary&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">ArtifactS3Location:</span> <span class="type">!Sub</span> <span class="string">&#x27;s3://$&#123;CanaryBucket&#125;&#x27;</span></span><br><span class="line">      <span class="attr">Code:</span></span><br><span class="line">        <span class="attr">Handler:</span> <span class="string">&#x27;index.handler&#x27;</span></span><br><span class="line">        <span class="attr">Script:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          const synthetics = require(&#x27;Synthetics&#x27;);</span></span><br><span class="line"><span class="string">          const log = require(&#x27;SyntheticsLogger&#x27;);</span></span><br><span class="line"><span class="string">          exports.handler = async () =&gt; &#123;</span></span><br><span class="line"><span class="string">            const page = await synthetics.getPage();</span></span><br><span class="line"><span class="string">            const response = await page.goto(&#x27;https://site.com&#x27;, &#123;waitUntil: &#x27;domcontentloaded&#x27;, timeout: 30000&#125;); // TODO replace with your URL</span></span><br><span class="line"><span class="string">            try &#123;</span></span><br><span class="line"><span class="string">              await page.waitFor(&#x27;h1&#x27;, &#123;timeout: 15000&#125;); // TODO replace with an HTML element to look for</span></span><br><span class="line"><span class="string">              const title = await page.title();</span></span><br><span class="line"><span class="string">              if (!title.includes(&#x27;REPLACE&#x27;)) &#123; // TODO replace with your important word in the title</span></span><br><span class="line"><span class="string">                throw new Error(&#x27;title not as expected!&#x27;);</span></span><br><span class="line"><span class="string">              &#125;</span></span><br><span class="line"><span class="string">              if (response.status() !== 200) &#123;</span></span><br><span class="line"><span class="string">                throw(new Error(&#x27;Failed to load page!&#x27;));</span></span><br><span class="line"><span class="string">              &#125;</span></span><br><span class="line"><span class="string">            &#125; finally &#123;</span></span><br><span class="line"><span class="string">              await synthetics.takeScreenshot(&#x27;loaded&#x27;, &#x27;result&#x27;);</span></span><br><span class="line"><span class="string">            &#125;</span></span><br><span class="line"><span class="string">          &#125;;</span></span><br><span class="line"><span class="string"></span>      <span class="attr">ExecutionRoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CanaryRole.Arn&#x27;</span></span><br><span class="line">      <span class="attr">FailureRetentionPeriod:</span> <span class="number">30</span></span><br><span class="line">      <span class="attr">Name:</span> <span class="string">&#x27;site-monitoring&#x27;</span> <span class="comment"># TODO replace with better name</span></span><br><span class="line">      <span class="attr">RunConfig:</span></span><br><span class="line">        <span class="attr">TimeoutInSeconds:</span> <span class="number">60</span></span><br><span class="line">      <span class="attr">RuntimeVersion:</span> <span class="string">&#x27;syn-1.0&#x27;</span></span><br><span class="line">      <span class="attr">Schedule:</span></span><br><span class="line">        <span class="attr">DurationInSeconds:</span> <span class="string">&#x27;0&#x27;</span> <span class="comment"># run forever</span></span><br><span class="line">        <span class="attr">Expression:</span> <span class="string">&#x27;rate(15 minutes)&#x27;</span></span><br><span class="line">      <span class="attr">StartCanaryAfterCreation:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">SuccessRetentionPeriod:</span> <span class="number">30</span></span><br><span class="line">  <span class="attr">SuccessPercentAlarm:</span></span><br><span class="line">    <span class="attr">DependsOn:</span> <span class="string">TopicPolicy</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AlarmActions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Topic</span></span><br><span class="line">      <span class="attr">AlarmDescription:</span> <span class="string">&#x27;Canary is failing.&#x27;</span></span><br><span class="line">      <span class="attr">ComparisonOperator:</span> <span class="string">LessThanThreshold</span></span><br><span class="line">      <span class="attr">Dimensions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">CanaryName</span></span><br><span class="line">        <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">Canary</span></span><br><span class="line">      <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">MetricName:</span> <span class="string">SuccessPercent</span></span><br><span class="line">      <span class="attr">Namespace:</span> <span class="string">CloudWatchSynthetics</span></span><br><span class="line">      <span class="attr">OKActions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Topic</span></span><br><span class="line">      <span class="attr">Period:</span> <span class="number">300</span></span><br><span class="line">      <span class="attr">Statistic:</span> <span class="string">Minimum</span></span><br><span class="line">      <span class="attr">Threshold:</span> <span class="number">90</span> <span class="comment"># TODO replace or confirm threshold</span></span><br><span class="line">      <span class="attr">TreatMissingData:</span> <span class="string">notBreaching</span></span><br></pre></td></tr></table></figure><p>Don’t forget to check out the CloudWatch <a href="https://aws.amazon.com/cloudwatch/pricing/" target="_blank" rel="noopener">pricing details</a>.</p><p>I also integrated CloudWatch Synthetics into <a href="http://marbot.io/" target="_blank" rel="noopener">marbot</a>. You can set up your external website from Slack and receive alerts in Slack like this:</p><ol><li>Send a message to marbot on a channel and ask him to monitor your website.</li><li>Select your preferred way of interacting with AWS (Management Console, CLI).</li><li>Set the monitoring goal to <strong>Synthetics website</strong>.</li><li>Select your AWS region.</li><li>Follow marbot to deploy a CloudFormation stack to set up CloudWatch Synthetics, CloudWatch Alarms, and much more.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/06/cloudwatch-synthetics-setup@730w.webp 730w, /images/2020/06/cloudwatch-synthetics-setup@730w2x.webp 1460w, /images/2020/06/cloudwatch-synthetics-setup@610w.webp 610w, /images/2020/06/cloudwatch-synthetics-setup@610w2x.webp 1220w, /images/2020/06/cloudwatch-synthetics-setup@450w.webp 450w, /images/2020/06/cloudwatch-synthetics-setup@450w2x.webp 900w, /images/2020/06/cloudwatch-synthetics-setup@330w.webp 330w, /images/2020/06/cloudwatch-synthetics-setup@330w2x.webp 660w, /images/2020/06/cloudwatch-synthetics-setup@545w.webp 545w, /images/2020/06/cloudwatch-synthetics-setup@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/06/cloudwatch-synthetics-setup@730w.png 730w, /images/2020/06/cloudwatch-synthetics-setup@730w2x.png 1460w, /images/2020/06/cloudwatch-synthetics-setup@610w.png 610w, /images/2020/06/cloudwatch-synthetics-setup@610w2x.png 1220w, /images/2020/06/cloudwatch-synthetics-setup@450w.png 450w, /images/2020/06/cloudwatch-synthetics-setup@450w2x.png 900w, /images/2020/06/cloudwatch-synthetics-setup@330w.png 330w, /images/2020/06/cloudwatch-synthetics-setup@330w2x.png 660w, /images/2020/06/cloudwatch-synthetics-setup@545w.png 545w, /images/2020/06/cloudwatch-synthetics-setup@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/06/cloudwatch-synthetics-setup.png" alt="CloudWatch Synthetics setup" title="CloudWatch Synthetics setup"></picture></p><p>That’s it. Your website is now monitored from an end-user perspective. If things go wrong, you will receive a message in Slack.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/06/cloudwatch-synthetics-alarm@730w.webp 730w, /images/2020/06/cloudwatch-synthetics-alarm@730w2x.webp 1460w, /images/2020/06/cloudwatch-synthetics-alarm@610w.webp 610w, /images/2020/06/cloudwatch-synthetics-alarm@610w2x.webp 1220w, /images/2020/06/cloudwatch-synthetics-alarm@450w.webp 450w, /images/2020/06/cloudwatch-synthetics-alarm@450w2x.webp 900w, /images/2020/06/cloudwatch-synthetics-alarm@330w.webp 330w, /images/2020/06/cloudwatch-synthetics-alarm@330w2x.webp 660w, /images/2020/06/cloudwatch-synthetics-alarm@545w.webp 545w, /images/2020/06/cloudwatch-synthetics-alarm@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/06/cloudwatch-synthetics-alarm@730w.png 730w, /images/2020/06/cloudwatch-synthetics-alarm@730w2x.png 1460w, /images/2020/06/cloudwatch-synthetics-alarm@610w.png 610w, /images/2020/06/cloudwatch-synthetics-alarm@610w2x.png 1220w, /images/2020/06/cloudwatch-synthetics-alarm@450w.png 450w, /images/2020/06/cloudwatch-synthetics-alarm@450w2x.png 900w, /images/2020/06/cloudwatch-synthetics-alarm@330w.png 330w, /images/2020/06/cloudwatch-synthetics-alarm@330w2x.png 660w, /images/2020/06/cloudwatch-synthetics-alarm@545w.png 545w, /images/2020/06/cloudwatch-synthetics-alarm@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/06/cloudwatch-synthetics-alarm.png" alt="CloudWatch Synthetics Alarm in Slack" title="CloudWatch Synthetics Alarm in Slack"></picture></p><p>Are you interested in marbot? <a href="http://marbot.io/" target="_blank" rel="noopener">Configure AWS monitoring, receive alerts, solve incidents from Slack</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Terraform, can you keep a secret?</title>
      <link>https://cloudonaut.io/terraform-can-you-keep-a-secret/</link>
      <description>
        <![CDATA[<p>Did you know that Terraform state can - and most likely does - contain <a href="https://developer.hashicorp.com/terraform/language/state/]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/terraform/">terraform</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/terraform-can-you-keep-a-secret/</guid>
      <pubDate>Fri, 29 May 2020 19:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Did you know that Terraform state can - and most likely does - contain <a href="https://developer.hashicorp.com/terraform/language/state/sensitive-data" target="_blank" rel="noopener">sensitive data</a>? A few examples of sensitive information stored in the Terraform state:</p><ul><li>Initial password for an <a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance.html" target="_blank" rel="noopener">RDS instance</a>.</li><li>Unencrypted value fetched from <a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter.html" target="_blank" rel="noopener">SSM parameter</a> (<code>SecureString</code>).</li><li>Preshared keys of <a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpn_connection.html" target="_blank" rel="noopener">VPN connection</a>.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/05/keep-a-secret@730w.webp 730w, /images/2020/05/keep-a-secret@730w2x.webp 1460w, /images/2020/05/keep-a-secret@610w.webp 610w, /images/2020/05/keep-a-secret@610w2x.webp 1220w, /images/2020/05/keep-a-secret@450w.webp 450w, /images/2020/05/keep-a-secret@450w2x.webp 900w, /images/2020/05/keep-a-secret@330w.webp 330w, /images/2020/05/keep-a-secret@330w2x.webp 660w, /images/2020/05/keep-a-secret@545w.webp 545w, /images/2020/05/keep-a-secret@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/05/keep-a-secret@730w.jpg 730w, /images/2020/05/keep-a-secret@730w2x.jpg 1460w, /images/2020/05/keep-a-secret@610w.jpg 610w, /images/2020/05/keep-a-secret@610w2x.jpg 1220w, /images/2020/05/keep-a-secret@450w.jpg 450w, /images/2020/05/keep-a-secret@450w2x.jpg 900w, /images/2020/05/keep-a-secret@330w.jpg 330w, /images/2020/05/keep-a-secret@330w2x.jpg 660w, /images/2020/05/keep-a-secret@545w.jpg 545w, /images/2020/05/keep-a-secret@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/05/keep-a-secret.jpg" alt="Terraform, can you keep a secret?" title="Terraform, can you keep a secret?"></picture></p><p>When using Terraform to provision cloud infrastructure on AWS, it is common to use S3 and DynamoDB to store the Terraform state as well. When doing so, Terraform will store sensitive information on S3. By default, the confidential data is neither encrypted at rest nor protected from access from other users or roles from the same AWS account.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/05/terraform-remote-state-s3@730w.webp 730w, /images/2020/05/terraform-remote-state-s3@730w2x.webp 1460w, /images/2020/05/terraform-remote-state-s3@610w.webp 610w, /images/2020/05/terraform-remote-state-s3@610w2x.webp 1220w, /images/2020/05/terraform-remote-state-s3@450w.webp 450w, /images/2020/05/terraform-remote-state-s3@450w2x.webp 900w, /images/2020/05/terraform-remote-state-s3@330w.webp 330w, /images/2020/05/terraform-remote-state-s3@330w2x.webp 660w, /images/2020/05/terraform-remote-state-s3@545w.webp 545w, /images/2020/05/terraform-remote-state-s3@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/05/terraform-remote-state-s3@730w.png 730w, /images/2020/05/terraform-remote-state-s3@730w2x.png 1460w, /images/2020/05/terraform-remote-state-s3@610w.png 610w, /images/2020/05/terraform-remote-state-s3@610w2x.png 1220w, /images/2020/05/terraform-remote-state-s3@450w.png 450w, /images/2020/05/terraform-remote-state-s3@450w2x.png 900w, /images/2020/05/terraform-remote-state-s3@330w.png 330w, /images/2020/05/terraform-remote-state-s3@330w2x.png 660w, /images/2020/05/terraform-remote-state-s3@545w.png 545w, /images/2020/05/terraform-remote-state-s3@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/05/terraform-remote-state-s3.png" alt="Terraform remote state with S3 backend" title="Terraform remote state with S3 backend"></picture></p><blockquote><p>When using the S3 backend to manage your Terraform state, you should not forget to enable encryption-at-rest and tight access control to the S3 bucket.</p></blockquote><p>Read on to learn how to protect your sensitive information.</p><h2 id="Encryption-at-Rest"><a href="#Encryption-at-Rest" class="headerlink" title="Encryption-at-Rest"></a>Encryption-at-Rest</h2><p>Enabling <a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/bucket-encryption.html" target="_blank" rel="noopener">S3 Default Encryption</a> will automatically encrypt the Terraform state when stored on S3. It’s only server-side encryption, but still much better than storing your sensitive information unencrypted.</p><p>For full control, I recommend using a customer-managed CMK managed by the Key Management Service (KMS) when configuring the default encryption for your S3 bucket.</p><p>The following snippet shows how to enable default encryption with CloudFormation.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">StateBucket:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">AWS::S3::Bucket</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">BucketEncryption:</span></span><br><span class="line">      <span class="attr">ServerSideEncryptionConfiguration:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">ServerSideEncryptionByDefault:</span></span><br><span class="line">          <span class="attr">KMSMasterKeyID:</span> &#123;<span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentKmsKeyStack&#125;-KeyArn&#x27;</span>&#125;</span><br><span class="line">          <span class="attr">SSEAlgorithm:</span> <span class="string">&#x27;aws:kms&#x27;</span></span><br><span class="line">    <span class="attr">BucketName:</span> <span class="type">!Ref</span> <span class="string">TerrformStateIdentifier</span></span><br></pre></td></tr></table></figure><h2 id="Access-Control"><a href="#Access-Control" class="headerlink" title="Access Control"></a>Access Control</h2><p>First of all, use a separate S3 bucket to store your Terraform state. I recommend creating an S3 bucket per AWS account and region.</p><p>Next, we need to follow the least privilege principle for read and write requests to the S3 bucket. The best way to restrict access to an S3 bucket very tightly is to make use of a bucket policy.</p><p>The following bucket policy grants the IAM role <code>tfadmin</code> full access to administer the S3 bucket. The IAM user <code>tfuser</code> is only granted read and write access to the objects within the bucket. Everyone else is neither allowed to modify the bucket nor to access the data stored within the bucket.</p><blockquote><p>As the bucket policy uses <code>Deny</code> statements with an <code>NotPrincipal</code> element, it is necessary to specify the account (<code>arn:aws:iam::111111111111:root</code> in my example) as well as the assumed-role user when using IAM roles. Check out <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_notprincipal.html#specifying-notprincipal" target="_blank" rel="noopener">NotPrincipal with Deny</a> to learn more.</p></blockquote><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2008-10-17&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Statement&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Deny&quot;</span>,</span><br><span class="line">      <span class="string">&quot;NotPrincipal&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;AWS&quot;</span>: [</span><br><span class="line">          <span class="string">&quot;arn:aws:iam::111111111111:root&quot;</span>,</span><br><span class="line">          <span class="string">&quot;arn:aws:iam::111111111111:role/tfadmin&quot;</span>,</span><br><span class="line">          <span class="string">&quot;arn:aws:sts::111111111111:assumed-role/tfadmin/session&quot;</span>,</span><br><span class="line">        ]</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="string">&quot;NotAction&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;s3:ListBucket&quot;</span>,</span><br><span class="line">        <span class="string">&quot;s3:GetObject&quot;</span>,</span><br><span class="line">        <span class="string">&quot;s3:PutObject&quot;</span></span><br><span class="line">      ],</span><br><span class="line">      <span class="string">&quot;Resource&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;arn:aws:s3:::tfstate-cloudonaut-demo&quot;</span>,</span><br><span class="line">        <span class="string">&quot;arn:aws:s3:::tfstate-cloudonaut-demo/*&quot;</span></span><br><span class="line">      ]</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Deny&quot;</span>,</span><br><span class="line">      <span class="string">&quot;NotPrincipal&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;AWS&quot;</span>: [</span><br><span class="line">          <span class="string">&quot;arn:aws:iam::111111111111:root&quot;</span></span><br><span class="line">          <span class="string">&quot;arn:aws:iam::111111111111:user/tfuser&quot;</span>,</span><br><span class="line">          <span class="string">&quot;arn:aws:iam::111111111111:role/tfadmin&quot;</span>,</span><br><span class="line">          <span class="string">&quot;arn:aws:sts::111111111111:assumed-role/tfadmin/session&quot;</span>,</span><br><span class="line">        ]</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="string">&quot;Action&quot;</span>: <span class="string">&quot;s3:*&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Resource&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;arn:aws:s3:::tfstate-cloudonaut-demo&quot;</span>,</span><br><span class="line">        <span class="string">&quot;arn:aws:s3:::tfstate-cloudonaut-demo/*&quot;</span></span><br><span class="line">      ]</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Terraform will not keep your secrets! Sensitive information like database passwords, secrets stored within the Parameter Store, or shared keys for a VPN connection is at risk. Therefore, you should enable encryption-at-rest and use a bucket policy to tightly control who can access your Terraform state when using Terraform’s S3 backend.</p><p>Are you looking for a ready-to-use implementation? I’ve added a CloudFormation template to our open-source project <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">widdix&#x2F;aws-cf-templates</a> that you can use to create an S3 bucket and DynamoDB optimized for the use as Terraform state backend. Check out the <a href="https://templates.cloudonaut.io/en/stable/operations/#terraform-state" target="_blank" rel="noopener">documentation</a> to get started.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>5th Anniversary of cloudonaut</title>
      <link>https://cloudonaut.io/cloudonaut-5th-anniversary/</link>
      <description>
        <![CDATA[<p>We published the first blog post five years ago, on May 9th, 2015. This was the time when we were writing our first book Amazon Web Servi]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/cloudonaut-5th-anniversary/</guid>
      <pubDate>Mon, 11 May 2020 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>We published the first blog post five years ago, on May 9th, 2015. This was the time when we were writing our first book Amazon Web Services in Action. Since then, we have published 250 blog posts, improved our writing, and received tremendous feedback from the community. Today, our blog posts are accessed more than 100,000 times per month. That’s a reason to celebrate!</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/05/birthday@730w.webp 730w, /images/2020/05/birthday@730w2x.webp 1460w, /images/2020/05/birthday@610w.webp 610w, /images/2020/05/birthday@610w2x.webp 1220w, /images/2020/05/birthday@450w.webp 450w, /images/2020/05/birthday@450w2x.webp 900w, /images/2020/05/birthday@330w.webp 330w, /images/2020/05/birthday@330w2x.webp 660w, /images/2020/05/birthday@545w.webp 545w, /images/2020/05/birthday@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/05/birthday@730w.jpg 730w, /images/2020/05/birthday@730w2x.jpg 1460w, /images/2020/05/birthday@610w.jpg 610w, /images/2020/05/birthday@610w2x.jpg 1220w, /images/2020/05/birthday@450w.jpg 450w, /images/2020/05/birthday@450w2x.jpg 900w, /images/2020/05/birthday@330w.jpg 330w, /images/2020/05/birthday@330w2x.jpg 660w, /images/2020/05/birthday@545w.jpg 545w, /images/2020/05/birthday@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/05/birthday.jpg" alt="Happy Birthday, cloudonaut!" title="Happy Birthday, cloudonaut!"></picture></p><p>Want to take a look behind the scenes? Check out <a href="https://cloudonaut.io/how-we-run-our-blog-cloudfront-s3-hexo/">How we run our blog cloudonaut.io</a>.</p><h2 id="Greetings"><a href="#Greetings" class="headerlink" title="Greetings"></a>Greetings</h2><p>We enjoy being part of a fantastic community and are happy to share the following anniversary greeting with you.</p><p><a href="https://x.com/antonbabenko" target="_blank" rel="noopener">Anton Babenko</a>, Terraform fanatic and AWS Community Hero, starts with his greetings.</p><blockquote><p>You guys started spreading the AWS knowledge more than 5 years ago when many companies didn’t even know what does AWS cloud mean. Since that time, you have developed a large number of solutions, wrote dozens of blog posts, recorded hours of podcasts, wrote several books, and so on. Overall, I am sure you have helped thousands of other developers to get their job done and educate them in one way or another! I wish you to continue growing your customer base and I am very glad you are using Terraform now, too :)</p></blockquote><p>Next, <a href="https://x.com/PaulDuvall" target="_blank" rel="noopener">Paul Duvall</a>, Founder and CTO at Stelligent as well as AWS Community Hero, send us kind and very motivating words.</p><blockquote><p>I am a big believer in sharing knowledge to help others learn faster. I am so impressed with the very useful, practical, and consistent technical content they’ve been producing over these five years. It’s clear they share because they want to help others. The fact it’s only two people continues to amaze me. Keep up the great work, Andreas and Michael! </p></blockquote><p>We are glad to hear from <a href="https://x.com/sqlheisenberg" target="_blank" rel="noopener">Dzenan Dzevlan</a>, DevOps, and Databases Enthusiast as well as AWS Community Hero, that our content helps people getting started with AWS.</p><blockquote><p>Since the first post where they described what is possible with AWS, which I referenced multiple times to my clients and AWS newbies, cloudonaut was and still is one of my favorites AWS blogs. When Andreas and Michael started sharing their knowledge within the AWS community, there were not so many great blogs. Through the years, I found their posts, CloudFormation templates, books, and podcasts as a place where I am learning and keeping my self updated. In front of the AWS community in Bosnia and my personal name, I would like to say thank you for your dedication and time. Your contribution to the AWS community is amazing, and we are happy and honored to have experts like you sharing their knowledge selflessly within the community.<br>Happy anniversary!</p></blockquote><p>We are happy to hear that <a href="https://x.com/alexbdebrie" target="_blank" rel="noopener">Alex DeBrie</a>, author of <a href="https://www.dynamodbbook.com/" target="_blank" rel="noopener">The DynamoDB book</a> and AWS Data Hero,  stumbles upon cloudonaut when searching how to solve AWS related problems.</p><blockquote><p>One of my favorite parts of working with AWS is the developer community. I’m consistently amazed by the quality of tooling, blog posts, and talks discussing AWS services and the rich discussion on best practices and patterns. Within this developer community, the cloudonaut blog stands out as an example of excellence. If I’m searching for an AWS issue and a cloudonaut page is in the search results, I know I’m getting a detailed, high-quality post that will help me out. I have multiple cloudonaut pages bookmarked that I reference regularly when working with AWS. Congrats to Andreas and Michael for five years, and thanks for all your help!</p></blockquote><p>And last but not least, we are really happy about what <a href="https://x.com/pgarbe" target="_blank" rel="noopener">Philipp Garbe</a>, a Platform Architect and AWS Container Hero, has written for our anniversary.</p><blockquote><p>Happy anniversary to your blog, Andreas and Michael! I read your blog from the beginning and got many insights. Your critical view of the AWS services is the perfect complement to the official documents and tells me which parts are missing and where the limits are. Keep it up!</p></blockquote><h2 id="Support-us"><a href="#Support-us" class="headerlink" title="Support us"></a>Support us</h2><p>Do you like our work? Please support us!</p><ul><li>Share our blog posts via Twitter, LinkedIn, Reddit,  Hacker News, Facebook, E-Mail, …</li><li>Recommend our blog or podcast to a friend.</li><li>Subscribe to our weekly newsletter or RSS feed.</li><li>Add the cloudonaut podcast to your podcast player: <a href="https://podcasts.apple.com/podcast/id1476505149" target="_blank" rel="noopener">Apple Podcasts</a>, <a href="https://www.google.com/podcasts?feed=aHR0cHM6Ly9wb2RjYXN0LmNsb3Vkb25hdXQuaW8vZmVlZC9hYWM=" target="_blank" rel="noopener">Google Podcasts</a>, <a href="https://open.spotify.com/show/1M44gYEuSZs3YX6zDUcVZs" target="_blank" rel="noopener">Spotify</a>, <a href="https://overcast.fm/itunes1476505149" target="_blank" rel="noopener">Overcast</a>, <a href="https://castro.fm/itunes/1476505149" target="_blank" rel="noopener">Castro</a>, <a href="https://pca.st/itunes/1476505149" target="_blank" rel="noopener">Pocket Casts</a>, …</li><li>Follow us on LinkedIn (<a href="https://www.linkedin.com/in/andreaswittig/" target="_blank" rel="noopener">Andreas Wittig</a> and <a href="https://www.linkedin.com/in/michael-wittig/" target="_blank" rel="noopener">Michael Wittig</a>).</li><li>Send us your feedback, praise, and criticism.</li><li>Buy one of our books: <a href="https://bit.ly/amazon-web-services-in-action-2nd-edition" target="_blank" rel="noopener">Amazon Web Services in Action</a> or Rapid Docker on AWS.</li><li>Check out <a href="https://marbot.io/" target="_blank" rel="noopener">marbot, to monitor your AWS infrastructure from Slack</a>.</li><li><a href="https://github.com/sponsors/widdix" target="_blank" rel="noopener">Become a sponsor</a> of our Open Source work on GitHub.</li><li><a href="https://widdix.net/" target="_blank" rel="noopener">Hire us</a> for customized training, coaching, workshop, infrastructure bootstrapping, or review.</li></ul><h2 id="Reader-Survey"><a href="#Reader-Survey" class="headerlink" title="Reader Survey"></a>Reader Survey</h2><p>We’d love to learn more about you! First of all, because we are curious. On top of that, we want to make sure that the content we will produce within the next year is relevant to you.</p><p>Therefore, please <a href="https://forms.gle/SMK4sSUNkh2Jj7tf7" target="_blank" rel="noopener">answer our reader survey</a>.</p><p>As a little goody, we raffle the following books among all readers who submit the survey and leave their e-mail address until May 30th, 2020:</p><ul><li>3x Amazon Web Service in Action (Printed Book)</li><li>3x Rapid Docker on AWS (eBook)</li></ul><p><a href="https://forms.gle/SMK4sSUNkh2Jj7tf7" target="_blank" rel="noopener"><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/05/readersurvey@730w.webp 730w, /images/2020/05/readersurvey@730w2x.webp 1460w, /images/2020/05/readersurvey@610w.webp 610w, /images/2020/05/readersurvey@610w2x.webp 1220w, /images/2020/05/readersurvey@450w.webp 450w, /images/2020/05/readersurvey@450w2x.webp 900w, /images/2020/05/readersurvey@330w.webp 330w, /images/2020/05/readersurvey@330w2x.webp 660w, /images/2020/05/readersurvey@545w.webp 545w, /images/2020/05/readersurvey@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/05/readersurvey@730w.jpg 730w, /images/2020/05/readersurvey@730w2x.jpg 1460w, /images/2020/05/readersurvey@610w.jpg 610w, /images/2020/05/readersurvey@610w2x.jpg 1220w, /images/2020/05/readersurvey@450w.jpg 450w, /images/2020/05/readersurvey@450w2x.jpg 900w, /images/2020/05/readersurvey@330w.jpg 330w, /images/2020/05/readersurvey@330w2x.jpg 660w, /images/2020/05/readersurvey@545w.jpg 545w, /images/2020/05/readersurvey@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/05/readersurvey.jpg" alt="Reader Survey" title="Reader Survey"></picture></a></p><h2 id="Thank-you"><a href="#Thank-you" class="headerlink" title="Thank you!"></a>Thank you!</h2><p>Thanks for supporting our work. It is a pleasure to be part of an inspiring community of AWS users.</p><p>We will do our very best to publish a new blog post every week. As always, our goal is to provide high-quality content with takeaways that you will not find anywhere else.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Scaling Container Clusters on AWS: ECS and EKS</title>
      <link>https://cloudonaut.io/scaling-container-clusters-on-aws-ecs-eks/</link>
      <description>
        <![CDATA[<p>Containers are a powerful tool to streamline your development and deployment process. However, a container cluster - no matter if you are]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <category domain="https://cloudonaut.io/tag/eks/">eks</category>
      <guid isPermaLink="true">https://cloudonaut.io/scaling-container-clusters-on-aws-ecs-eks/</guid>
      <pubDate>Fri, 08 May 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Containers are a powerful tool to streamline your development and deployment process. However, a container cluster - no matter if you are using ECS (Elastic Container Service), EKS (Elastic Kubernetes Service), or self-managed Kubernetes - increases complexity. You are not only managing virtual machines anymore, but you are also operating containers on top of those virtual machines. Luckily, AWS offers a few approaches to minimize the effort of providing the computing capacity for your container cluster.</p><ul><li>ECS with Cluster Auto Scaling</li><li>ECS with DIY Auto Scaling based on CloudWatch Events and Metrics</li><li>ECS on Fargate</li><li>EKS with Cluster Autoscaler and Managed Node Group</li><li>EKS on Fargate</li></ul><p>I have taken a closer look at the different approaches. Read on to learn about differences and pitfalls.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/05/container-ship@730w.webp 730w, /images/2020/05/container-ship@730w2x.webp 1460w, /images/2020/05/container-ship@610w.webp 610w, /images/2020/05/container-ship@610w2x.webp 1220w, /images/2020/05/container-ship@450w.webp 450w, /images/2020/05/container-ship@450w2x.webp 900w, /images/2020/05/container-ship@330w.webp 330w, /images/2020/05/container-ship@330w2x.webp 660w, /images/2020/05/container-ship@545w.webp 545w, /images/2020/05/container-ship@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/05/container-ship@730w.jpg 730w, /images/2020/05/container-ship@730w2x.jpg 1460w, /images/2020/05/container-ship@610w.jpg 610w, /images/2020/05/container-ship@610w2x.jpg 1220w, /images/2020/05/container-ship@450w.jpg 450w, /images/2020/05/container-ship@450w2x.jpg 900w, /images/2020/05/container-ship@330w.jpg 330w, /images/2020/05/container-ship@330w2x.jpg 660w, /images/2020/05/container-ship@545w.jpg 545w, /images/2020/05/container-ship@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/05/container-ship.jpg" alt="Scaling Container Clusters" title="Scaling Container Clusters"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/19-scaling-container-clusters-on-aws-ecs-eks/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="AWS-Fargate"><a href="#AWS-Fargate" class="headerlink" title="AWS Fargate"></a>AWS Fargate</h2><p>With Fargate, AWS offers a fully-managed and scalable container infrastructure. No need to manage virtual machines anymore. ECS and EKS launch containers on machines operated by AWS. Removing virtual machines from your architecture decreases complexity significantly. Therefore, I highly recommend using Fargate whenever possible.</p><p>However, there are a few reasons when using Fargate is not an option:</p><ul><li>Fargate (EKS) is only available in 8 of 22 commercial regions.</li><li>Fargate (EKS) supports ALB as the only load balancer type.</li><li>Fargate offers a maximum of 4 vCPU and 30 GB memory per container.</li><li>Fargate does not provide specialized hardware (e.g., memory-optimized, storage-optimized, GPU, …)</li><li>Pricing options to reduce costs are limited, especially for Fargate (EKS).</li></ul><p>What to do when using AWS Fargate is not an option?</p><h2 id="ECS-with-Cluster-Auto-Scaling"><a href="#ECS-with-Cluster-Auto-Scaling" class="headerlink" title="ECS with Cluster Auto Scaling"></a>ECS with Cluster Auto Scaling</h2><p>AWS announced Cluster Auto Scaling for ECS in December 2019. A huge improvement, as there was no built-in way to scale the EC2 instance for an ECS cluster automatically before.</p><p>To get started, you need to create a capacity provider associated with the Auto Scaling Group that manages the EC2 instances forming your ECS cluster. On top of that, Cluster Auto Scaling writes a CloudWatch metric named <code>CapacityProviderReservation</code>, a target tracking scaling policy continually tries to achieve a capacity reservation of 100 by scaling out or in. </p><p>It is important to mention that the lifecycle for ECS tasks has been extended with the additional state <code>PROVISIONING</code>. If there is not enough capacity within the cluster, an ECS task will stay in state <code>PENDING</code>. Next, the <code>CapacityProviderReservation</code> will cross the target of 100. Therefore, the target tracking scaling policy will increase the desired capacity of the Auto Scaling Group. A few minutes later, one or multiple EC2 instances will register at the ECS cluster.</p><p>Scaling in works the other way round. Overcapacity in the cluster will decrease the <code>CapacityProviderReservation</code> below the target of 100. Again, the target tracking scaling policy will kick in and reduces the desired capacity of the Auto Scaling Group.</p><p>This is where it gets tricky. The Cluster Auto Scaling sets the termination protection flag for all EC2 instances running at least one task (other than daemon tasks). Therefore, the Auto Scaling Group will not terminate an EC2 instance whenever this could cause a service interruption. The way the Cluster Auto Scaling scales in comes with two major problems:</p><ol><li>Cluster Auto Scaling does not mitigate fragmentation by moving tasks (a group of containers) to another EC2 instance. Therefore, you will waste a lot of money because the cluster does not scale in as much as possible.</li><li>There is no way to roll out a new version of the ECS AMI. The termination protection breaks rolling updates of EC2 instances via CloudFormation.</li></ol><p>Check out <a href="https://aws.amazon.com/blogs/containers/deep-dive-on-amazon-ecs-cluster-auto-scaling/" target="_blank" rel="noopener">Deep Dive on Amazon ECS Cluster Auto Scaling</a> when interested in more details.</p><p>In my opinion, those are two significant problems. That is why I came up with a do-it-yourself (DIY) auto-scaling approach.</p><h2 id="ECS-with-DIY-Auto-Scaling"><a href="#ECS-with-DIY-Auto-Scaling" class="headerlink" title="ECS with DIY Auto Scaling"></a>ECS with DIY Auto Scaling</h2><p>Before Cluster Auto Scaling was a thing, Michael and I tried to come up with a solution to launch and terminate EC2 instances for an ECS cluster automatically. We came up with a solution but were never 100% happy with that. For example, because our solution was always overprovisioning a whole EC2 instance. Because I was frustrated with the limitations of ECS Cluster Auto Scaling - as described above - I revisited a do-it-yourself approach for scaling an ECS cluster automatically.</p><p>Based on some newish features, I came up with the following solution:</p><ul><li>The ECS service emits a <strong>CloudWatch event</strong> <code>SERVICE_TASK_PLACEMENT_FAILURE</code> whenever there is not enough capacity to start a new task. The solution uses the <code>SERVICE_TASK_PLACEMENT_FAILURE</code> event to scale out the Auto Scaling Group immediately.</li><li>The <strong>CloudWatch metrics</strong> <code>CPUReservation</code> and <code>MemoryReservation</code> indicate the cluster utilization and can be used to scale in the Auto Scaling Group.</li><li>The <strong>Auto Scaling Group</strong> launches and terminates EC2 instances on demand.</li><li>An <strong>Auto Scaling Lifecycle Hook</strong> makes sure an EC2 instance is not terminated before all running containers have been moved to another instance.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/05/ecs-diy-auto-scaling@730w.webp 730w, /images/2020/05/ecs-diy-auto-scaling@730w2x.webp 1460w, /images/2020/05/ecs-diy-auto-scaling@610w.webp 610w, /images/2020/05/ecs-diy-auto-scaling@610w2x.webp 1220w, /images/2020/05/ecs-diy-auto-scaling@450w.webp 450w, /images/2020/05/ecs-diy-auto-scaling@450w2x.webp 900w, /images/2020/05/ecs-diy-auto-scaling@330w.webp 330w, /images/2020/05/ecs-diy-auto-scaling@330w2x.webp 660w, /images/2020/05/ecs-diy-auto-scaling@545w.webp 545w, /images/2020/05/ecs-diy-auto-scaling@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/05/ecs-diy-auto-scaling@730w.png 730w, /images/2020/05/ecs-diy-auto-scaling@730w2x.png 1460w, /images/2020/05/ecs-diy-auto-scaling@610w.png 610w, /images/2020/05/ecs-diy-auto-scaling@610w2x.png 1220w, /images/2020/05/ecs-diy-auto-scaling@450w.png 450w, /images/2020/05/ecs-diy-auto-scaling@450w2x.png 900w, /images/2020/05/ecs-diy-auto-scaling@330w.png 330w, /images/2020/05/ecs-diy-auto-scaling@330w2x.png 660w, /images/2020/05/ecs-diy-auto-scaling@545w.png 545w, /images/2020/05/ecs-diy-auto-scaling@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/05/ecs-diy-auto-scaling.png" alt="ECS with DIY Auto Scaling" title="ECS with DIY Auto Scaling"></picture></p><p>Check out <a href="https://github.com/widdix/aws-cf-templates/blob/master/ecs/cluster-cost-optimized.yaml" target="_blank" rel="noopener">cluster-cost-optimized.yaml</a> for an implementation example.</p><p>I have to mention that the DIY Auto Scaling comes with limitations as well:</p><ul><li>It only works for ECS services. The cluster will not scale out when creating a standalone task, as there is no CloudWatch event in this case.</li><li>ECS does publish the <code>SERVICE_TASK_PLACEMENT_FAILURE</code> only once. In case that event is not triggering an increase of the desired capacity, for example, because of a Lambda failure, the cluster will get stuck.</li><li>You have to maintain a DIY solution adding a lot of complexity to your container infrastructure.</li></ul><p>Having said that, the DIY Auto Scaling mitigates the problems of the built-in Cluster Auto Scaling:</p><ul><li>ECS will move tasks (containers) to other instances to avoid fragmentation. Therefore, the cluster scales in to a minimum, which is more cost-effective.</li><li>Rolling out the latest version of the ECS AMI works fine with CloudFormation.</li></ul><p>Unfortunately, there is no perfect solution for scaling an ECS cluster right now. Hopefully, the Cluster Auto Scaling will improve over time. Alternatively, I can think of a mixture of the built-in Cluster Auto Scaling with termination protection disabled and the Auto Scaling Lifecycle Hook from our DIY solution.</p><h2 id="EKS-with-Cluster-Autoscaler"><a href="#EKS-with-Cluster-Autoscaler" class="headerlink" title="EKS with Cluster Autoscaler"></a>EKS with Cluster Autoscaler</h2><p>AWS announced Managed Node Groups automating the provisioning and lifecycle management of EC2 instances for EKS clusters in November 2019. Kubernetes (K8s) itself comes with the Cluster Autoscaler, a tool that automatically adjusts the number of EC2 instances when one of the following conditions is true:</p><ul><li>there are pods that failed to run in the cluster due to insufficient resources,</li><li>there are nodes in the cluster that have been underutilized for an extended period of time, and their pods can be placed on other existing nodes.<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup></li></ul><p>By the way, the Cluster Autoscaler supports AliCloud, Azure, and BaiduCloud as well.</p><p>Besides a bunch of authentication and authorization configuration (IAM and RBAC), you need to deploy the Cluster Autoscaler into the <code>kube-system</code> namespace of your EKS cluster.</p><p>The Cluster Autoscaler respects the <a href="https://kubernetes.io/docs/tasks/run-application/configure-pdb/" target="_blank" rel="noopener">disruption budget of your application</a>. The following snippet shows a disruption budget stating that there should be at least one pod running for the app <code>redis-master</code>.</p><figure class="highlight nix"><table><tr><td class="code"><pre><span class="line"><span class="params">apiVersion:</span> policy<span class="symbol">/v1beta1</span></span><br><span class="line"><span class="params">kind:</span> PodDisruptionBudget</span><br><span class="line"><span class="params">metadata:</span></span><br><span class="line">  <span class="params">name:</span> redis-master-pdb</span><br><span class="line"><span class="params">spec:</span></span><br><span class="line">  <span class="params">minAvailable:</span> <span class="number">1</span></span><br><span class="line">  <span class="params">selector:</span></span><br><span class="line">    <span class="params">matchLabels:</span></span><br><span class="line">      <span class="params">app:</span> redis-master</span><br></pre></td></tr></table></figure><p>So far, I could not find any flaws when using the EKS Cluster Autoscaler in combination with a Managed Node Group.</p><h2 id="Summary-and-Comparison"><a href="#Summary-and-Comparison" class="headerlink" title="Summary and Comparison"></a>Summary and Comparison</h2><p>Fist of all, try to avoid having to scaling a container cluster at all. Use Fargate whenever possible. Using EKS, Managed Node Groups, and the K8s’s Cluster Autoscaler is the simplest way to manage the virtual machines for a container cluster. When using ECS, be aware that the built-in Cluster Auto Scaling will not scale in sufficiently and therefore cause unused overcapacity and overspending. Building an auto-scaling solution for ECS yourself is possible, but not optimal as well.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th>ECS Cluster Auto Scaling</th><th>ECS DIY Auto Scaling</th><th>EKS Cluster Autoscaler + Managed Node Group</th><th>Fargate (ECS/EKS)</th></tr></thead><tbody><tr><td>Scale out/in automatically</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>Scale out delay.</td><td>~3 min</td><td>immediately</td><td>immediately</td><td>immediately</td></tr><tr><td>Scale out for standalone task/pod</td><td>✅</td><td>❌</td><td>✅</td><td>✅</td></tr><tr><td>Move containers to optimize cluster utilization</td><td>❌</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>Rolling update to change AMI</td><td>❌</td><td>✅</td><td>✅</td><td>✅</td></tr></tbody></table><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. <a href="https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler">kubernetes/autoscaler</a> <a href="#fnref:1" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Databases on AWS</title>
      <link>https://cloudonaut.io/databases-on-aws/</link>
      <description>
        <![CDATA[<p>Andy Jassy, CEO of AWS, proclaimed <em>#DBFreedom</em>, aka use whatever database you like. AWS offers them all. At least, that’s what AW]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/rds/">rds</category>
      <category domain="https://cloudonaut.io/tag/dynamodb/">dynamodb</category>
      <category domain="https://cloudonaut.io/tag/elasticache/">elasticache</category>
      <category domain="https://cloudonaut.io/tag/elasticsearch/">elasticsearch</category>
      <category domain="https://cloudonaut.io/tag/documentdb/">documentdb</category>
      <guid isPermaLink="true">https://cloudonaut.io/databases-on-aws/</guid>
      <pubDate>Thu, 30 Apr 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Andy Jassy, CEO of AWS, proclaimed <em>#DBFreedom</em>, aka use whatever database you like. AWS offers them all. At least, that’s what AWS marketing wants us to understand.</p><p>In the real world, AWS offers a wide variety of databases for different use cases. Your job is to pick the right solution for your problem. Knowing all the options improves the quality of your architectural decisions. In this blog post, I introduce all the database options that AWS offers.</p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/freedom@730w.webp 730w, /images/2020/04/freedom@730w2x.webp 1460w, /images/2020/04/freedom@610w.webp 610w, /images/2020/04/freedom@610w2x.webp 1220w, /images/2020/04/freedom@450w.webp 450w, /images/2020/04/freedom@450w2x.webp 900w, /images/2020/04/freedom@330w.webp 330w, /images/2020/04/freedom@330w2x.webp 660w, /images/2020/04/freedom@545w.webp 545w, /images/2020/04/freedom@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/freedom@730w.jpg 730w, /images/2020/04/freedom@730w2x.jpg 1460w, /images/2020/04/freedom@610w.jpg 610w, /images/2020/04/freedom@610w2x.jpg 1220w, /images/2020/04/freedom@450w.jpg 450w, /images/2020/04/freedom@450w2x.jpg 900w, /images/2020/04/freedom@330w.jpg 330w, /images/2020/04/freedom@330w2x.jpg 660w, /images/2020/04/freedom@545w.jpg 545w, /images/2020/04/freedom@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/freedom.jpg" alt="Databases on AWS: #DBFreedom" title="Databases on AWS: #DBFreedom"></picture></p><h2 id="Amazon-Relational-Database-Service-RDS"><a href="#Amazon-Relational-Database-Service-RDS" class="headerlink" title="Amazon Relational Database Service (RDS)"></a>Amazon Relational Database Service (RDS)</h2><p><a href="https://aws.amazon.com/rds/" target="_blank" rel="noopener">Amazon RDS</a> provides traditional relational databases operated by AWS. You can create a new database with the click of a button, wait 5-15 minutes, and you are ready to go. AWS takes care of patching, monitoring, backups, HA deployments, and read-replicas.</p><p>The following engines are supported:</p><ul><li>MySQL</li><li>MariaDB</li><li>PostgreSQL</li><li>Oracle Database</li><li>Microsoft SQL Serve</li></ul><p>Use RDS if no other database is a better fit or if you are in doing a lift&amp;shift migration.</p><h2 id="Amazon-Aurora-Serverless"><a href="#Amazon-Aurora-Serverless" class="headerlink" title="Amazon Aurora (Serverless)"></a>Amazon Aurora (Serverless)</h2><p><a href="https://aws.amazon.com/rds/aurora/" target="_blank" rel="noopener">Amazon Aurora</a> is also part of RDS, but it requires a more detailed view. Aurora is a proprietary database engine developed by Amazon. The core of the technology is a unique storage layer that makes it possible to scale relational databases horizontally without hassle. The following figure demonstrates the replicated storage layer and the horizontally scalable database instance.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/rds-aurora@730w.webp 730w, /images/2020/04/rds-aurora@730w2x.webp 1460w, /images/2020/04/rds-aurora@610w.webp 610w, /images/2020/04/rds-aurora@610w2x.webp 1220w, /images/2020/04/rds-aurora@450w.webp 450w, /images/2020/04/rds-aurora@450w2x.webp 900w, /images/2020/04/rds-aurora@330w.webp 330w, /images/2020/04/rds-aurora@330w2x.webp 660w, /images/2020/04/rds-aurora@545w.webp 545w, /images/2020/04/rds-aurora@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/rds-aurora@730w.png 730w, /images/2020/04/rds-aurora@730w2x.png 1460w, /images/2020/04/rds-aurora@610w.png 610w, /images/2020/04/rds-aurora@610w2x.png 1220w, /images/2020/04/rds-aurora@450w.png 450w, /images/2020/04/rds-aurora@450w2x.png 900w, /images/2020/04/rds-aurora@330w.png 330w, /images/2020/04/rds-aurora@330w2x.png 660w, /images/2020/04/rds-aurora@545w.png 545w, /images/2020/04/rds-aurora@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/rds-aurora.png" alt="Amazon Aurora" title="Amazon Aurora"></picture></p><p>Aurora provides a MySQL or PostgreSQL compatible database that is easy to scale.</p><p>If you go with the Serverless offering, you get a database that scales in and out depending on load. Does your workload run on MySQL or PostgreSQL? Give Aurora Serverless a try!</p><h2 id="Amazon-DynamoDB"><a href="#Amazon-DynamoDB" class="headerlink" title="Amazon DynamoDB"></a>Amazon DynamoDB</h2><p><a href="https://aws.amazon.com/dynamodb/" target="_blank" rel="noopener">Amazon DynamoDB</a> is a NoSQL database with virtually unlimited scaling. Both in terms of storage and queries per second. The downside is that DynamoDB is not a relational database and does not support SQL. You can think of it as a document or key-value store if you are familiar with those concepts. If you create a data model, you have to work from the queries backward. The following figure shows a Serverless application that uses DynamoDB as a data store.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/dynamodb@730w.webp 730w, /images/2020/04/dynamodb@730w2x.webp 1460w, /images/2020/04/dynamodb@610w.webp 610w, /images/2020/04/dynamodb@610w2x.webp 1220w, /images/2020/04/dynamodb@450w.webp 450w, /images/2020/04/dynamodb@450w2x.webp 900w, /images/2020/04/dynamodb@330w.webp 330w, /images/2020/04/dynamodb@330w2x.webp 660w, /images/2020/04/dynamodb@545w.webp 545w, /images/2020/04/dynamodb@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/dynamodb@730w.png 730w, /images/2020/04/dynamodb@730w2x.png 1460w, /images/2020/04/dynamodb@610w.png 610w, /images/2020/04/dynamodb@610w2x.png 1220w, /images/2020/04/dynamodb@450w.png 450w, /images/2020/04/dynamodb@450w2x.png 900w, /images/2020/04/dynamodb@330w.png 330w, /images/2020/04/dynamodb@330w2x.png 660w, /images/2020/04/dynamodb@545w.png 545w, /images/2020/04/dynamodb@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/dynamodb.png" alt="Amazon DynamoDB" title="Amazon DynamoDB"></picture></p><p>You interact with DynamoDB via the AWS API. Usually, you use one of the AWS SDKs to call the API from your programming language of choice.</p><h2 id="Amazon-DocumentDB"><a href="#Amazon-DocumentDB" class="headerlink" title="Amazon DocumentDB"></a>Amazon DocumentDB</h2><p><a href="https://aws.amazon.com/documentdb/" target="_blank" rel="noopener">Amazon DocumentDB</a> provides a MongoDB compatible database hosted by AWS. DocumentDB is powered by Aurora storage technology. MongoDB is a document database that can be used as a primary database.</p><p>Not all features of MongoDB 3.6 are supported. If you are looking for a real MongoDB, check out <a href="https://www.mongodb.com/cloud/atlas" target="_blank" rel="noopener">MongoDB Atlas</a>.</p><p>Typical use cases include more complex data models, as found in business applications.</p><h2 id="Amazon-ElastiCache"><a href="#Amazon-ElastiCache" class="headerlink" title="Amazon ElastiCache"></a>Amazon ElastiCache</h2><p><a href="https://aws.amazon.com/elasticache/" target="_blank" rel="noopener">Amazon ElastiCache</a> provides in-memory caches operated by AWS. Choose between Redis and memcached, two popular Open Source in-memory databases. You can expect similar features that RDS provides as well: high availability, snapshots, and many more. Caches are usually not used as the primary data source. Instead, a cache is used to offload reads from the database. </p><p>Typical use cases are caching, low latency and read-intensive lookups, volatile data (e.g., a session store).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/elasticache@730w.webp 730w, /images/2020/04/elasticache@730w2x.webp 1460w, /images/2020/04/elasticache@610w.webp 610w, /images/2020/04/elasticache@610w2x.webp 1220w, /images/2020/04/elasticache@450w.webp 450w, /images/2020/04/elasticache@450w2x.webp 900w, /images/2020/04/elasticache@330w.webp 330w, /images/2020/04/elasticache@330w2x.webp 660w, /images/2020/04/elasticache@545w.webp 545w, /images/2020/04/elasticache@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/elasticache@730w.png 730w, /images/2020/04/elasticache@730w2x.png 1460w, /images/2020/04/elasticache@610w.png 610w, /images/2020/04/elasticache@610w2x.png 1220w, /images/2020/04/elasticache@450w.png 450w, /images/2020/04/elasticache@450w2x.png 900w, /images/2020/04/elasticache@330w.png 330w, /images/2020/04/elasticache@330w2x.png 660w, /images/2020/04/elasticache@545w.png 545w, /images/2020/04/elasticache@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/elasticache.png" alt="Amazon ElastiCache" title="Amazon ElastiCache"></picture></p><p>Cached data can become outdated. Commonly, caches are not always consistent with the primary data source. But the performance benefits are worth it.</p><h2 id="Amazon-Elasticsearch-Service"><a href="#Amazon-Elasticsearch-Service" class="headerlink" title="Amazon Elasticsearch Service"></a>Amazon Elasticsearch Service</h2><p><a href="https://aws.amazon.com/elasticsearch-service/" target="_blank" rel="noopener">Amazon Elasticsearch Service</a> provides Elasticsearch hosted by AWS. Elasticsearch is a document store with a search engine. Usually, Elasticsearch is not used as the primary database. Instead, data is replicated into Elasticsearch to provide search functionality to end-users.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/elasticsearch@730w.webp 730w, /images/2020/04/elasticsearch@730w2x.webp 1460w, /images/2020/04/elasticsearch@610w.webp 610w, /images/2020/04/elasticsearch@610w2x.webp 1220w, /images/2020/04/elasticsearch@450w.webp 450w, /images/2020/04/elasticsearch@450w2x.webp 900w, /images/2020/04/elasticsearch@330w.webp 330w, /images/2020/04/elasticsearch@330w2x.webp 660w, /images/2020/04/elasticsearch@545w.webp 545w, /images/2020/04/elasticsearch@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/elasticsearch@730w.png 730w, /images/2020/04/elasticsearch@730w2x.png 1460w, /images/2020/04/elasticsearch@610w.png 610w, /images/2020/04/elasticsearch@610w2x.png 1220w, /images/2020/04/elasticsearch@450w.png 450w, /images/2020/04/elasticsearch@450w2x.png 900w, /images/2020/04/elasticsearch@330w.png 330w, /images/2020/04/elasticsearch@330w2x.png 660w, /images/2020/04/elasticsearch@545w.png 545w, /images/2020/04/elasticsearch@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/elasticsearch.png" alt="Amazon Elasticsearch Service" title="Amazon Elasticsearch Service"></picture></p><p>Due to some licensing conflicts with Elastic, Amazon provides the <a href="https://opendistro.github.io/for-elasticsearch/" target="_blank" rel="noopener">Open Distro for Elasticsearch</a>, which is a flavor of Elasticsearch. Don’t be confused by Open Distro. It’s the same as Elasticsearch, but open-source implementations replace the commercial plugins.</p><p>Typical use case: full-text search for documents as well as faceted search for an online shop.</p><h2 id="Amazon-Redshift"><a href="#Amazon-Redshift" class="headerlink" title="Amazon Redshift"></a>Amazon Redshift</h2><p><a href="https://aws.amazon.com/redshift/" target="_blank" rel="noopener">Amazon Redshift</a> provides a data warehouse managed by AWS. Redshift can deal with up to 8 PB of data! It’s a relational database that supports SQL for queries. Redshift is good at inserting large data chunks in one shot. It doesn’t like to receive frequent but small updates.</p><p>The typical use case is a data warehouse.</p><h2 id="Amazon-Neptune"><a href="#Amazon-Neptune" class="headerlink" title="Amazon Neptune"></a>Amazon Neptune</h2><p><a href="https://aws.amazon.com/neptune/" target="_blank" rel="noopener">Amazon Neptune</a> provides a graph database operated by AWS. Neptune supports Gremlin and SPARQL to interact with the data. Graph databases shine when your data is highly interconnected. Graphs can be used to answer questions such as “people like you bought this” or “friends of friends like that”.</p><p>The typical use case is highly interconnected data.</p><h2 id="Amazon-Quantum-Ledger-Database-QLDB"><a href="#Amazon-Quantum-Ledger-Database-QLDB" class="headerlink" title="Amazon Quantum Ledger Database (QLDB)"></a>Amazon Quantum Ledger Database (QLDB)</h2><p>Amazon QLDB provides a ledger database. This is a new category of databases. You cannot update or delete data in QLDB. You can only append new data. QLDB goes one step further: you can cryptographically verify that the data has not changed. A perfect fit for a system that deals with financial transactions that must never change. QLDB comes with zero operational effort for you. AWS takes care of everything.</p><h2 id="Amazon-Keyspaces-for-Apache-Cassandra"><a href="#Amazon-Keyspaces-for-Apache-Cassandra" class="headerlink" title="Amazon Keyspaces (for Apache Cassandra)"></a>Amazon Keyspaces (for Apache Cassandra)</h2><p><a href="https://aws.amazon.com/keyspaces/" target="_blank" rel="noopener">Amazon Keyspaces</a> provides a Cassandra compatible database. MCS comes with a Serverless offering with built-in auto-scaling. All you have to do is query the database. Cassandra is known as a wide column database with a proven track record. Be warned, Cassandra is not easy to use!</p><p>Previously known as Amazon Managed Apache Cassandra Service (MCS).</p><h2 id="Amazon-Timestream"><a href="#Amazon-Timestream" class="headerlink" title="Amazon Timestream"></a>Amazon Timestream</h2><p><a href="https://aws.amazon.com/timestream/" target="_blank" rel="noopener">Amazon Timestream</a> provides a time-series database. Time series data is all about time. Good fits are sensor data, stock market data, FX rates, and so on. Whenever tuples of time and value are stored, and your queries deal with time spans, this database might be of value. Unfortunately, there is not too much information about Timestream published at this moment. Timestream is in private preview and, therefore, not ready for most of us and certainly not for production workloads!</p><h2 id="Comparison"><a href="#Comparison" class="headerlink" title="Comparison"></a>Comparison</h2><p>The following table provides a comparison of the different database options on AWS.</p><div class="table-responsive"><table class="table table-striped table-sm"><thead><tr><th></th><th>RDS / Aurora / Serverless</th><th>DynamoDB</th><th>DocumentDB</th><th>ElastiCache</th><th>Elasticsearch Service</th><th>Redshift</th><th>Neptune</th><th>QLDB</th><th>Amazon Keyspaces (for Apache Cassandra)</th><th>Timestream</th></tr></thead><tbody><tr><td>Max. Data Volume</td><td><p>64 TiB</p></td><td><p>Unlimited</p></td><td><p>64 TB</p></td><td><p>155 TiB</p></td><td><p>3 PB</p></td><td><p>8 PB</p></td><td><p>64 TB</p></td><td><p>Unlimited</p></td><td><p>Unlimited</p></td><td><p>Not documented yet.</p></td></tr><tr><td>Interface</td><td><p>SQL</p></td><td><p>AWS API</p></td><td><p>Subset of MongoDB API</p></td><td><p>Redis&#x2F;memcached API</p></td><td><p>Elasticsearch API</p></td><td><p>SQL</p></td><td><p>Subset of Gremlin &amp; SPARQL</p></td><td><p>Subset of PartiQL</p></td><td><p>Subset of CQL</p></td><td><p>Not documented yet.</p></td></tr><tr><td>Replication</td><td><p>None, Multi-AZ, Multi-Region</p></td><td><p>Multi-AZ, Multi-Region</p></td><td><p>Multi-AZ</p></td><td><p>Multi-AZ</p></td><td><p>Multi-AZ</p></td><td><p>Multi-AZ</p></td><td><p>Multi-AZ</p></td><td><p>Multi-AZ</p></td><td><p>Multi-AZ</p></td><td><p>Not documented yet.</p></td></tr><tr><td>DB Model</td><td><p>Relational</p></td><td><p>Key-value, Document</p></td><td><p>Document</p></td><td><p>Key-value</p></td><td><p>Document, SearchEngine</p></td><td><p>Relational</p></td><td><p>Graph</p></td><td><p>Ledger</p></td><td><p>Widecolumn</p></td><td><p>Timeseries</p></td></tr></tbody></table></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Anonymize CloudFront Access Logs</title>
      <link>https://cloudonaut.io/anonymize-cloudfront-access-logs/</link>
      <description>
        <![CDATA[<p><a href="https://aws.amazon.com/cloudfront/" target="_blank" rel="noopener">Amazon CloudFront</a> can upload <a href="https://docs.aws.am]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudfront/">cloudfront</category>
      <guid isPermaLink="true">https://cloudonaut.io/anonymize-cloudfront-access-logs/</guid>
      <pubDate>Wed, 22 Apr 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><a href="https://aws.amazon.com/cloudfront/" target="_blank" rel="noopener">Amazon CloudFront</a> can upload <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html" target="_blank" rel="noopener">access log files</a> to an S3 bucket. By default, CloudFront logs the IP address of the client. Optionally, cookies could be logged as well. If EU citizens access your CloudFront distribution, you have to process personally identifiable information (PII) in a General Data Protection Regulation (GDPR) compliant way. IP addresses are considered PII, and cookie data could also contain PII. If you want to process and store PII, you need a reason in the spirit of the GDPR.</p><blockquote><p>Disclaimer: I’m not a lawyer! This is not legal advice.</p></blockquote><p>Access logs are required to support operations to debug issues. For that purpose, it is okay to keep the access logs for seven days<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup>. But you might need access logs for capacity planning as well. How can you keep the access logs for more than seven days without violating GDPR?</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/anonymize@730w.webp 730w, /images/2020/04/anonymize@730w2x.webp 1460w, /images/2020/04/anonymize@610w.webp 610w, /images/2020/04/anonymize@610w2x.webp 1220w, /images/2020/04/anonymize@450w.webp 450w, /images/2020/04/anonymize@450w2x.webp 900w, /images/2020/04/anonymize@330w.webp 330w, /images/2020/04/anonymize@330w2x.webp 660w, /images/2020/04/anonymize@545w.webp 545w, /images/2020/04/anonymize@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/anonymize@730w.jpg 730w, /images/2020/04/anonymize@730w2x.jpg 1460w, /images/2020/04/anonymize@610w.jpg 610w, /images/2020/04/anonymize@610w2x.jpg 1220w, /images/2020/04/anonymize@450w.jpg 450w, /images/2020/04/anonymize@450w2x.jpg 900w, /images/2020/04/anonymize@330w.jpg 330w, /images/2020/04/anonymize@330w2x.jpg 660w, /images/2020/04/anonymize@545w.jpg 545w, /images/2020/04/anonymize@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/anonymize.jpg" alt="Anonymize Data" title="Anonymize Data"></picture></p><p>The question is: do you really need the IP address in our access logs? The answer is likely no. Unfortunately, CloudFront does not allow us to disable the IP address logging. We have to implement a workaround to anonymize the access logs as soon as they are available on S3. The workaround works like this:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/anonymize-cloudfront-access-logs@730w.webp 730w, /images/2020/04/anonymize-cloudfront-access-logs@730w2x.webp 1460w, /images/2020/04/anonymize-cloudfront-access-logs@610w.webp 610w, /images/2020/04/anonymize-cloudfront-access-logs@610w2x.webp 1220w, /images/2020/04/anonymize-cloudfront-access-logs@450w.webp 450w, /images/2020/04/anonymize-cloudfront-access-logs@450w2x.webp 900w, /images/2020/04/anonymize-cloudfront-access-logs@330w.webp 330w, /images/2020/04/anonymize-cloudfront-access-logs@330w2x.webp 660w, /images/2020/04/anonymize-cloudfront-access-logs@545w.webp 545w, /images/2020/04/anonymize-cloudfront-access-logs@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/anonymize-cloudfront-access-logs@730w.png 730w, /images/2020/04/anonymize-cloudfront-access-logs@730w2x.png 1460w, /images/2020/04/anonymize-cloudfront-access-logs@610w.png 610w, /images/2020/04/anonymize-cloudfront-access-logs@610w2x.png 1220w, /images/2020/04/anonymize-cloudfront-access-logs@450w.png 450w, /images/2020/04/anonymize-cloudfront-access-logs@450w2x.png 900w, /images/2020/04/anonymize-cloudfront-access-logs@330w.png 330w, /images/2020/04/anonymize-cloudfront-access-logs@330w2x.png 660w, /images/2020/04/anonymize-cloudfront-access-logs@545w.png 545w, /images/2020/04/anonymize-cloudfront-access-logs@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/anonymize-cloudfront-access-logs.png" alt="Anonymize CloudFront Access Logs" title="Anonymize CloudFront Access Logs"></picture></p><p><em>The diagram was created with <a href="https://bit.ly/2PpMc2M" target="_blank" rel="noopener">Cloudcraft - Visualize your cloud architecture like a pro</a>.</em></p><p>We can use a similar mechanism that is implemented by <a href="https://support.google.com/analytics/answer/2763052?hl=en" target="_blank" rel="noopener">Google Analytics</a>. An IPv4 address like 91.45.135.67 is turned into 91.45.135.0 (the last 8 bits are removed, 24 bits are kept). IPv6 addresses need a different logic: Google removes the last 80 bits. I will go one step further and remove the last 96 bits and keep 32 bits <sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup>.</p><p>The following steps are needed to anonymize an access log file:</p><ol><li>Download the object from S3</li><li>Decompress the <code>gzip</code> data</li><li>Parse the data (tab-separated values, <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html#LogFileFormat" target="_blank" rel="noopener">log file format</a>)</li><li>Replace the IP addresses with anonymized values</li><li>Compress the data with <code>gzip</code></li><li>Upload the anonymized data to S3</li><li>Remove the original data from S3</li></ol><p>There is no documented max size of an access log file. We should prepare for files that are larger than the available memory. Luckily, Lambda functions support Node.js, which has superb support to deal with streaming data. If we stream data, we never load all data into memory at once.</p><p>First, weload some core Node.js dependencies and the AWS SDK:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> zlib = <span class="built_in">require</span>(<span class="string">&#x27;zlib&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> stream = <span class="built_in">require</span>(<span class="string">&#x27;stream&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> s3 = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title function_">S3</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2006-03-01&#x27;</span>&#125;);</span><br></pre></td></tr></table></figure><p>It’s time to implement the anonymization:</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">anonymizeIPv4Address</span>(<span class="params">str</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> s = str.<span class="title function_">split</span>(<span class="string">&#x27;.&#x27;</span>);</span><br><span class="line">  s[<span class="number">3</span>] = <span class="string">&#x27;0&#x27;</span>;</span><br><span class="line">  <span class="keyword">return</span> s.<span class="title function_">join</span>(<span class="string">&#x27;.&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">anonymizeIPv6Address</span>(<span class="params">str</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> s = str.<span class="title function_">split</span>(<span class="string">&#x27;:&#x27;</span>).<span class="title function_">slice</span>(<span class="number">0</span>, <span class="number">2</span>);</span><br><span class="line">  s.<span class="title function_">push</span>(<span class="string">&#x27;:&#x27;</span>);</span><br><span class="line">  <span class="keyword">return</span> s.<span class="title function_">join</span>(<span class="string">&#x27;:&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">anonymizeIpAddress</span>(<span class="params">str</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (str === <span class="string">&#x27;-&#x27;</span> || str === <span class="string">&#x27;unknown&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> str;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (str.<span class="title function_">includes</span>(<span class="string">&#x27;.&#x27;</span>)) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">anonymizeIPv4Address</span>(str);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (str.<span class="title function_">includes</span>(<span class="string">&#x27;:&#x27;</span>)) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">anonymizeIPv6Address</span>(str);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;Neither IPv4 nor IPv6: &#x27;</span> + str);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>We also have to deal with TSV (tab-separated values)</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">transformLine</span>(<span class="params">line</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (line.<span class="title function_">startsWith</span>(<span class="string">&#x27;#&#x27;</span>) || line.<span class="title function_">trim</span>() === <span class="string">&#x27;&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> line;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> values = line.<span class="title function_">split</span>(<span class="string">&#x27;\t&#x27;</span>);</span><br><span class="line">  values[<span class="number">4</span>] = <span class="title function_">anonymizeIpAddress</span>(values[<span class="number">4</span>]);</span><br><span class="line">  values[<span class="number">19</span>] = <span class="title function_">anonymizeIpAddress</span>(values[<span class="number">19</span>]);</span><br><span class="line">  <span class="keyword">return</span> values.<span class="title function_">join</span>(<span class="string">&#x27;\t&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>So far, we process only small amounts of data: a single access log file line. It’s time to deal with the whole file.</p><p>Each chunk of data is represented as a <a href="https://nodejs.org/api/buffer.html" target="_blank" rel="noopener">buffer</a> in Node.js. A buffer represents binary data in the form of a sequence of bytes. In the buffer, we search for the line-end <code>\n</code> byte. We slice all bytes from beginning to <code>\n</code> and convert them into a string to extract a line. Continue with the apporach until end of file is reached. There is one edge case: A chunk of data can stop in the middle of a line. We have to add the old chunk to the beginning of a new chunk.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">process</span>(<span class="params">record</span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> chunk = <span class="title class_">Buffer</span>.<span class="title function_">alloc</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">transform</span> = (<span class="params">currentChunk, encoding, callback</span>) =&gt; &#123;</span><br><span class="line">    chunk = <span class="title class_">Buffer</span>.<span class="title function_">concat</span>([chunk, currentChunk]);</span><br><span class="line">    <span class="keyword">const</span> lines = [];</span><br><span class="line">    <span class="keyword">while</span>(chunk.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> i = chunk.<span class="title function_">indexOf</span>(<span class="string">&#x27;\n&#x27;</span>, <span class="string">&#x27;utf8&#x27;</span>);</span><br><span class="line">      <span class="keyword">if</span> (i === -<span class="number">1</span>) &#123;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        lines.<span class="title function_">push</span>(chunk.<span class="title function_">slice</span>(<span class="number">0</span>, i).<span class="title function_">toString</span>(<span class="string">&#x27;utf8&#x27;</span>));</span><br><span class="line">        chunk = chunk.<span class="title function_">slice</span>(i+<span class="number">1</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    lines.<span class="title function_">push</span>(<span class="string">&#x27;&#x27;</span>);</span><br><span class="line">    <span class="keyword">const</span> transformed = lines</span><br><span class="line">      .<span class="title function_">map</span>(transformLine)</span><br><span class="line">      .<span class="title function_">join</span>(<span class="string">&#x27;\n&#x27;</span>);</span><br><span class="line">    <span class="title function_">callback</span>(<span class="literal">null</span>, <span class="title class_">Buffer</span>.<span class="title function_">from</span>(transformed, <span class="string">&#x27;utf8&#x27;</span>));</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">const</span> params = &#123;</span><br><span class="line">    <span class="title class_">Bucket</span>: record.<span class="property">s3</span>.<span class="property">bucket</span>.<span class="property">name</span>,</span><br><span class="line">    <span class="title class_">Key</span>: record.<span class="property">s3</span>.<span class="property">object</span>.<span class="property">key</span></span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">if</span> (<span class="string">&#x27;versionId&#x27;</span> <span class="keyword">in</span> record.<span class="property">s3</span>.<span class="property">object</span>) &#123;</span><br><span class="line">    params.<span class="property">VersionId</span> = record.<span class="property">s3</span>.<span class="property">object</span>.<span class="property">versionId</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> body = s3.<span class="title function_">getObject</span>(params).<span class="title function_">createReadStream</span>()</span><br><span class="line">    .<span class="title function_">pipe</span>(zlib.<span class="title function_">createGunzip</span>())</span><br><span class="line">    .<span class="title function_">pipe</span>(<span class="keyword">new</span> stream.<span class="title class_">Transform</span>(&#123;</span><br><span class="line">      transform</span><br><span class="line">    &#125;))</span><br><span class="line">    .<span class="title function_">pipe</span>(zlib.<span class="title function_">createGzip</span>());</span><br><span class="line">  <span class="keyword">await</span> s3.<span class="title function_">upload</span>(&#123;</span><br><span class="line">    <span class="title class_">Bucket</span>: record.<span class="property">s3</span>.<span class="property">bucket</span>.<span class="property">name</span>,</span><br><span class="line">    <span class="title class_">Key</span>: record.<span class="property">s3</span>.<span class="property">object</span>.<span class="property">key</span>.<span class="title function_">slice</span>(<span class="number">0</span>, -<span class="number">2</span>) + <span class="string">&#x27;anonymized.gz&#x27;</span>,</span><br><span class="line">    <span class="title class_">Body</span>: body</span><br><span class="line">  &#125;).<span class="title function_">promise</span>();</span><br><span class="line">  <span class="keyword">if</span> (chunk.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;file was not read completly&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> s3.<span class="title function_">deleteObject</span>(params).<span class="title function_">promise</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Finally, Lambda requires a thin interface that we have to implement. I also ensure that anonymized data is not processed again to avoid an expensive infinit loop.</p><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">exports</span>.<span class="property">handler</span> = <span class="title function_">async</span> (event) =&gt; &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(event));</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> record <span class="keyword">of</span> event.<span class="property">Records</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (record.<span class="property">s3</span>.<span class="property">object</span>.<span class="property">key</span>.<span class="title function_">endsWith</span>(<span class="string">&#x27;.anonymized.gz&#x27;</span>)) &#123;</span><br><span class="line">      <span class="keyword">continue</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (record.<span class="property">s3</span>.<span class="property">object</span>.<span class="property">key</span>.<span class="title function_">endsWith</span>(<span class="string">&#x27;.gz&#x27;</span>)) &#123;</span><br><span class="line">      <span class="keyword">await</span> <span class="title function_">process</span>(record);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>I integrated the workaround into our collection of <a href="https://github.com/widdix/aws-cf-templates/" target="_blank" rel="noopener">aws-cf-templates</a>. <a href="https://templates.cloudonaut.io/en/stable/operations/#access-logs-anonymizer" target="_blank" rel="noopener">Check out the documentation</a> or the <a href="https://github.com/widdix/aws-cf-templates/blob/master/operations/cloudfront-access-logs-anonymizer.yaml" target="_blank" rel="noopener">code on GitHub</a>. A similar approach can be used to anonymize access logs from ELB load balancers (ALB, CLB, NLB).</p><p>PS: You should also enable S3 lifecycle rules to delete access logs after 38 months.</p><p><em>Thanks to <a href="https://x.com/hoegertn" target="_blank" rel="noopener">Thorsten Höger</a> for reviewing this article.</em></p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. Germany source: https://www.bsi.bund.de/SharedDocs/Downloads/DE/BSI/Veranstaltungen/ITSiKongress/14ter/Vortraege-19-05-2015/Heidrich_Wegener.pdf?__blob=publicationFile <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. One official recommendation I found recommends dropping at least the last 88 bits of an IPv6 address (German source: https://www.datenschutz-bayern.de/dsbk-ent/DSK_84-IPv6.html) <a href="#fnref:2" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Building with EC2: 10 Tips for the Successful Cloud Architect</title>
      <link>https://cloudonaut.io/building-with-ec2/</link>
      <description>
        <![CDATA[<p>Despite the Kubernetes and Serverless hypes, the vast majority of cloud workloads still happen on virtual machines. AWS offers the Amazon]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <guid isPermaLink="true">https://cloudonaut.io/building-with-ec2/</guid>
      <pubDate>Thu, 16 Apr 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Despite the Kubernetes and Serverless hypes, the vast majority of cloud workloads still happen on virtual machines. AWS offers the Amazon Elastic Compute Cloud (EC2) service, where you can launch virtual machines (AWS calls them instances). The EC2 service has evolved over 13 years. More performance, lower and less volatile latencies, and easier management are just some of the innovations of the last years. This blog post demonstrates how you can build modern architectures on EC2 and comes with ten tips to avoid the common pitfalls.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/build@730w.webp 730w, /images/2020/04/build@730w2x.webp 1460w, /images/2020/04/build@610w.webp 610w, /images/2020/04/build@610w2x.webp 1220w, /images/2020/04/build@450w.webp 450w, /images/2020/04/build@450w2x.webp 900w, /images/2020/04/build@330w.webp 330w, /images/2020/04/build@330w2x.webp 660w, /images/2020/04/build@545w.webp 545w, /images/2020/04/build@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/build@730w.jpg 730w, /images/2020/04/build@730w2x.jpg 1460w, /images/2020/04/build@610w.jpg 610w, /images/2020/04/build@610w2x.jpg 1220w, /images/2020/04/build@450w.jpg 450w, /images/2020/04/build@450w2x.jpg 900w, /images/2020/04/build@330w.jpg 330w, /images/2020/04/build@330w2x.jpg 660w, /images/2020/04/build@545w.jpg 545w, /images/2020/04/build@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/build.jpg" alt="Building with EC2" title="Building with EC2"></picture></p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/building-with-ec2/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><h2 id="The-Available-Software"><a href="#The-Available-Software" class="headerlink" title="The Available Software"></a>The Available Software</h2><p>EC2 provides Windows and Linux instances. You can find all kinds of versions of Windows and many Linux distributions. Amazon Linux is Amazon’s in-house Linux distribution that comes with the best integration into the AWS ecosystem. If you use CentOS or RHEL already, Amazon Linux feels familiar to you.</p><p>Operating systems are made available as so-called Amazon Machine Images (AMI). Keep in mind that everyone (including you) can publish AMIs.</p><blockquote><p>Tip #1: Only use AMIs published by AWS or trusted sources like well-known software vendors. Do not rely on the name of the images only. Ask the software vendor for the exact AMI IDs. AWS does not review the names! An attacker could create an image with a misleading name.</p></blockquote><p>In summary, an EC2 instance is launched based on an AMI that contains at least an operating system.</p><h2 id="The-Available-Hardware"><a href="#The-Available-Hardware" class="headerlink" title="The Available Hardware"></a>The Available Hardware</h2><p>If you dig deeper into the EC2 specs, you are confronted with a lesser-known unit (GiB) to specify available storage and memory sizes. One gibibyte (GiB) is 1,073,741,824 bytes while one gigabyte (GB) is 1,000,000,000 bytes. Therefore, 1 GiB &gt; 1 GB. If you migrate existing workloads to AWS, you likely have to convert between the two units.</p><blockquote><p>Tip #2: Google can convert Gib to GB. Search for “1 GiB to GB”, and Google will do the math for you.</p></blockquote><p>You can get instances with up to 448 CPU cores, 24 TiB of memory, 100 GBit&#x2F;s network throughput, and ~58 TiB of local disk space. If you need GPUs, you can get up to 16 GPUs with 192 GiB of memory as well. Unfortunately, you can not configure an instance with the hardware requirements you need. Instead, AWS provides instance types, and you have to select one that fits your needs. A few examples (prices are valid for the us-east-1 region):</p><table class="table table-striped table-responsive"><thead><tr><th>Purpose</th><th>Architecture</th><th>Instance Type</th><th>CPU Cores</th><th>Memory</th><th>MonthlyCost ($)</th></tr></thead><tbody><tr><td>Compute-optimized</td><td>Intel</td><td>c5.large</td><td>2</td><td>4 GiB</td><td>61.20</td></tr><tr><td>General-purpose</td><td>ARM</td><td>a1.large</td><td>2</td><td>4 GiB</td><td>36.72</td></tr><tr><td>General-purpose</td><td>Intel</td><td>m5.large</td><td>2</td><td>8 GiB</td><td>69.12</td></tr><tr><td>Memory-optimized</td><td>Intel</td><td>r5.large</td><td>2</td><td>16 GiB</td><td>90.72</td></tr><tr><td>General-purpose</td><td>AMD</td><td>m5a.large</td><td>2</td><td>8 GiB</td><td>61.92</td></tr><tr><td>General-purpose</td><td>AMD</td><td>m5a.xlarge</td><td>16</td><td>16 GiB</td><td>123.84</td></tr><tr><td>General-purpose</td><td>AMD</td><td>m5a.16xlarge</td><td>64</td><td>256 GiB</td><td>1,981.44</td></tr></tbody></table><blockquote><p>Tip #3: Get the best overview of all available instance types in a given region here: <a href="https://ec2instances.info/" target="_blank" rel="noopener">ec2instances.info</a></p></blockquote><p>If you stay in the same instance family (e.g., m5a), the costs are linear to the size.</p><table class="table table-striped table-responsive"><thead><tr><th>Family</th><th>Size</th><th>Factor</th><th>Monthly Cost ($)</th></tr></thead><tbody><tr><td>m5a</td><td>large</td><td>1</td><td>1*61.92</td></tr><tr><td>m5a</td><td>xlarge</td><td>2</td><td>2*61.92</td></tr><tr><td>m5a</td><td>2xlarge</td><td>4</td><td>4*61.92</td></tr><tr><td>m5a</td><td>16xlarge</td><td>32</td><td>32*61.92</td></tr></tbody></table><p>The AWS offering includes CPUs by Intel, AMD (AMD64&#x2F;x86-64 architecture), and ARM (ARM64 architecture). The following costs formula applies: ARM &lt; AMD &lt; Intel.</p><blockquote><p>Tip #4: You can cut costs by 10% if you select an AMD instead of an Intel instance type while you can run the same applications. You can save more with ARM instances, but you have to check if your application runs on ARM.</p></blockquote><p>If you need bare-metal performance, EC2 has you covered. An instance can be either a virtual machine or a bare-metal machine. If you prefer not to share a host with other tenants (AWS customers) for compliance reasons, you can run virtual machines in a dedicated fashion.</p><blockquote><p>Tip #5: The network throughput of EC2 instances is a resource limit that is easy to miss. Unfortunately, AWS does not publish all the numbers that are required. You have to rely on  <a href="https://cloudonaut.io/ec2-network-performance-cheat-sheet/">network throughput data tables generated by the community</a>.</p></blockquote><h2 id="Mutable-or-Immutable-Management-of-Instances"><a href="#Mutable-or-Immutable-Management-of-Instances" class="headerlink" title="Mutable or Immutable Management of Instances"></a>Mutable or Immutable Management of Instances</h2><p>When it comes to managing EC2 instances, you have to pick one of two approaches:</p><p>A <strong>mutable</strong> EC2 instance is created once and then lives for many years. This comes with a significant disadvantage. If you manage more than one instance, sooner or later, the configuration of those instances will differ because someone forgets to run a command on the other instance. We call this configuration drift, which is the evil of all issues related to “but it works on this instance, why not on the other”.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/ec2-mutable@730w.webp 730w, /images/2020/04/ec2-mutable@730w2x.webp 1460w, /images/2020/04/ec2-mutable@610w.webp 610w, /images/2020/04/ec2-mutable@610w2x.webp 1220w, /images/2020/04/ec2-mutable@450w.webp 450w, /images/2020/04/ec2-mutable@450w2x.webp 900w, /images/2020/04/ec2-mutable@330w.webp 330w, /images/2020/04/ec2-mutable@330w2x.webp 660w, /images/2020/04/ec2-mutable@545w.webp 545w, /images/2020/04/ec2-mutable@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/ec2-mutable@730w.png 730w, /images/2020/04/ec2-mutable@730w2x.png 1460w, /images/2020/04/ec2-mutable@610w.png 610w, /images/2020/04/ec2-mutable@610w2x.png 1220w, /images/2020/04/ec2-mutable@450w.png 450w, /images/2020/04/ec2-mutable@450w2x.png 900w, /images/2020/04/ec2-mutable@330w.png 330w, /images/2020/04/ec2-mutable@330w2x.png 660w, /images/2020/04/ec2-mutable@545w.png 545w, /images/2020/04/ec2-mutable@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/ec2-mutable.png" alt="Mutable EC2 Instance" title="Mutable EC2 Instance"></picture></p><p>The workflow: humans or scripts log on to the running instance (e.g., via SSH or RDP) and do their work. For example, apply OS updates, install new packages, modify configuration files. Deployments happen while the EC2 instance is running. </p><p>To reduce the chances of configuration drift, you should use a tool to apply changes to running instances instead of remote sessions. </p><blockquote><p>Tip #6: AWS offers a suite of tools combined in the <a href="https://aws.amazon.com/systems-manager/" target="_blank" rel="noopener">AWS Systems Manager (SSM)</a> service to manage mutable instances. SSM covers patching during defined time windows, running commands on a fleet of instances, remote shells, software inventories, and much more. To deploy your software, I recommend <a href="https://aws.amazon.com/codedeploy/" target="_blank" rel="noopener">AWS CodeDeploy</a>.</p></blockquote><p>An <strong>immutable</strong> EC2 instance is never changed after creation. This comes with a significant benefit: All your instances are precisely the same. No configuration drift is possible.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/ec2-mutable@730w.webp 730w, /images/2020/04/ec2-mutable@730w2x.webp 1460w, /images/2020/04/ec2-mutable@610w.webp 610w, /images/2020/04/ec2-mutable@610w2x.webp 1220w, /images/2020/04/ec2-mutable@450w.webp 450w, /images/2020/04/ec2-mutable@450w2x.webp 900w, /images/2020/04/ec2-mutable@330w.webp 330w, /images/2020/04/ec2-mutable@330w2x.webp 660w, /images/2020/04/ec2-mutable@545w.webp 545w, /images/2020/04/ec2-mutable@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/ec2-mutable@730w.png 730w, /images/2020/04/ec2-mutable@730w2x.png 1460w, /images/2020/04/ec2-mutable@610w.png 610w, /images/2020/04/ec2-mutable@610w2x.png 1220w, /images/2020/04/ec2-mutable@450w.png 450w, /images/2020/04/ec2-mutable@450w2x.png 900w, /images/2020/04/ec2-mutable@330w.png 330w, /images/2020/04/ec2-mutable@330w2x.png 660w, /images/2020/04/ec2-mutable@545w.png 545w, /images/2020/04/ec2-mutable@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/ec2-mutable.png" alt="Immutable EC2 Instance" title="Immutable EC2 Instance"></picture></p><p>The workflow: You cannot change an instance. Instead, you launch a new instance based on a newer AMI and delete (AWS calls it terminate) the old one. So how do you get a newer AMI? You can build your own images. To do so, you launch a temporary instance based on an existing AMI (e.g., Amazon Linux 2). When the instance is ready, you log on and install the needed packages, add your application, create configuration files. Once you are done, you can create an AMI based on the EC2 instance. Once the AMI is created, you terminate the temporary EC2 instance. Does this sound like a lot of manual work? Luckily, there are tools to help us do so.</p><blockquote><p>Tip #7: <a href="https://www.packer.io/" target="_blank" rel="noopener">Packer by HashiCorp</a> is the most popular option. With Packer, you only have to care about automating the setup procedure (e.g., a bash script) while Packer cares about the rest and spits out an AMI at the end.</p></blockquote><h2 id="A-single-instance-is-a-single-point-of-failure"><a href="#A-single-instance-is-a-single-point-of-failure" class="headerlink" title="A single instance is a single point of failure"></a>A single instance is a single point of failure</h2><p>If you aim for a highly available or fault-tolerant architecture, you need more than one EC2 instance.</p><p>If the host has an issue, your EC2 instance will become unavailable. AWS will not migrate your instance to another host by default. Another strong argument against depending on a single EC2 instance in your architecture: AWS promises an uptime of only 90% for a single EC2 instance.</p><blockquote><p>Tip #8: If you run a single EC2 instance, <a href="https://aws.amazon.com/premiumsupport/knowledge-center/automatic-recovery-ec2-cloudwatch/" target="_blank" rel="noopener">enable auto-recovery</a> to move the instance to another instance in case of issues.</p></blockquote><p>Besides that, you also should spread your workload across <a href="https://blog.cloudcraft.co/building-with-ec2/#availability-zones" target="_blank" rel="noopener">Availability Zones (AZs)</a>. You can think of an AZ as an isolated data center. Two AZs are independent (location, power, cooling, connectivity, …). You indirectly control the AZ into which an instance is launched. You define the AZ by selecting a VPC subnet, which can only belong to one AZ. Therefore, you have to launch two EC2 instances in two different subnets that are created in separate AZs.</p><p>If you follow the immutable approach (discussed earlier in this post), you can make use of Auto Scaling Groups (ASGs) to launch N instances that are evenly spread across AZs. Don’t be confused by the name Auto Scaling Group. The number of instances will not be increased or decreased automatically for you. The benefit of ASGs is that they make sure that a desired number of instances is running at any time. In the rare case that an instance fails, the ASG will replace it within minutes.</p><blockquote><p>Tip #9: If you run an ASG with N&gt;&#x3D;2 in multiple subnets (in separate AZs), you are fine in terms of availability. This approach only works for immutable instances.</p></blockquote><p>If you follow the mutable approach, you are in trouble. You need to ensure that you can spin up a new instance to replace the failed one promptly. You also have to take backups of your instance (e.g., with <a href="https://aws.amazon.com/backup/" target="_blank" rel="noopener">AWS Backup</a>).</p><p>There is one problem if you run more than one EC2 instance: How do clients connect to the instance? You now have two IP addresses. You could use DNS to help you, but the best solution is described in the next section.</p><h2 id="Decoupling-with-Load-Balancer-or-SQS"><a href="#Decoupling-with-Load-Balancer-or-SQS" class="headerlink" title="Decoupling with Load Balancer or SQS"></a>Decoupling with Load Balancer or SQS</h2><p>AWS offers two ways to decouple clients from your EC2 instances. Load balancers can be used for synchronous decoupling. The client communicates with the load balancer, while the load balancer knows about the EC2 instances and forward traffic to them. The clue: load balancers offered by AWS are highly available by default. The following figure shows how a client connects to a load balancer, which connects to one of the instances. I’ve created the <a href="http://cloudcraft.co/" target="_blank" rel="noopener">AWS diagram with Cloudcraft</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/decouple-elb@730w.webp 730w, /images/2020/04/decouple-elb@730w2x.webp 1460w, /images/2020/04/decouple-elb@610w.webp 610w, /images/2020/04/decouple-elb@610w2x.webp 1220w, /images/2020/04/decouple-elb@450w.webp 450w, /images/2020/04/decouple-elb@450w2x.webp 900w, /images/2020/04/decouple-elb@330w.webp 330w, /images/2020/04/decouple-elb@330w2x.webp 660w, /images/2020/04/decouple-elb@545w.webp 545w, /images/2020/04/decouple-elb@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/decouple-elb@730w.png 730w, /images/2020/04/decouple-elb@730w2x.png 1460w, /images/2020/04/decouple-elb@610w.png 610w, /images/2020/04/decouple-elb@610w2x.png 1220w, /images/2020/04/decouple-elb@450w.png 450w, /images/2020/04/decouple-elb@450w2x.png 900w, /images/2020/04/decouple-elb@330w.png 330w, /images/2020/04/decouple-elb@330w2x.png 660w, /images/2020/04/decouple-elb@545w.png 545w, /images/2020/04/decouple-elb@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/decouple-elb.png" alt="Decoupling with Load Balancer" title="Decoupling with Load Balancer"></picture></p><p>If possible you should decouple the clients from your EC2 instances in an asynchronous way using a message queue. The most convenient option is the <a href="https://aws.amazon.com/sqs/" target="_blank" rel="noopener">Amazon Simple Queue Service</a>. Again, the queues offered by AWS are highly available. The clue is that the client can continue to send new jobs even if all EC2 instances are down (e.g., for maintenance). The following <a href="https://app.cloudcraft.co/view/72550874-efbe-41b2-9006-618898c41773?key=r4Ko1klYvMa2FUjxNacuOQ" target="_blank" rel="noopener">figure</a> shows how it works.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/decouple-sqs@730w.webp 730w, /images/2020/04/decouple-sqs@730w2x.webp 1460w, /images/2020/04/decouple-sqs@610w.webp 610w, /images/2020/04/decouple-sqs@610w2x.webp 1220w, /images/2020/04/decouple-sqs@450w.webp 450w, /images/2020/04/decouple-sqs@450w2x.webp 900w, /images/2020/04/decouple-sqs@330w.webp 330w, /images/2020/04/decouple-sqs@330w2x.webp 660w, /images/2020/04/decouple-sqs@545w.webp 545w, /images/2020/04/decouple-sqs@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/decouple-sqs@730w.png 730w, /images/2020/04/decouple-sqs@730w2x.png 1460w, /images/2020/04/decouple-sqs@610w.png 610w, /images/2020/04/decouple-sqs@610w2x.png 1220w, /images/2020/04/decouple-sqs@450w.png 450w, /images/2020/04/decouple-sqs@450w2x.png 900w, /images/2020/04/decouple-sqs@330w.png 330w, /images/2020/04/decouple-sqs@330w2x.png 660w, /images/2020/04/decouple-sqs@545w.png 545w, /images/2020/04/decouple-sqs@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/decouple-sqs.png" alt="Decoupling with SQS" title="Decoupling with SQS"></picture></p><blockquote><p>Tip #10: Always decouple your clients from your EC2 instances. If possible, use the asynchronous approach with SQS. Alternatively, use an Elastic Load Balancer for synchronous workloads.</p></blockquote><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>EC2 instances are the core of any architecture on AWS. With the right tools and patterns, you can create highly available architectures that can be operated efficiently. Follow my ten tips to ensure that you avoid common pitfalls and utilize the powers of EC2.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Account Structure: Think twice before using AWS Organizations</title>
      <link>https://cloudonaut.io/aws-account-structure-think-twice-before-using-aws-organizations/</link>
      <description>
        <![CDATA[<p>What is an AWS account? I like to use the following two ways to describe the concept of an AWS account: a tenant in Amazon’s multi-tenant]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-account-structure-think-twice-before-using-aws-organizations/</guid>
      <pubDate>Wed, 08 Apr 2020 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>What is an AWS account? I like to use the following two ways to describe the concept of an AWS account: a tenant in Amazon’s multi-tenant cloud or a virtual data center.</p><p>When running multiple workloads and environments using numerous AWS accounts is the best approach to draw the line between the following aspects:</p><ul><li>Billing and Cost Management</li><li>Identity and Access Management</li><li>Limit Management: Resources and API Requests</li></ul><p>Being able to isolate workloads and environments from each other is one of the secrets of the success of cloud journeys. Unfortunately, AWS started to weaken that principle with AWS Organizations during the last few years.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/organization@730w.webp 730w, /images/2020/04/organization@730w2x.webp 1460w, /images/2020/04/organization@610w.webp 610w, /images/2020/04/organization@610w2x.webp 1220w, /images/2020/04/organization@450w.webp 450w, /images/2020/04/organization@450w2x.webp 900w, /images/2020/04/organization@330w.webp 330w, /images/2020/04/organization@330w2x.webp 660w, /images/2020/04/organization@545w.webp 545w, /images/2020/04/organization@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/organization@730w.jpg 730w, /images/2020/04/organization@730w2x.jpg 1460w, /images/2020/04/organization@610w.jpg 610w, /images/2020/04/organization@610w2x.jpg 1220w, /images/2020/04/organization@450w.jpg 450w, /images/2020/04/organization@450w2x.jpg 900w, /images/2020/04/organization@330w.jpg 330w, /images/2020/04/organization@330w2x.jpg 660w, /images/2020/04/organization@545w.jpg 545w, /images/2020/04/organization@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/organization.jpg" alt="AWS Account Structure: Think twice before using AWS Organizations" title="AWS Account Structure: Think twice before using AWS Organizations"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/17-aws-account-structure-think-twice-before-using-aws-organizations/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="Evolution"><a href="#Evolution" class="headerlink" title="Evolution"></a>Evolution</h2><p>A few years ago, I started exploring AWS by creating an AWS account with my private credit card. Step by step, I deployed more and more workloads and environments to my AWS account.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/single-account@730w.webp 730w, /images/2020/04/single-account@730w2x.webp 1460w, /images/2020/04/single-account@610w.webp 610w, /images/2020/04/single-account@610w2x.webp 1220w, /images/2020/04/single-account@450w.webp 450w, /images/2020/04/single-account@450w2x.webp 900w, /images/2020/04/single-account@330w.webp 330w, /images/2020/04/single-account@330w2x.webp 660w, /images/2020/04/single-account@545w.webp 545w, /images/2020/04/single-account@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/single-account@730w.png 730w, /images/2020/04/single-account@730w2x.png 1460w, /images/2020/04/single-account@610w.png 610w, /images/2020/04/single-account@610w2x.png 1220w, /images/2020/04/single-account@450w.png 450w, /images/2020/04/single-account@450w2x.png 900w, /images/2020/04/single-account@330w.png 330w, /images/2020/04/single-account@330w2x.png 660w, /images/2020/04/single-account@545w.png 545w, /images/2020/04/single-account@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/single-account.png" alt="Single AWS Account" title="Single AWS Account"></picture></p><p>Later I learned, that AWS encourages us to create multiple accounts to isolate environments from each other. That blew my mind, as many web services even prohibit creating more than one account. So instead of running different workloads and environments within the same account, I created a second account to isolate the test environment from the production environment.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/multi-account@730w.webp 730w, /images/2020/04/multi-account@730w2x.webp 1460w, /images/2020/04/multi-account@610w.webp 610w, /images/2020/04/multi-account@610w2x.webp 1220w, /images/2020/04/multi-account@450w.webp 450w, /images/2020/04/multi-account@450w2x.webp 900w, /images/2020/04/multi-account@330w.webp 330w, /images/2020/04/multi-account@330w2x.webp 660w, /images/2020/04/multi-account@545w.webp 545w, /images/2020/04/multi-account@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/multi-account@730w.png 730w, /images/2020/04/multi-account@730w2x.png 1460w, /images/2020/04/multi-account@610w.png 610w, /images/2020/04/multi-account@610w2x.png 1220w, /images/2020/04/multi-account@450w.png 450w, /images/2020/04/multi-account@450w2x.png 900w, /images/2020/04/multi-account@330w.png 330w, /images/2020/04/multi-account@330w2x.png 660w, /images/2020/04/multi-account@545w.png 545w, /images/2020/04/multi-account@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/multi-account.png" alt="Multiple AWS Accounts" title="Multiple AWS Accounts"></picture></p><h3 id="2010-Multiple-AWS-Accounts-Consolidated-Billing"><a href="#2010-Multiple-AWS-Accounts-Consolidated-Billing" class="headerlink" title="2010: Multiple AWS Accounts + Consolidated Billing"></a>2010: Multiple AWS Accounts + Consolidated Billing</h3><p>Hard to believe, but true: AWS introduced consolidated billing in 2010. That solved two problems:</p><ol><li>A single monthly bill accumulates the spending among many AWS accounts.</li><li>Benefit from volume pricing across more than one AWS account.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/consolidated-billing@730w.webp 730w, /images/2020/04/consolidated-billing@730w2x.webp 1460w, /images/2020/04/consolidated-billing@610w.webp 610w, /images/2020/04/consolidated-billing@610w2x.webp 1220w, /images/2020/04/consolidated-billing@450w.webp 450w, /images/2020/04/consolidated-billing@450w2x.webp 900w, /images/2020/04/consolidated-billing@330w.webp 330w, /images/2020/04/consolidated-billing@330w2x.webp 660w, /images/2020/04/consolidated-billing@545w.webp 545w, /images/2020/04/consolidated-billing@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/consolidated-billing@730w.png 730w, /images/2020/04/consolidated-billing@730w2x.png 1460w, /images/2020/04/consolidated-billing@610w.png 610w, /images/2020/04/consolidated-billing@610w2x.png 1220w, /images/2020/04/consolidated-billing@450w.png 450w, /images/2020/04/consolidated-billing@450w2x.png 900w, /images/2020/04/consolidated-billing@330w.png 330w, /images/2020/04/consolidated-billing@330w2x.png 660w, /images/2020/04/consolidated-billing@545w.png 545w, /images/2020/04/consolidated-billing@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/consolidated-billing.png" alt="Consolidated Billing" title="Consolidated Billing"></picture></p><p>That reduced the burden of managing more than one AWS account significantly.</p><h3 id="2017-AWS-Organizations-1-0-account-management-and-billing"><a href="#2017-AWS-Organizations-1-0-account-management-and-billing" class="headerlink" title="2017: AWS Organizations 1.0: account management and billing"></a>2017: AWS Organizations 1.0: account management and billing</h3><p>As more and more Enterprise customers chose to move their workloads to the cloud, AWS focused more on their needs for managing hundreds or even thousands of AWS accounts centrally. Therefore, AWS introduced AWS Organizations in 2017.</p><p>AWS Organizations is designed to govern your AWS accounts and started with two main features:</p><ol><li>Ability to create and manage AWS accounts via the AWS Management Console and API.</li><li>Restricting the usage of AWS services per AWS account with a Service Control Policy (SCP).</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/aws-organizations-1@730w.webp 730w, /images/2020/04/aws-organizations-1@730w2x.webp 1460w, /images/2020/04/aws-organizations-1@610w.webp 610w, /images/2020/04/aws-organizations-1@610w2x.webp 1220w, /images/2020/04/aws-organizations-1@450w.webp 450w, /images/2020/04/aws-organizations-1@450w2x.webp 900w, /images/2020/04/aws-organizations-1@330w.webp 330w, /images/2020/04/aws-organizations-1@330w2x.webp 660w, /images/2020/04/aws-organizations-1@545w.webp 545w, /images/2020/04/aws-organizations-1@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/aws-organizations-1@730w.png 730w, /images/2020/04/aws-organizations-1@730w2x.png 1460w, /images/2020/04/aws-organizations-1@610w.png 610w, /images/2020/04/aws-organizations-1@610w2x.png 1220w, /images/2020/04/aws-organizations-1@450w.png 450w, /images/2020/04/aws-organizations-1@450w2x.png 900w, /images/2020/04/aws-organizations-1@330w.png 330w, /images/2020/04/aws-organizations-1@330w2x.png 660w, /images/2020/04/aws-organizations-1@545w.png 545w, /images/2020/04/aws-organizations-1@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/aws-organizations-1.png" alt="AWS Organizations 1.0" title="AWS Organizations 1.0"></picture></p><h3 id="2020-AWS-Organizations-2-0-services-are-operating-at-an-organization-level"><a href="#2020-AWS-Organizations-2-0-services-are-operating-at-an-organization-level" class="headerlink" title="2020: AWS Organizations 2.0: services are operating at an organization level"></a>2020: AWS Organizations 2.0: services are operating at an organization level</h3><p>AWS added more and more features allowing us to govern cloud infrastructures across AWS accounts centrally. A few examples:</p><ul><li>When creating an account via AWS Organizations, an IAM role granting administrator access to the root account (also called master or payer account) is added to the new account by default. Therefore, an administrator for the root account of your organization gets administrator access to all AWS accounts belonging to your organization as well.</li><li>AWS CloudFormation Stack Sets to deploy your Infrastructure as Code templates to multiple&#x2F;all AWS accounts within your organization.</li><li>The AWS Firewall Manager enables you to implement Web Application Firewall (WAF) rules for all Application Load Balancers and CloudFront distributions across multiple&#x2F;all AWS accounts belonging to your organization.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/aws-organizations-2@730w.webp 730w, /images/2020/04/aws-organizations-2@730w2x.webp 1460w, /images/2020/04/aws-organizations-2@610w.webp 610w, /images/2020/04/aws-organizations-2@610w2x.webp 1220w, /images/2020/04/aws-organizations-2@450w.webp 450w, /images/2020/04/aws-organizations-2@450w2x.webp 900w, /images/2020/04/aws-organizations-2@330w.webp 330w, /images/2020/04/aws-organizations-2@330w2x.webp 660w, /images/2020/04/aws-organizations-2@545w.webp 545w, /images/2020/04/aws-organizations-2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/aws-organizations-2@730w.png 730w, /images/2020/04/aws-organizations-2@730w2x.png 1460w, /images/2020/04/aws-organizations-2@610w.png 610w, /images/2020/04/aws-organizations-2@610w2x.png 1220w, /images/2020/04/aws-organizations-2@450w.png 450w, /images/2020/04/aws-organizations-2@450w2x.png 900w, /images/2020/04/aws-organizations-2@330w.png 330w, /images/2020/04/aws-organizations-2@330w2x.png 660w, /images/2020/04/aws-organizations-2@545w.png 545w, /images/2020/04/aws-organizations-2@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/aws-organizations-2.png" alt="AWS Organizations 2.0" title="AWS Organizations 2.0"></picture></p><p>The evolution from consolidated billing to AWS Organizations with services that are operating at an organization level sounds like a success story. But I have more and more reservations. Read on!</p><h2 id="The-truth-about-centralism"><a href="#The-truth-about-centralism" class="headerlink" title="The truth about centralism"></a>The truth about centralism</h2><p>It doesn’t work. Centralism causes more problems than it solves. Let me share some observations with you.</p><p>First observation: an enterprise decided to use a Service Control Policy (SCP) to only grant access to AWS services and features that had been approved by a security and governance board. It sounds like a good idea at the beginning. But the SCP causes significant problems:</p><ul><li>The engineers deploying their workloads to AWS do not know about the SCP and waste a lot of time to find out why configuring certain services, or features result in <code>Access Denied</code> errors.</li><li>The engineers need to ask for permission for every service or feature they want to test or use for production. That causes a dependency on the security and governance board that slows down the engineers.</li><li>The organization administrators have a hard time making any changes to the SCP. Each change to the SCP could potentially interrupt production workloads in all AWS accounts of the organization.</li></ul><p>In general, it is a great idea to make sure engineers think twice about the security, compliance, and cost implications of using new AWS services or features. However, solving that problem with centralism causes more problems than it solves.</p><p>Second observation: to increase the security of all the web applications within the company, a network security specialist has been instructed to roll out Web Application Firewall (WAF) rules for all public endpoints. The security expert was granted access to the root account of the AWS organization to do so. It sounds like a good idea at the beginning. But the centrally managed WAF rules cause major problems:</p><ul><li>Because the network security specialist is missing important information about the web applications, the newly deployed WAF rules cause several service interruptions due to mistakenly blocked requests.</li><li>On the one hand, the developers feel less responsible for securing their applications as they start relying on the centrally managed WAF. On the other hand, the network security specialist does not have enough insight into the application to implement WAF rules with high quality. As a result, the overall security level declines.</li><li>The developers have to ask for exceptions and changes to the WAF rules to make sure their applications are working correctly. Often, developers are cannot deploy changes to their applications as they have to wait for the network security specialist to implement changes to the WAF rules.</li></ul><p>Third observation: an enterprise owns hundreds of AWS accounts and added those accounts to a single AWS organization. As a side effect, all employees that have been granted administrator access to the root account of the organization are also able to assume an IAM role that grants them administrator access to every AWS account belonging to the organization.</p><ul><li>In theory, a single person could delete all the data of your organization. Including the backups.</li><li>In case an attacker takes over the computer of an organization administrator, the attacker has administrator access to all AWS accounts belonging to the organization. That would allow the attacker to steal sensitive data, intrusion into all systems, …</li></ul><p>And that’s only three of many observations from my day-to-day consulting work with companies all over the world.</p><h2 id="Decentralization-for-the-win"><a href="#Decentralization-for-the-win" class="headerlink" title="Decentralization for the win"></a>Decentralization for the win</h2><p>Where do we go from here? First of all, use AWS accounts to isolate workloads and environments from each other. There is no other way to do so 100%.</p><p>But, resist the temptation to manage all your AWS accounts with a single AWS organization (aka. root account).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/aws-organizations-old@730w.webp 730w, /images/2020/04/aws-organizations-old@730w2x.webp 1460w, /images/2020/04/aws-organizations-old@610w.webp 610w, /images/2020/04/aws-organizations-old@610w2x.webp 1220w, /images/2020/04/aws-organizations-old@450w.webp 450w, /images/2020/04/aws-organizations-old@450w2x.webp 900w, /images/2020/04/aws-organizations-old@330w.webp 330w, /images/2020/04/aws-organizations-old@330w2x.webp 660w, /images/2020/04/aws-organizations-old@545w.webp 545w, /images/2020/04/aws-organizations-old@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/aws-organizations-old@730w.png 730w, /images/2020/04/aws-organizations-old@730w2x.png 1460w, /images/2020/04/aws-organizations-old@610w.png 610w, /images/2020/04/aws-organizations-old@610w2x.png 1220w, /images/2020/04/aws-organizations-old@450w.png 450w, /images/2020/04/aws-organizations-old@450w2x.png 900w, /images/2020/04/aws-organizations-old@330w.png 330w, /images/2020/04/aws-organizations-old@330w2x.png 660w, /images/2020/04/aws-organizations-old@545w.png 545w, /images/2020/04/aws-organizations-old@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/aws-organizations-old.png" alt="Single AWS Organizations" title="Single AWS Organizations"></picture></p><p>Instead, divide your AWS accounts into smaller units. I’m thinking about 10 to 50 AWS accounts per unit. Create an AWS organization (aka. root account) for each of those units. Also, use AWS Organizations but for consolidated billing and account creation only. Do not use Service Control Policies (SCP) or any of the deeper integrations (called Trusted Organization Access).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/aws-organizations-new@730w.webp 730w, /images/2020/04/aws-organizations-new@730w2x.webp 1460w, /images/2020/04/aws-organizations-new@610w.webp 610w, /images/2020/04/aws-organizations-new@610w2x.webp 1220w, /images/2020/04/aws-organizations-new@450w.webp 450w, /images/2020/04/aws-organizations-new@450w2x.webp 900w, /images/2020/04/aws-organizations-new@330w.webp 330w, /images/2020/04/aws-organizations-new@330w2x.webp 660w, /images/2020/04/aws-organizations-new@545w.webp 545w, /images/2020/04/aws-organizations-new@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/aws-organizations-new@730w.png 730w, /images/2020/04/aws-organizations-new@730w2x.png 1460w, /images/2020/04/aws-organizations-new@610w.png 610w, /images/2020/04/aws-organizations-new@610w2x.png 1220w, /images/2020/04/aws-organizations-new@450w.png 450w, /images/2020/04/aws-organizations-new@450w2x.png 900w, /images/2020/04/aws-organizations-new@330w.png 330w, /images/2020/04/aws-organizations-new@330w2x.png 660w, /images/2020/04/aws-organizations-new@545w.png 545w, /images/2020/04/aws-organizations-new@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/aws-organizations-new.png" alt="Multiple AWS Organizations" title="Multiple AWS Organizations"></picture></p><p>At first sight, you might argue that decentralization is expensive as you need to solve the same problems over and over again. I agree that decentralization adds extra work. However, I’m confident that it will pay off in the long run as you do avoid the obstacles described in my observations above.</p><p>Next, a few tips when going the decentralization path:</p><ul><li>Lead by example. Provide Infrastructure as Code templates that implement security best practices and are compliant with laws and regulations. Make sure you are collecting feedback and improve those templates continuously.</li><li>Provide engineers with training and tools to detect security and reliability issues as well as to optimize cloud spending.</li><li>Handing over control and responsibility for security to the smallest possible group - for example, a team of engineers. Make sure to support that team with everything they ask for to master this challenge.</li></ul><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Using multiple AWS accounts to isolate workloads has been a best practice, not only since AWS introduced consolidated billing in 2010. AWS made a huge step by introducing AWS Organizations in 2017 and has added more and more features on top of the formerly boundary of an AWS account. In my opinion, we have passed the sweet spot between centralism and isolated accounts. The possibilities powered by AWS Organizations ruin the concept of isolated accounts with limited blast radius.</p><p>I recommend, to manage no more than 50 AWS accounts per AWS organization. Use multiple AWS organizations instead. Also, think twice before using SCP or Trusted Organization Access, both features make centralism permanent. I haven’t seen a thriving, innovative, and centralized IT organization so far. Correct me if I’m wrong.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Top 14 Must-Haves for Your AWS Architecture Checklist</title>
      <link>https://cloudonaut.io/aws-architecture-checklist/</link>
      <description>
        <![CDATA[<p>After analyzing the requirements of your application, you came up with an AWS architecture. Good job! This blog post contains a checklist]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-architecture-checklist/</guid>
      <pubDate>Thu, 02 Apr 2020 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>After analyzing the requirements of your application, you came up with an AWS architecture. Good job! This blog post contains a checklist helping you to validate your architecture. Make sure that you haven’t forgotten any important aspects.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/checklist@730w.webp 730w, /images/2020/04/checklist@730w2x.webp 1460w, /images/2020/04/checklist@610w.webp 610w, /images/2020/04/checklist@610w2x.webp 1220w, /images/2020/04/checklist@450w.webp 450w, /images/2020/04/checklist@450w2x.webp 900w, /images/2020/04/checklist@330w.webp 330w, /images/2020/04/checklist@330w2x.webp 660w, /images/2020/04/checklist@545w.webp 545w, /images/2020/04/checklist@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/checklist@730w.jpg 730w, /images/2020/04/checklist@730w2x.jpg 1460w, /images/2020/04/checklist@610w.jpg 610w, /images/2020/04/checklist@610w2x.jpg 1220w, /images/2020/04/checklist@450w.jpg 450w, /images/2020/04/checklist@450w2x.jpg 900w, /images/2020/04/checklist@330w.jpg 330w, /images/2020/04/checklist@330w2x.jpg 660w, /images/2020/04/checklist@545w.jpg 545w, /images/2020/04/checklist@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/checklist.jpg" alt="Top 14 Must-Haves for Your AWS Architecture Checklist" title="Top 14 Must-Haves for Your AWS Architecture Checklist"></picture></p><blockquote><p>This is a cross-post from the <a href="https://blog.cloudcraft.co/" target="_blank" rel="noopener">Cloudcraft blog</a>.</p></blockquote><p>I’ve created an AWS diagram with Cloudcraft and will use the following architecture as an example when going through my AWS Architecture Checklist. The architecture shows a typical web application consisting of a layer for synchronous request processing and a layer for asynchronous job processing. My architecture is made up of the following components:</p><ul><li>Route 53 a globally distributed Domain Name System (DNS)</li><li>CloudFront a Content Delivery Network (CDN)</li><li>Simple Storage Service (S3) an object store</li><li>Application Load Balancer (ALB) distributing incoming HTTPS requests among a fleet of EC2 Instances</li><li>EC2 Instance a virtual machine</li><li>Auto Scaling Group manages a fleet of EC2 instances</li><li>Simple Queue Service (SQS) a message queue</li><li>Relational Database Service (RDS) Aurora a PostgreSQL-compatible database</li><li>Elastic File System (EFS) a network file system (NFS)</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/example-architecture@730w.webp 730w, /images/2020/04/example-architecture@730w2x.webp 1460w, /images/2020/04/example-architecture@610w.webp 610w, /images/2020/04/example-architecture@610w2x.webp 1220w, /images/2020/04/example-architecture@450w.webp 450w, /images/2020/04/example-architecture@450w2x.webp 900w, /images/2020/04/example-architecture@330w.webp 330w, /images/2020/04/example-architecture@330w2x.webp 660w, /images/2020/04/example-architecture@545w.webp 545w, /images/2020/04/example-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/example-architecture@730w.png 730w, /images/2020/04/example-architecture@730w2x.png 1460w, /images/2020/04/example-architecture@610w.png 610w, /images/2020/04/example-architecture@610w2x.png 1220w, /images/2020/04/example-architecture@450w.png 450w, /images/2020/04/example-architecture@450w2x.png 900w, /images/2020/04/example-architecture@330w.png 330w, /images/2020/04/example-architecture@330w2x.png 660w, /images/2020/04/example-architecture@545w.png 545w, /images/2020/04/example-architecture@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/example-architecture.png" alt="Example Architecture" title="Example Architecture"></picture></p><h2 id="✅-Multi-AZ"><a href="#✅-Multi-AZ" class="headerlink" title="✅ Multi-AZ"></a>✅ Multi-AZ</h2><p>Is every component distributed among at least two availability zones? AWS partitions their regions in so-called availability zones (AZ). An availability zone consists of one or multiple isolated data centers.</p><p>There are three types of AWS services:</p><p>Services that are operating among multiple AZs by default<br>Services that come with a built-in option for Multi-AZ deployments<br>Services that you need to deploy among multiple AZs yourself</p><p>There is no action needed for services that are operating among multiple AZs by default. However, make sure you have verified that information with the help of the AWS documentation.</p><p>Make sure you have specified Multi-AZ as a requirement for services that come with a built-in option for Multi-AZ deployments.</p><p>Double check whether your application is running on multiple EC2 instances. Make sure to distribute the EC2 instances among at least two AZs in parallel. Be aware that it might not be possible to operate a legacy application on more than one machine at the same time. In that case, you need to know that AWS does not even offer an SLA for EC2 instances running in a single AZ only. You have to find a way to recover operation in another AZ in case of a disaster on your own.</p><p>Back to my example, architecture.</p><ul><li><strong>Multi-AZ by default:</strong> Route 53, CloudFront, S3, SQS</li><li><strong>Multi-AZ optional:</strong> ALB, RDS Aurora, EFS, Auto Scaling</li><li><strong>Action required:</strong> EC2 Instances</li></ul><p>In my example, I’m using Auto Scaling Groups to make sure EC2 instances are distributed among two Availability Zones.</p><h2 id="✅-Multi-Region"><a href="#✅-Multi-Region" class="headerlink" title="✅ Multi-Region"></a>✅ Multi-Region</h2><p>Is it necessary to deploy your application to multiple regions? As discussed in the previous section, AWS partitions their regions into isolated availability zones. That’s one of the approaches to avoid failures to cause outages affecting the whole region.</p><p>Depending on your availability requirements, you might need to deploy your application among multiple regions. For example, use US East (Ohio) and US West (Oregon) to operate your application on the east and west coast in parallel.</p><p>However, availability comes with a cost. It is complicated and expensive to go to the Multi-Region path. Make sure that there is a real business case for the availability requirements. Until today, Multi-Region deployments are exceptions, not the norm.</p><p>In my example, I’m not making use of a 2nd region to improve availability.</p><h2 id="✅-Stateless-Server"><a href="#✅-Stateless-Server" class="headerlink" title="✅ Stateless Server"></a>✅ Stateless Server</h2><p>Is your application persisting data on the virtual machines - either in memory or on disk? Check all EC2 instances in your architecture. Aim to implement the concept of a stateless server. Instead of persisting data on a single EC2 instance, outsource storing data to the database (for example, RDS Aurora), an object store (e.g., S3), or an NFS filesystem (EFS for Linux or FSx for Windows File Server).</p><p>Doing so enables you to add or remove EC2 instances on demand, roll out new versions of your application without any service interruption, replace failed EC2 instances automatically without losing any data.</p><p>Back to the example. No data is stored on EC2 instances. Instead, the following services are used to persist data:</p><ul><li>S3 the object store</li><li>RDS Aurora a PostgreSQL database</li><li>EFS a network file system (NFS) or FSx for Windows File Server</li></ul><h2 id="✅-Auto-Recovery"><a href="#✅-Auto-Recovery" class="headerlink" title="✅ Auto Recovery"></a>✅ Auto Recovery</h2><p>Do all components recover from failure automatically? Many AWS services do recover from failure without human interaction:</p><p>The Application Load Balancer uses a DNS name to be able to shift away from an AZ affected by an outage.<br>RDS Aurora will failover to a replica instance automatically within minutes after the primary instance becomes unavailable.<br>S3 is fault-tolerant by design and will route incoming requests to healthy nodes automatically.</p><p>It is important to note that EC2 does not recover from failure automatically. By default, a failed virtual machine is broken until you fix the issue manually. However, there are two options to recover failed instances automatically:</p><p>An Auto Scaling Group uses built-in EC2 health checks or the health check status from a linked load balancer to detect failed instances, terminates failed instances, and replaces them by launching a new instance.<br>EC2 Auto Recovery allows you to recover a single EC2 instance automatically. The built-in EC2 health checks are used to detect failures.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/auto-recovery@730w.webp 730w, /images/2020/04/auto-recovery@730w2x.webp 1460w, /images/2020/04/auto-recovery@610w.webp 610w, /images/2020/04/auto-recovery@610w2x.webp 1220w, /images/2020/04/auto-recovery@450w.webp 450w, /images/2020/04/auto-recovery@450w2x.webp 900w, /images/2020/04/auto-recovery@330w.webp 330w, /images/2020/04/auto-recovery@330w2x.webp 660w, /images/2020/04/auto-recovery@545w.webp 545w, /images/2020/04/auto-recovery@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/auto-recovery@730w.png 730w, /images/2020/04/auto-recovery@730w2x.png 1460w, /images/2020/04/auto-recovery@610w.png 610w, /images/2020/04/auto-recovery@610w2x.png 1220w, /images/2020/04/auto-recovery@450w.png 450w, /images/2020/04/auto-recovery@450w2x.png 900w, /images/2020/04/auto-recovery@330w.png 330w, /images/2020/04/auto-recovery@330w2x.png 660w, /images/2020/04/auto-recovery@545w.png 545w, /images/2020/04/auto-recovery@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/auto-recovery.png" alt="Auto Recovery" title="Auto Recovery"></picture></p><p>In my architecture, the Auto Scaling Groups (ASG) will recover failed instances automatically. In one case, the health check of the Application Load Balancer (ALB) is used to detect failed instances. In the other case, the ASG uses the built-in EC2 health check to detect failures.</p><h2 id="✅-Decouple-Client-Server-Communication"><a href="#✅-Decouple-Client-Server-Communication" class="headerlink" title="✅ Decouple Client-Server-Communication"></a>✅ Decouple Client-Server-Communication</h2><p>Are clients communicating with EC2 instances directly? Try to decouple the client from the server by introducing a load balancer or a queue. Doing is a requirement to scale on-demand and replace EC2 instances on the fly.</p><p>AWS provides three types of load balancers:</p><ul><li><strong>Classic Load Balancer (CLB)</strong> a legacy load balancer that you should not use for new architectures anymore.</li><li><strong>Application Load Balancer (ALB)</strong> a layer-7 load balancer that you should use for HTTP&#x2F;HTTPS based communication.</li><li><strong>Network Load Balancer (NLB)</strong> a load balancer that you should use for all communication that does not use HTTP&#x2F;HTTPS.</li></ul><p>A load balancer is an excellent option to decouple your client-server-communication. Especially if the client expects an immediate response to each request.</p><p>However, use asynchronous decoupling whenever possible. AWS offers a bunch of messaging services that you can use to do so:</p><ul><li>Simple Queue Service (SQS) a highly distributed message queue </li><li>Kinesis an event stream for ordered messaging</li><li>Amazon MQ provides managed Apache ActiveMQ message brokers</li><li>Amazon Managed Streaming for Apache Kafka (Amazon MSK)</li></ul><p>Back to my example. I’m using an Application Load Balancer to decouple the HTTPS communication between client and server. The browser does not need to know which server is available to answer requests. Instead, the browser sends requests to the ALB, which is responsible for forwarding the request to a healthy server. For asynchronous requests, I’m using SQS in my example. The web servers add jobs to a queue that workers are picking up and processing asynchronously.</p><h2 id="✅-Auto-Scaling"><a href="#✅-Auto-Scaling" class="headerlink" title="✅ Auto Scaling"></a>✅ Auto Scaling</h2><p>The promise of a public cloud is that we have to spend less on idle resources. As capacity is available on-demand, all you need to do is to scale your infrastructure automatically. Doing so is especially important when making use of EC2 instances.</p><p>In that case, make sure your architecture leverages Auto Scaling Groups to adjust the number of running EC2 instances. There are two options to do so:</p><ul><li>Based on a schedule.</li><li>Based on a utilization metric.</li></ul><p>In general, scaling based on a schedule is the more straightforward option. For example, you could set the number of EC2 instances to 4 during working hours and to 2 during the night and weekends. Scaling a fleet of EC2 instances is very simple when using SQS. In that case, use the number of messages in the queue to decide how many instances you need.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/auto-scaling@730w.webp 730w, /images/2020/04/auto-scaling@730w2x.webp 1460w, /images/2020/04/auto-scaling@610w.webp 610w, /images/2020/04/auto-scaling@610w2x.webp 1220w, /images/2020/04/auto-scaling@450w.webp 450w, /images/2020/04/auto-scaling@450w2x.webp 900w, /images/2020/04/auto-scaling@330w.webp 330w, /images/2020/04/auto-scaling@330w2x.webp 660w, /images/2020/04/auto-scaling@545w.webp 545w, /images/2020/04/auto-scaling@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/auto-scaling@730w.png 730w, /images/2020/04/auto-scaling@730w2x.png 1460w, /images/2020/04/auto-scaling@610w.png 610w, /images/2020/04/auto-scaling@610w2x.png 1220w, /images/2020/04/auto-scaling@450w.png 450w, /images/2020/04/auto-scaling@450w2x.png 900w, /images/2020/04/auto-scaling@330w.png 330w, /images/2020/04/auto-scaling@330w2x.png 660w, /images/2020/04/auto-scaling@545w.png 545w, /images/2020/04/auto-scaling@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/auto-scaling.png" alt="Auto Scaling" title="Auto Scaling"></picture></p><p>A side note: in some cases (e.g., development or test environments), it might be possible to scale down to 0, which means to shut down your application completely.</p><p>In my example, I’m using Auto Scaling Groups to adjust the number of EC2 instances automatically.</p><h2 id="✅-Backup-and-Restore"><a href="#✅-Backup-and-Restore" class="headerlink" title="✅ Backup and Restore"></a>✅ Backup and Restore</h2><p>Have you thought about a way to backup all persistent data? I recommend using AWS Backup to create snapshots of your data periodically. Currently, AWS Backup supports the following services:</p><ul><li>EC2 Instances</li><li>EBS Volumes</li><li>RDS</li><li>DynamoDB</li><li>EFS</li><li>AWS Storage Gateway</li></ul><p>Make sure the RPO (Recovery Point Objective) is defined, and the AWS Backup plans are configured accordingly.</p><p>In my example architecture, I’m using AWS Backup to create snapshots of the RDS Aurora database and the EFS file system.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/efs-rds-backup@730w.webp 730w, /images/2020/04/efs-rds-backup@730w2x.webp 1460w, /images/2020/04/efs-rds-backup@610w.webp 610w, /images/2020/04/efs-rds-backup@610w2x.webp 1220w, /images/2020/04/efs-rds-backup@450w.webp 450w, /images/2020/04/efs-rds-backup@450w2x.webp 900w, /images/2020/04/efs-rds-backup@330w.webp 330w, /images/2020/04/efs-rds-backup@330w2x.webp 660w, /images/2020/04/efs-rds-backup@545w.webp 545w, /images/2020/04/efs-rds-backup@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/efs-rds-backup@730w.png 730w, /images/2020/04/efs-rds-backup@730w2x.png 1460w, /images/2020/04/efs-rds-backup@610w.png 610w, /images/2020/04/efs-rds-backup@610w2x.png 1220w, /images/2020/04/efs-rds-backup@450w.png 450w, /images/2020/04/efs-rds-backup@450w2x.png 900w, /images/2020/04/efs-rds-backup@330w.png 330w, /images/2020/04/efs-rds-backup@330w2x.png 660w, /images/2020/04/efs-rds-backup@545w.png 545w, /images/2020/04/efs-rds-backup@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/efs-rds-backup.png" alt="Backing up EFS and RDS" title="Backing up EFS and RDS"></picture></p><p>If so, how long will it take to restore a backup? This question is critical to decide whether your architecture will be able to fulfill your RTO (Recovery Time Objective). Unfortunately, AWS typically does not offer any guarantees for the duration of a restore.</p><h2 id="✅-Encrypt-Data-In-Transit"><a href="#✅-Encrypt-Data-In-Transit" class="headerlink" title="✅ Encrypt Data-In-Transit"></a>✅ Encrypt Data-In-Transit</h2><p>Werner Vogels (CTO of Amazon) is quoted with: “Dance like nobody’s watching. Encrypt like everyone is.” You should take this advice to heart: encrypt all data in transit. No matter if the data is traversing the public Internet or just your private network (VPC).</p><p>Use the Amazon Certificate Manager (ACM) to manage TLS&#x2F;SSL certificates and make sure you have specified HTTPS&#x2F;TLS connections for incoming traffic to your load balancers only.</p><p>Further, you should make use of the TLS&#x2F;SSL encrypted endpoints that database services like RDS are offering. The communication with AWS API’s (e.g., S3 or SQS) uses HTTPS and is encrypted by default.</p><p>Back to my example, all external and internal communication is either HTTPS or using an encrypted channel (RDS).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/encryption-in-transit@730w.webp 730w, /images/2020/04/encryption-in-transit@730w2x.webp 1460w, /images/2020/04/encryption-in-transit@610w.webp 610w, /images/2020/04/encryption-in-transit@610w2x.webp 1220w, /images/2020/04/encryption-in-transit@450w.webp 450w, /images/2020/04/encryption-in-transit@450w2x.webp 900w, /images/2020/04/encryption-in-transit@330w.webp 330w, /images/2020/04/encryption-in-transit@330w2x.webp 660w, /images/2020/04/encryption-in-transit@545w.webp 545w, /images/2020/04/encryption-in-transit@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/encryption-in-transit@730w.png 730w, /images/2020/04/encryption-in-transit@730w2x.png 1460w, /images/2020/04/encryption-in-transit@610w.png 610w, /images/2020/04/encryption-in-transit@610w2x.png 1220w, /images/2020/04/encryption-in-transit@450w.png 450w, /images/2020/04/encryption-in-transit@450w2x.png 900w, /images/2020/04/encryption-in-transit@330w.png 330w, /images/2020/04/encryption-in-transit@330w2x.png 660w, /images/2020/04/encryption-in-transit@545w.png 545w, /images/2020/04/encryption-in-transit@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/encryption-in-transit.png" alt="Encrypt Data-In-Transit" title="Encrypt Data-In-Transit"></picture></p><h2 id="✅-Encrypt-Data-At-Rest"><a href="#✅-Encrypt-Data-At-Rest" class="headerlink" title="✅ Encrypt Data-At-Rest"></a>✅ Encrypt Data-At-Rest</h2><p>Furthermore, you should verify that all components also encrypt data-at-rest. Most AWS services allow you to specify a key managed by the AWS Key Management Service (KMS) to encrypt and decrypt your data.</p><p>Your architecture should specify the requirements for the KMS encryption keys.</p><p>AWS managed CMKs (customer master key) are easy to use but do not allow fine granular access control.<br>Customer managed CMKs are more complicated to use but allow you to control access and use keys among multiple AWS accounts.<br>Use key material generated by AWS or bring your key material.</p><p>Depending on your requirements, it might be necessary to use AWS CloudHSM to protect your master keys.</p><p>In my example architecture, I’m using KSM with AWS managed CMKs to encrypt data-at-rest stored in RDS and EFS. S3 contains publicly available files only. Therefore I’m not encrypting that data at all.</p><h2 id="✅-Minimize-Network-I-O"><a href="#✅-Minimize-Network-I-O" class="headerlink" title="✅ Minimize Network I&#x2F;O"></a>✅ Minimize Network I&#x2F;O</h2><p>AWS charges for network traffic. First of all, you have to pay for traffic from AWS to the Internet. On top of that, you are also paying for traffic between availability zones.</p><p>Therefore, one goal for your architecture is to minimize network I&#x2F;O. Do so by compressing data, reduce communication overhead, disable cross-region load balancing, for example.</p><h2 id="✅-Maximum-I-O-Throughput"><a href="#✅-Maximum-I-O-Throughput" class="headerlink" title="✅ Maximum I&#x2F;O Throughput"></a>✅ Maximum I&#x2F;O Throughput</h2><p>Verify the maximum I&#x2F;O throughput for every network connection in your architecture. Handle this checklist item with care. I’ve seen too many architectures failing in production because of insufficient I&#x2F;O throughput.</p><p>The EC2 instance type defines the maximum network throughput between the ALB and your EC2 instance.<br>The EC2 instance type defines the maximum network throughput between the EC2 instance and SQS&#x2F;RDS&#x2F;EFS.<br>The RDS instance type defines the maximum network throughput between the RDS instance and the EC2 instances.</p><p>Check out <a href="https://ec2instances.info/" target="_blank" rel="noopener">ec2instances.info</a> or the EC2 Network Performance Cheat Sheet to learn more about the maximum network throughput of your EC2 and RDS instances.</p><p>On top of that, there is also a hidden network connection between your EC2 instance and the EBS volume. The maximum I&#x2F;O throughput to disk is also limited by the configuration of the EBS volume as well as the maximum network throughput from the EC2 instance to the EBS volume. Some instance types come with a dedicated network connection for EBS volumes as well. Head over to the AWS documentation to learn more: Amazon EBS Volume Types and Amazon EBS–Optimized Instances.</p><h2 id="✅-Caching"><a href="#✅-Caching" class="headerlink" title="✅ Caching"></a>✅ Caching</h2><p>Have you thought about adding caching layers to reduce the number of read requests? Introducing caching decreases the load on all underlying parts of your architecture. But caching comes with a downside, you have to find a way to invalidate the cache.</p><p>AWS offers a few services that allow you to introduce caching to your architecture:</p><p>CloudFront, the content delivery network, caches responses to HTTP requests.<br>ElastiCache provides in-memory databases (Redis or memcached) that you can integrate into your applications, for example, to cache responses from the database or to store pre-calculated results.<br>DynamoDB DAX is a caching layer for Amazon’s NoSQL database.</p><p>In my example, I’m using CloudFront to cache the static contents (e.g., JavaScript and CSS files) of the web application.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/cloudfront-static-content@730w.webp 730w, /images/2020/04/cloudfront-static-content@730w2x.webp 1460w, /images/2020/04/cloudfront-static-content@610w.webp 610w, /images/2020/04/cloudfront-static-content@610w2x.webp 1220w, /images/2020/04/cloudfront-static-content@450w.webp 450w, /images/2020/04/cloudfront-static-content@450w2x.webp 900w, /images/2020/04/cloudfront-static-content@330w.webp 330w, /images/2020/04/cloudfront-static-content@330w2x.webp 660w, /images/2020/04/cloudfront-static-content@545w.webp 545w, /images/2020/04/cloudfront-static-content@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/cloudfront-static-content@730w.png 730w, /images/2020/04/cloudfront-static-content@730w2x.png 1460w, /images/2020/04/cloudfront-static-content@610w.png 610w, /images/2020/04/cloudfront-static-content@610w2x.png 1220w, /images/2020/04/cloudfront-static-content@450w.png 450w, /images/2020/04/cloudfront-static-content@450w2x.png 900w, /images/2020/04/cloudfront-static-content@330w.png 330w, /images/2020/04/cloudfront-static-content@330w2x.png 660w, /images/2020/04/cloudfront-static-content@545w.png 545w, /images/2020/04/cloudfront-static-content@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/cloudfront-static-content.png" alt="CloudFront" title="CloudFront"></picture></p><h2 id="✅-Protect-Network-Boundaries"><a href="#✅-Protect-Network-Boundaries" class="headerlink" title="✅ Protect Network Boundaries"></a>✅ Protect Network Boundaries</h2><p>From a network security perspective, it is crucial to reduce the attack surface from the Internet to a minimum. Go through your architecture and make sure you have specified network access only for a minimum of endpoints.</p><p>Your network architecture should consider that as well. It is typical to divide your VPC into public and private subnets. EC2 Instances and database services should be placed into a private subnet to avoid incoming traffic from the Internet at all.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/04/vpc@730w.webp 730w, /images/2020/04/vpc@730w2x.webp 1460w, /images/2020/04/vpc@610w.webp 610w, /images/2020/04/vpc@610w2x.webp 1220w, /images/2020/04/vpc@450w.webp 450w, /images/2020/04/vpc@450w2x.webp 900w, /images/2020/04/vpc@330w.webp 330w, /images/2020/04/vpc@330w2x.webp 660w, /images/2020/04/vpc@545w.webp 545w, /images/2020/04/vpc@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/04/vpc@730w.png 730w, /images/2020/04/vpc@730w2x.png 1460w, /images/2020/04/vpc@610w.png 610w, /images/2020/04/vpc@610w2x.png 1220w, /images/2020/04/vpc@450w.png 450w, /images/2020/04/vpc@450w2x.png 900w, /images/2020/04/vpc@330w.png 330w, /images/2020/04/vpc@330w2x.png 660w, /images/2020/04/vpc@545w.png 545w, /images/2020/04/vpc@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/04/vpc.png" alt="Virtual Private Cloud (VPC)" title="Virtual Private Cloud (VPC)"></picture></p><p>In my example, the only public endpoints are CloudFront and the Application Load Balancer. The Application Load Balancer is deployed to the public subnets.</p><h2 id="✅-Least-Privilege-Principle-for-IAM"><a href="#✅-Least-Privilege-Principle-for-IAM" class="headerlink" title="✅ Least Privilege Principle for IAM"></a>✅ Least Privilege Principle for IAM</h2><p>Identity and Access Management (IAM) controls who can access or administer your cloud resources. Make sure your architecture follows the Least Privilege Principle when it comes to accessing AWS APIs.</p><p>That is easier said than done. Your architecture does not need to go into every detail here. But I suggest that you specify that EC2 instances, ECS tasks, and Lambda functions should make use of IAM roles to authenticate for AWS API calls.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>My AWS Architecture Checklist summarizes best practices that I have learned the hard way when drafting and implementing architectures for AWS. The checklist covers the basics and helps you not to lose sight of a single critical point.</p><p>I’m closing with a template of my AWS Architecture Checklist for you to fill out.</p><p>◻️ Multi-AZ<br>◻️ Multi-Region<br>◻️ Stateless Server<br>◻️ Auto Recovery<br>◻️ Decouple Client-Server-Communication<br>◻️ Auto Scaling<br>◻️ Backup and Restore<br>◻️ Encrypt Data-In-Transit<br>◻️ Encrypt Data-At-Rest<br>◻️ Minimize Network I&#x2F;O<br>◻️ Maximum I&#x2F;O Throughput<br>◻️ Caching<br>◻️ Protect Network Boundaries<br>◻️ Least Privilege Principle for IAM</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>
        <![CDATA[CloudWatch Metrics & Alarms reloaded]]>
      </title>
      <link>https://cloudonaut.io/cloudwatch-metrics-and-alarms-reloaded/</link>
      <description>
        <![CDATA[<p>Amazon CloudWatch improved significantly over the years. It’s time to look at its monitoring capabilities again. CloudWatch is an excelle]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <guid isPermaLink="true">https://cloudonaut.io/cloudwatch-metrics-and-alarms-reloaded/</guid>
      <pubDate>Thu, 26 Mar 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Amazon CloudWatch improved significantly over the years. It’s time to look at its monitoring capabilities again. CloudWatch is an excellent starting point to implement enhanced monitoring on AWS. In this blog post, I demonstrate what you can do with CloudWatch metrics and alarms. <strong>Metrics</strong> provide a time-series database for telemetry (e.g., CPU utilization of an EC2 instance). <strong>Alarms</strong> watch a metric and trigger actions if a threshold is reached. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/cloudwatch-alarms-reloaded@730w.webp 730w, /images/2020/03/cloudwatch-alarms-reloaded@730w2x.webp 1460w, /images/2020/03/cloudwatch-alarms-reloaded@610w.webp 610w, /images/2020/03/cloudwatch-alarms-reloaded@610w2x.webp 1220w, /images/2020/03/cloudwatch-alarms-reloaded@450w.webp 450w, /images/2020/03/cloudwatch-alarms-reloaded@450w2x.webp 900w, /images/2020/03/cloudwatch-alarms-reloaded@330w.webp 330w, /images/2020/03/cloudwatch-alarms-reloaded@330w2x.webp 660w, /images/2020/03/cloudwatch-alarms-reloaded@545w.webp 545w, /images/2020/03/cloudwatch-alarms-reloaded@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/cloudwatch-alarms-reloaded@730w.jpg 730w, /images/2020/03/cloudwatch-alarms-reloaded@730w2x.jpg 1460w, /images/2020/03/cloudwatch-alarms-reloaded@610w.jpg 610w, /images/2020/03/cloudwatch-alarms-reloaded@610w2x.jpg 1220w, /images/2020/03/cloudwatch-alarms-reloaded@450w.jpg 450w, /images/2020/03/cloudwatch-alarms-reloaded@450w2x.jpg 900w, /images/2020/03/cloudwatch-alarms-reloaded@330w.jpg 330w, /images/2020/03/cloudwatch-alarms-reloaded@330w2x.jpg 660w, /images/2020/03/cloudwatch-alarms-reloaded@545w.jpg 545w, /images/2020/03/cloudwatch-alarms-reloaded@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/cloudwatch-alarms-reloaded.jpg" alt="CloudWatch Metrics &amp; Alarms reloaded" title="CloudWatch Metrics &amp; Alarms reloaded"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/16-cloudwatch-metrics-and-alarms-reloaded/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="Metrics"><a href="#Metrics" class="headerlink" title="Metrics"></a>Metrics</h2><p>A <strong>metric</strong> stores your telemetry data for 🆕 15 months. The highest resolution is 🆕 one second. The resolution of the data is automatically reduced over time. The following figure demonstrates how the resolution is reduced over time.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/cloudwatch-metric-aging@730w.webp 730w, /images/2020/03/cloudwatch-metric-aging@730w2x.webp 1460w, /images/2020/03/cloudwatch-metric-aging@610w.webp 610w, /images/2020/03/cloudwatch-metric-aging@610w2x.webp 1220w, /images/2020/03/cloudwatch-metric-aging@450w.webp 450w, /images/2020/03/cloudwatch-metric-aging@450w2x.webp 900w, /images/2020/03/cloudwatch-metric-aging@330w.webp 330w, /images/2020/03/cloudwatch-metric-aging@330w2x.webp 660w, /images/2020/03/cloudwatch-metric-aging@545w.webp 545w, /images/2020/03/cloudwatch-metric-aging@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/cloudwatch-metric-aging@730w.png 730w, /images/2020/03/cloudwatch-metric-aging@730w2x.png 1460w, /images/2020/03/cloudwatch-metric-aging@610w.png 610w, /images/2020/03/cloudwatch-metric-aging@610w2x.png 1220w, /images/2020/03/cloudwatch-metric-aging@450w.png 450w, /images/2020/03/cloudwatch-metric-aging@450w2x.png 900w, /images/2020/03/cloudwatch-metric-aging@330w.png 330w, /images/2020/03/cloudwatch-metric-aging@330w2x.png 660w, /images/2020/03/cloudwatch-metric-aging@545w.png 545w, /images/2020/03/cloudwatch-metric-aging@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/cloudwatch-metric-aging.png" alt="CloudWatch Metric aging" title="CloudWatch Metric aging"></picture></p><p>Let me give you an example: If you have data points with a 1-minute resolution that become older than 15 days, CloudWatch will combine 5 data points into one as the following tables show:</p><table class="table table-striped table-responsive"><thead><tr><th>Time</th><th style="text-align:right">Value</th></tr></thead><tbody><tr><td>08:09</td><td style="text-align:right">81</td></tr><tr><td>08:08</td><td style="text-align:right">39</td></tr><tr><td>08:07</td><td style="text-align:right">32</td></tr><tr><td>08:06</td><td style="text-align:right">36</td></tr><tr><td>08:05</td><td style="text-align:right">31</td></tr></tbody></table><p>The result is no longer a table of raw values, instead; CloudWatch stores statistics to describe the values:</p><table class="table table-striped table-responsive"><thead><tr><th>Time</th><th style="text-align:right">Samples</th><th style="text-align:right">Min</th><th style="text-align:right">Max</th><th style="text-align:right">Sum</th></tr></thead><tbody><tr><td>08:09</td><td style="text-align:right">5</td><td style="text-align:right">31</td><td style="text-align:right">81</td><td style="text-align:right">219</td></tr></tbody></table><p>You can compute time window statistics (aka <strong>aggregations</strong>) over your metric data. The following functions are supported: average, minimum, maximum, sum, and 🆕 percentiles. Let me give you an example. The following data is stored in CloudWatch.</p><table class="table table-striped table-responsive"><thead><tr><th>Time</th><th style="text-align:right">Value</th></tr></thead><tbody><tr><td>10:17</td><td style="text-align:right">84</td></tr><tr><td>10:16</td><td style="text-align:right">73</td></tr><tr><td>10:15</td><td style="text-align:right">90</td></tr><tr><td>10:14</td><td style="text-align:right">85</td></tr><tr><td>10:13</td><td style="text-align:right">74</td></tr><tr><td>10:12</td><td style="text-align:right">83</td></tr><tr><td>10:11</td><td style="text-align:right">45</td></tr><tr><td>10:10</td><td style="text-align:right">65</td></tr><tr><td>10:09</td><td style="text-align:right">81</td></tr><tr><td>10:08</td><td style="text-align:right">39</td></tr><tr><td>10:07</td><td style="text-align:right">32</td></tr><tr><td>10:06</td><td style="text-align:right">36</td></tr><tr><td>10:05</td><td style="text-align:right">31</td></tr><tr><td>10:04</td><td style="text-align:right">45</td></tr><tr><td>10:03</td><td style="text-align:right">56</td></tr><tr><td>10:02</td><td style="text-align:right">71</td></tr></tbody></table><p>Let me give you an aggregation example: for all values between 10:05 (inclusive) and 10:15 (exclusive), look at 5 minute time windows (aka <strong>period</strong>), and compute the maximum. </p><table class="table table-striped table-responsive"><thead><tr><th>Time Window</th><th>Values</th></tr></thead><tbody><tr><td>[10:10, 10:15[</td><td>65, 45, 83, 74, 85</td></tr><tr><td>[10:05, 10:10[</td><td>31, 36, 32, 39, 81</td></tr></tbody></table><p>The result will look like this:</p><table class="table table-striped table-responsive"><thead><tr><th>Time Window</th><th style="text-align:right">Maximum</th></tr></thead><tbody><tr><td>[10:10, 10:15[</td><td style="text-align:right">85</td></tr><tr><td>[10:05, 10:10[</td><td style="text-align:right">81</td></tr></tbody></table><p>🆕 <strong>Metric Math</strong> allows you to combine multiple metrics. For example, we have covered the following use cases in our blog already:</p><ul><li><a href="/monitoring-ec2-network-utilization/">Monitoring EC2 Network Utilization</a></li><li><a href="/combine-cloudwatch-metrics-autoscaling-reduce-costs/">Combine CloudWatch metrics for Auto Scaling or to reduce costs</a></li></ul><p>Most AWS Services (e.g., EC2, RDS, and many more) report telemetry data to CloudWatch Metrics out of the box. On top of that, you can also send your own data (aka <strong>custom metric</strong>).</p><p>Last but not least, creating dashboards that show multiple metrics in one place is a handy feature. The following figure shows a CloudWatch dashboard of our product <a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV - Antivirus for Amazon S3</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/cloudwatch-dashboard-s3-virussscan@730w.webp 730w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@730w2x.webp 1460w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@610w.webp 610w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@610w2x.webp 1220w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@450w.webp 450w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@450w2x.webp 900w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@330w.webp 330w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@330w2x.webp 660w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@545w.webp 545w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/cloudwatch-dashboard-s3-virussscan@730w.png 730w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@730w2x.png 1460w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@610w.png 610w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@610w2x.png 1220w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@450w.png 450w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@450w2x.png 900w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@330w.png 330w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@330w2x.png 660w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@545w.png 545w, /images/2020/03/cloudwatch-dashboard-s3-virussscan@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/cloudwatch-dashboard-s3-virussscan.png" alt="bucketAV Dashboard" title="bucketAV Dashboard"></picture></p><h2 id="Alarms"><a href="#Alarms" class="headerlink" title="Alarms"></a>Alarms</h2><p>A <strong>metric alarm</strong> (previously just alarm) continually runs an aggregation over the latest period(s) and checks the result against a <strong>threshold</strong>. A 🆕 <strong>composite alarm</strong> continually checks other alarms.</p><blockquote><p>Composite alarms are charged twice. You pay for the metric alarms and the composite alarms. You likely don’t need composite alarms, use metric math instead!</p></blockquote><p>An alarm can be in three states:</p><ul><li><code>OK</code>: Threshold is not reached</li><li><code>ALARM</code>: Threshold is reached</li><li><code>INSUFFICIENT_DATA</code>: No data available</li></ul><p>Whenever the state of an alarm changes, it triggers an action:</p><ul><li>Send a message to SNS</li><li>Execute an EC2 auto-scaling action</li><li>Execute an EC2 recovery action</li></ul><p>🆕 You can configure how an alarm deals with missing data:</p><ul><li>ignore it altogether</li><li>treat it as <code>OK</code></li><li>treat it as <code>ALARM</code></li><li>old behavior: go to state <code>INSUFFICIENT_DATA</code></li></ul><p>A simple alarm could be:</p><p>If the average CPU utilization over the last 5-minutes period is greater than 80, then send a message to an SNS topic.</p><table class="table table-striped table-responsive"><thead><tr><th>placeholder</th><th>value</th></tr></thead><tbody><tr><td>statistic</td><td>average</td></tr><tr><td>metric</td><td>CPU utilization</td></tr><tr><td>period</td><td>5 minutes</td></tr><tr><td>comparator</td><td>greater than</td></tr><tr><td>threshold</td><td>80</td></tr><tr><td>action</td><td>send a message to an SNS topic</td></tr></tbody></table><p>Formula: <code>If the $statistic $metric over the last $period period is $comparator $threshold, then $action.</code></p><p>To make alarms more stable, you can also look at the last N periods instead of only the last period:</p><p>If the average CPU utilization over the last 3 5-minutes periods is greater than 80, then send a message to an SNS topic.</p><table class="table table-striped table-responsive"><thead><tr><th>placeholder</th><th>value</th></tr></thead><tbody><tr><td>statistic</td><td>average</td></tr><tr><td>metric</td><td>CPU utilization</td></tr><tr><td>period</td><td>5 minutes</td></tr><tr><td>evaluation-periods</td><td>3</td></tr><tr><td>comparator</td><td>greater than</td></tr><tr><td>threshold</td><td>80</td></tr><tr><td>action</td><td>send a message to an SNS topic</td></tr></tbody></table><p>Formula: <code>If the $statistic $metric over the last $evaluation-periods $period periods is $comparator $threshold, then $action.</code></p><p>Less prone to short spikes is the usage of 🆕 M out of N logic:</p><p>If the average CPU utilization over the last 4 5-minutes periods is greater than 80 for at least 2 times, then send a message to an SNS topic.</p><table class="table table-striped table-responsive"><thead><tr><th>placeholder</th><th>value</th></tr></thead><tbody><tr><td>statistic</td><td>average</td></tr><tr><td>metric</td><td>CPU utilization</td></tr><tr><td>period</td><td>5 minutes</td></tr><tr><td>evaluation-periods</td><td>4</td></tr><tr><td>datapoints-to-alarm</td><td>2</td></tr><tr><td>comparator</td><td>greater than</td></tr><tr><td>threshold</td><td>80</td></tr><tr><td>action</td><td>send a message to an SNS topic</td></tr></tbody></table><p>Formula: <code>If the $statistic $metric over the last $evaluation-periods $period periods is $comparator $threshold for at least $datapoints-to-alarm times, then $action.</code></p><p>One problem with the approach so far is that you have to set the threshold. With 🆕 <strong>anomaly detection</strong>, CloudWatch will train a model to predict the threshold based on the past. Anomaly detection will recognize trends of the past 2 weeks. It is aware of hourly, daily, and weekly patterns. Keep in mind that anomaly detection only compares the present with the past two weeks. If your latency is worse every evening, that will just look fine for anomaly detection!</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>CloudWatch Metrics and Alarms are getting more powerful every year. Data can now be tracked at a 1-second resolution and is stored for 15 months. The new percentiles statistics is an excellent fit to monitor latencies. Metric math provides rich capabilities to work with multiple metrics at once.</p><p>The new composite alarms group multiple alarms together (easy, but more expensive than metric math). With the M out of N logic, you can ensure that your alarms can better deal with short spikes. Finally, anomaly detection can replace a static threshold.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Seamless EC2 monitoring with the Unified CloudWatch Agent</title>
      <link>https://cloudonaut.io/seamless-ec2-monitoring-with-the-unified-cloudwatch-agent/</link>
      <description>
        <![CDATA[<p>Shipping logs and metrics from an EC2 instance to CloudWatch was painful in the past. By default, you only get metrics about CPU utilizat]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/seamless-ec2-monitoring-with-the-unified-cloudwatch-agent/</guid>
      <pubDate>Thu, 19 Mar 2020 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Shipping logs and metrics from an EC2 instance to CloudWatch was painful in the past. By default, you only get metrics about CPU utilization, disk and network IO. The missing pieces are metrics about memory and disk usage and logs. Plenty of different options are available to achieve the goal of collecting the missing information. You can use <a href="https://collectd.org/" target="_blank" rel="noopener">collectd</a> with a <a href="https://github.com/awslabs/collectd-cloudwatch" target="_blank" rel="noopener">plugin</a> to ship metrics. <a href="https://www.fluentd.org/" target="_blank" rel="noopener">fluentd</a> comes with plugins to send logs and metrics as well. On top of that, AWS provides or provided multiple agents to ship metrics and logs.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/ec2-monitoring@730w.webp 730w, /images/2020/03/ec2-monitoring@730w2x.webp 1460w, /images/2020/03/ec2-monitoring@610w.webp 610w, /images/2020/03/ec2-monitoring@610w2x.webp 1220w, /images/2020/03/ec2-monitoring@450w.webp 450w, /images/2020/03/ec2-monitoring@450w2x.webp 900w, /images/2020/03/ec2-monitoring@330w.webp 330w, /images/2020/03/ec2-monitoring@330w2x.webp 660w, /images/2020/03/ec2-monitoring@545w.webp 545w, /images/2020/03/ec2-monitoring@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/ec2-monitoring@730w.jpg 730w, /images/2020/03/ec2-monitoring@730w2x.jpg 1460w, /images/2020/03/ec2-monitoring@610w.jpg 610w, /images/2020/03/ec2-monitoring@610w2x.jpg 1220w, /images/2020/03/ec2-monitoring@450w.jpg 450w, /images/2020/03/ec2-monitoring@450w2x.jpg 900w, /images/2020/03/ec2-monitoring@330w.jpg 330w, /images/2020/03/ec2-monitoring@330w2x.jpg 660w, /images/2020/03/ec2-monitoring@545w.jpg 545w, /images/2020/03/ec2-monitoring@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/ec2-monitoring.jpg" alt="EC2 monitoring" title="EC2 monitoring"></picture></p><p>The good news is that AWS finally seems to settle on a single agent, called Unified CloudWatch Agent, which solves all problems. The Unified CloudWatch Agent ships logs and metrics to CloudWatch. The bad news is that the Unified CloudWatch Agent is not part of Amazon Linux 2 by default. (<a href="/aws-ssm-is-a-trojan-horse-fix-it-now/">Compare with the SSM agent that is installed and started by default, which might surprise you and your security team</a>). In this blog post, you learn how to install and configure the Unified CloudWatch Agent with Amazon Linux 2. Without forcing you to use SSM or any other non-standard Linux tools. As long as you can install RPMs, you are okay with any other Linux distribution too. </p><p>The agent creates additional metrics like memory and swap usage.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/cloudwatch-metrics@730w.webp 730w, /images/2020/03/cloudwatch-metrics@730w2x.webp 1460w, /images/2020/03/cloudwatch-metrics@610w.webp 610w, /images/2020/03/cloudwatch-metrics@610w2x.webp 1220w, /images/2020/03/cloudwatch-metrics@450w.webp 450w, /images/2020/03/cloudwatch-metrics@450w2x.webp 900w, /images/2020/03/cloudwatch-metrics@330w.webp 330w, /images/2020/03/cloudwatch-metrics@330w2x.webp 660w, /images/2020/03/cloudwatch-metrics@545w.webp 545w, /images/2020/03/cloudwatch-metrics@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/cloudwatch-metrics@730w.png 730w, /images/2020/03/cloudwatch-metrics@730w2x.png 1460w, /images/2020/03/cloudwatch-metrics@610w.png 610w, /images/2020/03/cloudwatch-metrics@610w2x.png 1220w, /images/2020/03/cloudwatch-metrics@450w.png 450w, /images/2020/03/cloudwatch-metrics@450w2x.png 900w, /images/2020/03/cloudwatch-metrics@330w.png 330w, /images/2020/03/cloudwatch-metrics@330w2x.png 660w, /images/2020/03/cloudwatch-metrics@545w.png 545w, /images/2020/03/cloudwatch-metrics@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/cloudwatch-metrics.png" alt="CloudWatch Metrics published by the Unified CloudWatch Agent" title="CloudWatch Metrics published by the Unified CloudWatch Agent"></picture></p><p>On top of that, the agent will also send events from log files to a log group. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/cloudwatch-logs@730w.webp 730w, /images/2020/03/cloudwatch-logs@730w2x.webp 1460w, /images/2020/03/cloudwatch-logs@610w.webp 610w, /images/2020/03/cloudwatch-logs@610w2x.webp 1220w, /images/2020/03/cloudwatch-logs@450w.webp 450w, /images/2020/03/cloudwatch-logs@450w2x.webp 900w, /images/2020/03/cloudwatch-logs@330w.webp 330w, /images/2020/03/cloudwatch-logs@330w2x.webp 660w, /images/2020/03/cloudwatch-logs@545w.webp 545w, /images/2020/03/cloudwatch-logs@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/cloudwatch-logs@730w.png 730w, /images/2020/03/cloudwatch-logs@730w2x.png 1460w, /images/2020/03/cloudwatch-logs@610w.png 610w, /images/2020/03/cloudwatch-logs@610w2x.png 1220w, /images/2020/03/cloudwatch-logs@450w.png 450w, /images/2020/03/cloudwatch-logs@450w2x.png 900w, /images/2020/03/cloudwatch-logs@330w.png 330w, /images/2020/03/cloudwatch-logs@330w2x.png 660w, /images/2020/03/cloudwatch-logs@545w.png 545w, /images/2020/03/cloudwatch-logs@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/cloudwatch-logs.png" alt="CloudWatch Logs published by the Unified CloudWatch Agent" title="CloudWatch Logs published by the Unified CloudWatch Agent"></picture></p><h2 id="Installing"><a href="#Installing" class="headerlink" title="Installing"></a>Installing</h2><p>Unfortunately, the Unified CloudWatch Agent is not part of the official package repositories included in Amazon Linux 2. Instead of a straightforward <code>yum install xyz</code>, execute the following commands.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">wget https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm</span><br><span class="line"><span class="built_in">sudo</span> rpm -U amazon-cloudwatch-agent.rpm</span><br></pre></td></tr></table></figure><p>I don’t recommend to install whatever the latest version is. Instead, you should pin the version like this:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">wget https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/1.237768.0/amazon-cloudwatch-agent.rpm</span><br><span class="line"><span class="built_in">sudo</span> rpm -U amazon-cloudwatch-agent.rpm</span><br></pre></td></tr></table></figure><p>You might ask yourself: where can I find the release notes? AWS provides us with <a href="https://s3.amazonaws.com/amazoncloudwatch-agent/info/latest/RELEASE_NOTES" target="_blank" rel="noopener">RELEASE_NOTES</a>, which feels a little bit outdated nowadays where most of us use GitHub where you can subscribe to new releases via RSS. But it works. Besides that, there is no way to subscribe to new releases anywhere. Interestingly, you can get the latest version from <code>https://s3.amazonaws.com/amazoncloudwatch-agent/info/latest/CWAGENT_VERSION</code>.</p><p>Unfortunately, the <code>RELEASE_NOTES</code> and the <code>CWAGENT_VERSION</code> are not in sync right now. The release notes list version <code>1.229195.0</code> as the latest release, which is not available for download. Instead, the <code>CWAGENT_VERSION</code> points to the newest version <code>1.237768.0</code>, which I can download 🤷. As I said, the Unified CloudWatch Agent needs more ❤️.</p><h2 id="Configuring"><a href="#Configuring" class="headerlink" title="Configuring"></a>Configuring</h2><p>The most important configuration file is <code>/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json</code>. The <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-Configuration-File-Details.html" target="_blank" rel="noopener">details are documented in depth</a>.</p><p>In the following, I will provide you a default configuration that works for Amazon Linux 2 and ships:</p><ul><li>logs from <code>/var/log/</code></li><li>memory and swap usage metrics</li><li>root volume (<code>/</code>) disk usage metric</li></ul><p>The log streams are automatically prefixed with the EC2 instance id, and the metrics are published with an <code>InstanceId</code> dimension as well.</p><p>Replace <code>LOG_GROUP_NAME</code> with the name of your CloudWatch log group and <code>CW_NAMESPACE</code> with the name-space you want to use for the custom metrics.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;logs&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;logs_collected&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;files&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;collect_list&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/amazon/amazon-cloudwatch-agent/amazon-cloudwatch-agent.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/amazon/amazon-cloudwatch-agent/amazon-cloudwatch-agent.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timestamp_format&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%Y-%m-%dT%H:%M:%S&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/amazon/amazon-cloudwatch-agent/configuration-validation.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/amazon/amazon-cloudwatch-agent/configuration-validation.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timestamp_format&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%Y/%m/%d %H:%M:%S&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/amazon/ssm/amazon-ssm-agent.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/amazon/ssm/amazon-ssm-agent.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;timestamp_format&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%Y-%m-%d %H:%M:%S&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/amazon/ssm/errors.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/amazon/ssm/errors.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timestamp_format&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%Y-%m-%d %H:%M:%S&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/audit/audit.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/audit/audit.log&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/boot.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/boot.log&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/cfn-hup.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/cfn-hup.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timestamp_format&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%Y-%m-%d %H:%M:%S&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/cfn-init-cmd.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/cfn-init-cmd.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timestamp_format&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%Y-%m-%d %H:%M:%S&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/cfn-init.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/cfn-init.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timestamp_format&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%Y-%m-%d %H:%M:%S&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/cfn-wire.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/cfn-wire.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timestamp_format&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%Y-%m-%d %H:%M:%S&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/cloud-init-output.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/cloud-init-output.log&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/cloud-init.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/cloud-init.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timestamp_format&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%b %d %H:%M:%S&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/cron&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/cron&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timestamp_format&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%b %-d %H:%M:%S&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/dmesg&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/dmesg&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/grubby_prune_debug&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/grubby_prune_debug&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/maillog&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/maillog&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timestamp_format&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%b %-d %H:%M:%S&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/messages&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/messages&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timestamp_format&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%b %-d %H:%M:%S&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/secure&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/secure&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timestamp_format&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%b %-d %H:%M:%S&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span></span><br><span class="line">           <span class="attr">&quot;log_group_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_LOG_GROUP_NAME&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;file_path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/var/log/yum.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;log_stream_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;instance_id&#125;/var/log/yum.log&quot;</span><span class="punctuation">,</span></span><br><span class="line">           <span class="attr">&quot;timestamp_format&quot;</span><span class="punctuation">:</span> <span class="string">&quot;%b %d %H:%M:%S&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">]</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;metrics&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;namespace&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_NAMESPACE&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;append_dimensions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;InstanceId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;aws:InstanceId&#125;&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;metrics_collected&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;mem&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;measurement&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">          <span class="string">&quot;mem_used_percent&quot;</span></span><br><span class="line">        <span class="punctuation">]</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;swap&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;measurement&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">          <span class="string">&quot;swap_used_percent&quot;</span></span><br><span class="line">        <span class="punctuation">]</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;disk&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;resources&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">          <span class="string">&quot;/&quot;</span></span><br><span class="line">        <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;measurement&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">          <span class="string">&quot;used_percent&quot;</span></span><br><span class="line">        <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;drop_device&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Do you want to use the configuration for instances running inside an Auto Scaling Group? In that scenario, you likely want to change the dimension used for metrics and publish the <code>AutoScalingGroupName</code> dimension:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;metrics&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;namespace&quot;</span>: <span class="string">&quot;CW_NAMESPACE&quot;</span>,</span><br><span class="line">    <span class="string">&quot;append_dimensions&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;AutoScalingGroupName&quot;</span>: <span class="string">&quot;<span class="variable">$&#123;aws:AutoScalingGroupName&#125;</span>&quot;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&quot;metrics_collected&quot;</span>: &#123;</span><br><span class="line">      [...]</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>It wouldn’t be AWS if you didn’t also have to grant IAM permissions, right?</p><h2 id="IAM-permissions"><a href="#IAM-permissions" class="headerlink" title="IAM permissions"></a>IAM permissions</h2><p>The following policy needs to be attached to the IAM role that is attached to the EC2 instance (via an Instance Profile). The policy grants permissions to publish CloudWatch metrics and logs and follows the least privilege principle.</p><p>Replace <code>CW_NAMESPACE</code> and <code>LOG_GROUP_ARN</code> with your values.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2012-10-17&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Statement&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cloudwatch:PutMetricData&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Condition&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;StringEquals&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;cloudwatch:namespace&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CW_NAMESPACE&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;logs:CreateLogGroup&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;logs:CreateLogStream&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;logs:PutLogEvents&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;logs:DescribeLogStreams&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;logs:DescribeLogGroups&quot;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;LOG_GROUP_ARN&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>If you are planning to use the configuration for instances running inside an Auto Scaling Group, you also need the following permission:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2012-10-17&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Statement&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ec2:DescribeTags&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="Starting-Stopping"><a href="#Starting-Stopping" class="headerlink" title="Starting &amp; Stopping"></a>Starting &amp; Stopping</h2><p>To start the agent, run:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">systemctl start amazon-cloudwatch-agent</span><br></pre></td></tr></table></figure><p>To stop the agent, run:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">systemctl stop amazon-cloudwatch-agent</span><br></pre></td></tr></table></figure><p>To start the agent when the system boots up, run:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">systemctl <span class="built_in">enable</span> amazon-cloudwatch-agent</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>The <strong>Unified CloudWatch Agent</strong> is an excellent option to ship logs and metrics from EC2 instances to CloudWatch. The Unified CloudWatch Agent docs seem to be heavily influenced by SSM marketing gurus. The obvious choice to install a tool on Linux seems to be: install package, modify configuration files, start with systemd. Instead, the docs mention all kinds of useful and not so useful helpers that hide the important stuff. I hope that the agent will be pre-installed on Amazon Linux 2 AMI one day (or at least available via package repositories).</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Advanced AWS Networking: Pitfalls That You Should Avoid</title>
      <link>https://cloudonaut.io/advanved-aws-networking-pitfalls-that-you-should-avoid/</link>
      <description>
        <![CDATA[<p>AWS offers shiny and powerful networking services. However, you should know about the pitfalls when designing advanced networking archite]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/advanved-aws-networking-pitfalls-that-you-should-avoid/</guid>
      <pubDate>Tue, 17 Mar 2020 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>AWS offers shiny and powerful networking services. However, you should know about the pitfalls when designing advanced networking architectures for AWS. I will share some pitfalls that came to my attention when consulting clients to get the most out of AWS.</p><p>You will learn how to answer the following questions:</p><ul><li>VPC Peering or Transit Gateway?</li><li>NAT Gateway or Public Subnet?</li><li>VPC Endpoints or NAT Gateway?</li><li>CloudFront or Akamai, Cloudflare, Fastly …?</li><li>Route 53 Resolver or Public Hosted Zone?</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/network@730w.webp 730w, /images/2020/03/network@730w2x.webp 1460w, /images/2020/03/network@610w.webp 610w, /images/2020/03/network@610w2x.webp 1220w, /images/2020/03/network@450w.webp 450w, /images/2020/03/network@450w2x.webp 900w, /images/2020/03/network@330w.webp 330w, /images/2020/03/network@330w2x.webp 660w, /images/2020/03/network@545w.webp 545w, /images/2020/03/network@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/network@730w.jpg 730w, /images/2020/03/network@730w2x.jpg 1460w, /images/2020/03/network@610w.jpg 610w, /images/2020/03/network@610w2x.jpg 1220w, /images/2020/03/network@450w.jpg 450w, /images/2020/03/network@450w2x.jpg 900w, /images/2020/03/network@330w.jpg 330w, /images/2020/03/network@330w2x.jpg 660w, /images/2020/03/network@545w.jpg 545w, /images/2020/03/network@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/network.jpg" alt="Network Trade Offs" title="Network Trade Offs"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/15-advanced-aws-networking/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>Let’s get started! </p><h2 id="VPC-Peering-or-Transit-Gateway"><a href="#VPC-Peering-or-Transit-Gateway" class="headerlink" title="VPC Peering or Transit Gateway?"></a>VPC Peering or Transit Gateway?</h2><p>There are two different approaches to connect multiple VPCs. One option is to use VPC Peering. Creating a peering connection is simple: the owner of VPC A creates a peering request, and the owner of VPC B accepts the peering request. After the virtual peering is in place, all you need to do is to update the routing tables.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/vpc_peering@730w.webp 730w, /images/2020/03/vpc_peering@730w2x.webp 1460w, /images/2020/03/vpc_peering@610w.webp 610w, /images/2020/03/vpc_peering@610w2x.webp 1220w, /images/2020/03/vpc_peering@450w.webp 450w, /images/2020/03/vpc_peering@450w2x.webp 900w, /images/2020/03/vpc_peering@330w.webp 330w, /images/2020/03/vpc_peering@330w2x.webp 660w, /images/2020/03/vpc_peering@545w.webp 545w, /images/2020/03/vpc_peering@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/vpc_peering@730w.png 730w, /images/2020/03/vpc_peering@730w2x.png 1460w, /images/2020/03/vpc_peering@610w.png 610w, /images/2020/03/vpc_peering@610w2x.png 1220w, /images/2020/03/vpc_peering@450w.png 450w, /images/2020/03/vpc_peering@450w2x.png 900w, /images/2020/03/vpc_peering@330w.png 330w, /images/2020/03/vpc_peering@330w2x.png 660w, /images/2020/03/vpc_peering@545w.png 545w, /images/2020/03/vpc_peering@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/vpc_peering.png" alt="VPC Peering" title="VPC Peering"></picture></p><p>However, you need to set up a VPC Peering between every VPC. Therefore, the number of VPC Peering grows exponentially with the number of VPCs that you need to connect.</p><p>Since 2018, there is a second option to enable communication between networks: AWS Transit Gateway. First and foremost, Transit Gateway acts as a gateway connecting up to 5.000 networks. After attaching a VPC to a Transit Gateway, you need to update the routing tables of your subnets as well. Additionally, defining custom route tables to configure the routing within the Transit Gateway is possible as well.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/vpc_transit_gateway@730w.webp 730w, /images/2020/03/vpc_transit_gateway@730w2x.webp 1460w, /images/2020/03/vpc_transit_gateway@610w.webp 610w, /images/2020/03/vpc_transit_gateway@610w2x.webp 1220w, /images/2020/03/vpc_transit_gateway@450w.webp 450w, /images/2020/03/vpc_transit_gateway@450w2x.webp 900w, /images/2020/03/vpc_transit_gateway@330w.webp 330w, /images/2020/03/vpc_transit_gateway@330w2x.webp 660w, /images/2020/03/vpc_transit_gateway@545w.webp 545w, /images/2020/03/vpc_transit_gateway@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/vpc_transit_gateway@730w.png 730w, /images/2020/03/vpc_transit_gateway@730w2x.png 1460w, /images/2020/03/vpc_transit_gateway@610w.png 610w, /images/2020/03/vpc_transit_gateway@610w2x.png 1220w, /images/2020/03/vpc_transit_gateway@450w.png 450w, /images/2020/03/vpc_transit_gateway@450w2x.png 900w, /images/2020/03/vpc_transit_gateway@330w.png 330w, /images/2020/03/vpc_transit_gateway@330w2x.png 660w, /images/2020/03/vpc_transit_gateway@545w.png 545w, /images/2020/03/vpc_transit_gateway@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/vpc_transit_gateway.png" alt="Transit Gateway" title="Transit Gateway"></picture></p><table class="table table-striped table-responsive"><thead><tr><th></th><th>VPC Peering</th><th>Transit Gateway</th></tr></thead><tbody><tr><td>Create a network mesh with minimal configuration effort.</td><td>❌</td><td>✅</td></tr><tr><td>Reuse the same VPN connection for multiple VPCs.</td><td>❌</td><td>✅</td></tr><tr><td>Full flexibility to configure routing between networks?</td><td>✅</td><td>✅</td></tr><tr><td>Bandwith Limitation</td><td>No</td><td>50 Gbps (burst)</td></tr><tr><td>Connect networks across AWS accounts?</td><td>✅</td><td>✅</td></tr><tr><td>Connect networks across AWS regions?</td><td>✅</td><td>✅</td></tr><tr><td>Baseline Costs (per connected VPC and month)</td><td>$0.00</td><td>$36.00</td></tr><tr><td>Traffic Costs (per GB)</td><td>$0.02</td><td>$0.02</td></tr></tbody></table><p>At first, it looks like choosing Transit Gateway over VPC Peering is always a good idea. But that’s no longer true when having a look at the pricing.</p><p>A pricing example:</p><ul><li>Connect 4 VPCs with each other</li><li>2 VPCs connected with the on-premises network via VPN</li><li>1,000 GB traffic between VPCs</li><li>500 GB outgoing traffic via VPN</li></ul><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">VPC Peering</th><th style="text-align:right">Transit Gateway</th></tr></thead><tbody><tr><td>VPC Peerings / TGW attachments</td><td style="text-align:right">$0.00</td><td style="text-align:right">$180.00</td></tr><tr><td>VPN connections</td><td style="text-align:right">$72.00</td><td style="text-align:right">$36.00</td></tr><tr><td>Traffic between VPCs</td><td style="text-align:right">$20.00</td><td style="text-align:right">$20.00</td></tr><tr><td>Traffic across VPN</td><td style="text-align:right">$45.00</td><td style="text-align:right">$45.00</td></tr><tr><td><strong>Total Monthly Costs</strong></td><td style="text-align:right"><strong>$137.00</strong></td><td style="text-align:right"><strong>$281.00</strong></td></tr></tbody></table><p>So using Transit Gateway doubles monthly costs for your networking infrastructure. Let’s have a look at the pricing details.</p><ul><li>Attaching a VPC to a Transit Gateway costs $36.00 per month.</li><li>A VPN connection costs $36.00 per month.</li><li>Traffic costs are the same for VPC Peering and Transit Gateway.</li></ul><p>The baseline costs for a Site-to-Site VPN connect are $36.00 per month. The same is valid for attaching a VPC to a Transit Gateway. As long as you don’t need more than one VPN connection per VPC, you are better off with VPC Peering from a pricing perspective. Keep in mind that I haven’t taken the complexity for managing VPN connections and providing the on-premises hardware into account here.</p><h2 id="NAT-Gateway-or-Public-Subnet"><a href="#NAT-Gateway-or-Public-Subnet" class="headerlink" title="NAT Gateway or Public Subnet?"></a>NAT Gateway or Public Subnet?</h2><p>AWS advocates to divide your VPC into public and private subnets. A public subnet comes with a route to the Internet Gateway. Therefore, a public subnet enables incoming and outgoing connections to the Internet. A private subnet is neither accessible from the Internet, nor is it possible to establish a connection to the Internet.</p><p>However, it is quite common that EC2 instances need to connect to a resource via the Internet. This is where the NAT Gateway comes in. The NAT Gateway enables outgoing Internet connectivity for a private subnet. It is important to note that you need to create a NAT Gateway for every Availability Zone that you have created private subnets to achieve high availability.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/vpc_nat_gateway@730w.webp 730w, /images/2020/03/vpc_nat_gateway@730w2x.webp 1460w, /images/2020/03/vpc_nat_gateway@610w.webp 610w, /images/2020/03/vpc_nat_gateway@610w2x.webp 1220w, /images/2020/03/vpc_nat_gateway@450w.webp 450w, /images/2020/03/vpc_nat_gateway@450w2x.webp 900w, /images/2020/03/vpc_nat_gateway@330w.webp 330w, /images/2020/03/vpc_nat_gateway@330w2x.webp 660w, /images/2020/03/vpc_nat_gateway@545w.webp 545w, /images/2020/03/vpc_nat_gateway@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/vpc_nat_gateway@730w.png 730w, /images/2020/03/vpc_nat_gateway@730w2x.png 1460w, /images/2020/03/vpc_nat_gateway@610w.png 610w, /images/2020/03/vpc_nat_gateway@610w2x.png 1220w, /images/2020/03/vpc_nat_gateway@450w.png 450w, /images/2020/03/vpc_nat_gateway@450w2x.png 900w, /images/2020/03/vpc_nat_gateway@330w.png 330w, /images/2020/03/vpc_nat_gateway@330w2x.png 660w, /images/2020/03/vpc_nat_gateway@545w.png 545w, /images/2020/03/vpc_nat_gateway@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/vpc_nat_gateway.png" alt="NAT Gateway" title="NAT Gateway"></picture></p><p>The described network architecture consisting of public subnets, private subnets, and NAT gateways works fine but comes with two downsides.</p><p>If keeping costs to a minimum is essential, the baseline costs of $32.00 per month per NAT Gateway could be a show stopper. When using three AZs, you will pay $96.00 per month for three NAT Gateways.</p><p>The NAT Gateway also increases costs for outbound traffic. You have to pay a premium of $0.045 per GB flowing from a private subnet to the Internet. That’s raising the costs for outgoing traffic by 50%.</p><p>Let’s have a look at a pricing example.</p><table class="table table-striped table-responsive"><thead><tr><th>Outgoing Traffic</th><th style="text-align:right">100 GB</th><th style="text-align:right">1,000 GB</th><th style="text-align:right">10,000 GB</th></tr></thead><tbody><tr><td>With NAT Gateway</td><td style="text-align:right">$13.50</td><td style="text-align:right">$135.00</td><td style="text-align:right">$1350.00</td></tr><tr><td>Without NAT Gateway</td><td style="text-align:right">$9.00</td><td style="text-align:right">$90.00</td><td style="text-align:right">$900.00</td></tr></tbody></table><p>In summary, following the standard network architecture on AWS can become quite costly.</p><p>Keep in mind that the NAT Gateway bandwidth can scale up to 45 Gbps. Check out our CloudFormation templates to learn how to <a href="https://github.com/widdix/aws-cf-templates/pull/411" target="_blank" rel="noopener">monitor this resource limit</a>!</p><p>What’s the alternative? Place your workload - most likely, your EC2 instances - into the public subnets. By doing so, the NAT Gateways are no longer needed. Compared to the example with NAT Gateway from above, you will save $141.00 per month.</p><p>Keep in mind that your EC2 instances are now reachable from the Internet via their public IP addresses. Make sure you are blocking unwanted incoming traffic by making use of Security Groups and Network Access Control Lists.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/vpc_public_subnet@730w.webp 730w, /images/2020/03/vpc_public_subnet@730w2x.webp 1460w, /images/2020/03/vpc_public_subnet@610w.webp 610w, /images/2020/03/vpc_public_subnet@610w2x.webp 1220w, /images/2020/03/vpc_public_subnet@450w.webp 450w, /images/2020/03/vpc_public_subnet@450w2x.webp 900w, /images/2020/03/vpc_public_subnet@330w.webp 330w, /images/2020/03/vpc_public_subnet@330w2x.webp 660w, /images/2020/03/vpc_public_subnet@545w.webp 545w, /images/2020/03/vpc_public_subnet@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/vpc_public_subnet@730w.png 730w, /images/2020/03/vpc_public_subnet@730w2x.png 1460w, /images/2020/03/vpc_public_subnet@610w.png 610w, /images/2020/03/vpc_public_subnet@610w2x.png 1220w, /images/2020/03/vpc_public_subnet@450w.png 450w, /images/2020/03/vpc_public_subnet@450w2x.png 900w, /images/2020/03/vpc_public_subnet@330w.png 330w, /images/2020/03/vpc_public_subnet@330w2x.png 660w, /images/2020/03/vpc_public_subnet@545w.png 545w, /images/2020/03/vpc_public_subnet@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/vpc_public_subnet.png" alt="Public Subnet" title="Public Subnet"></picture></p><p>When designing the network architecture, keep the costs for the NAT Gateways in mind. I have to mention at well, that deviation from the standard can cause troubles. For example, an external auditor is most likely not happy with placing an EC2 instance into a public subnet.</p><h2 id="VPC-Endpoints-or-NAT-Gateway"><a href="#VPC-Endpoints-or-NAT-Gateway" class="headerlink" title="VPC Endpoints or NAT Gateway?"></a>VPC Endpoints or NAT Gateway?</h2><p>AWS services like EC2, RDS, and ElastiCache come with an Elastic Network Interface (ENI), which enables communication from within your VPCs. However, many AWS services provide a REST API, available via the Internet only. A few examples: S3, DynamoDB, CloudWatch, SQS, and Kinesis.</p><p>There are three options to make these services accessible from private subnets:</p><ul><li>A <strong>Gateway Endpoints</strong> is free of charge, but are only available for S3 and DynamoDB.</li><li>An <strong>Interface Endpoint</strong> costs $7.20 per month and AZ plus $0.01 per GB and is available for most AWS services.</li><li>A <strong>NAT Gateway</strong> can be used to access AWS services or any other services with a public API. Costs are $32.40 per month and AZ plus $0.045 per GB.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/vpc_endpoints@730w.webp 730w, /images/2020/03/vpc_endpoints@730w2x.webp 1460w, /images/2020/03/vpc_endpoints@610w.webp 610w, /images/2020/03/vpc_endpoints@610w2x.webp 1220w, /images/2020/03/vpc_endpoints@450w.webp 450w, /images/2020/03/vpc_endpoints@450w2x.webp 900w, /images/2020/03/vpc_endpoints@330w.webp 330w, /images/2020/03/vpc_endpoints@330w2x.webp 660w, /images/2020/03/vpc_endpoints@545w.webp 545w, /images/2020/03/vpc_endpoints@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/vpc_endpoints@730w.png 730w, /images/2020/03/vpc_endpoints@730w2x.png 1460w, /images/2020/03/vpc_endpoints@610w.png 610w, /images/2020/03/vpc_endpoints@610w2x.png 1220w, /images/2020/03/vpc_endpoints@450w.png 450w, /images/2020/03/vpc_endpoints@450w2x.png 900w, /images/2020/03/vpc_endpoints@330w.png 330w, /images/2020/03/vpc_endpoints@330w2x.png 660w, /images/2020/03/vpc_endpoints@545w.png 545w, /images/2020/03/vpc_endpoints@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/vpc_endpoints.png" alt="VPC Endpoints" title="VPC Endpoints"></picture></p><p>Keep the following rules of thumb in mind when designing your network architecture.</p><ul><li>Adding Gateway Endpoints for S3 and DynamoDB should the default.</li><li>Do you need to access non-AWS resources via the Internet, add a NAT Gateway. Do the math if traffic to AWS services justifies additional Interface Endpoints.</li><li>Are you only accessing AWS services from the private subnets? No more than four different services? Use Interface Endpoints. Otherwise, do the math to calculate costs for Interface Endpoints and NAT Gateway.</li></ul><h2 id="CloudFront-or-Akamai-Cloudflare-Fastly-…"><a href="#CloudFront-or-Akamai-Cloudflare-Fastly-…" class="headerlink" title="CloudFront or Akamai, Cloudflare, Fastly …?"></a>CloudFront or Akamai, Cloudflare, Fastly …?</h2><p>Using a Content Delivery Network (CDN) to cache static content at edge locations close to your users improves the performance of your web application a lot. Read <a href="https://cloudonaut.io/how-we-run-our-blog-cloudfront-s3-hexo/">How we run our blog cloudonaut.io</a> to learn how we are making use of CloudFront, Amazon’s Content Delivery Network.</p><p>Besides CloudFront, there are other popular CDN providers out there: Akamai, Cloudflare, and Fastly to name a few. When choosing your CDN provider, you should take into consideration, that outbound traffic from AWS to the Internet is priced at roundabout $0.09 per GB. There is one exception to that rule: when using CloudFront, you are not paying for outbound traffic from the AWS region where you operate your infrastructure. You are only paying for the outbound traffic from CloudFront to the Internet, which is valid for other CDN providers as well.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/vpc_cloudfront_akamai@730w.webp 730w, /images/2020/03/vpc_cloudfront_akamai@730w2x.webp 1460w, /images/2020/03/vpc_cloudfront_akamai@610w.webp 610w, /images/2020/03/vpc_cloudfront_akamai@610w2x.webp 1220w, /images/2020/03/vpc_cloudfront_akamai@450w.webp 450w, /images/2020/03/vpc_cloudfront_akamai@450w2x.webp 900w, /images/2020/03/vpc_cloudfront_akamai@330w.webp 330w, /images/2020/03/vpc_cloudfront_akamai@330w2x.webp 660w, /images/2020/03/vpc_cloudfront_akamai@545w.webp 545w, /images/2020/03/vpc_cloudfront_akamai@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/vpc_cloudfront_akamai@730w.png 730w, /images/2020/03/vpc_cloudfront_akamai@730w2x.png 1460w, /images/2020/03/vpc_cloudfront_akamai@610w.png 610w, /images/2020/03/vpc_cloudfront_akamai@610w2x.png 1220w, /images/2020/03/vpc_cloudfront_akamai@450w.png 450w, /images/2020/03/vpc_cloudfront_akamai@450w2x.png 900w, /images/2020/03/vpc_cloudfront_akamai@330w.png 330w, /images/2020/03/vpc_cloudfront_akamai@330w2x.png 660w, /images/2020/03/vpc_cloudfront_akamai@545w.png 545w, /images/2020/03/vpc_cloudfront_akamai@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/vpc_cloudfront_akamai.png" alt="CloudFront or Akamai, Cloudflare, Fastly" title="CloudFront or Akamai, Cloudflare, Fastly"></picture></p><p>To put that in other words: when using another CDN provider than CloudFront, you are paying twice for outbound traffic. That’s not fair, but you have to deal with it.</p><h2 id="Route-53-Resolver-or-Public-Hosted-Zone"><a href="#Route-53-Resolver-or-Public-Hosted-Zone" class="headerlink" title="Route 53 Resolver or Public Hosted Zone?"></a>Route 53 Resolver or Public Hosted Zone?</h2><p>DNS is a crucial component of your network architecture.</p><p>To manage DNS names for your applications running on AWS with ease - for example, by using CloudFormation and Terraform - using a Private Hosted Zone is the right choice. But how do you enable machines from your Corporate Network to resolve records from the Private Hosted Zone? The simplest solution is to create a Route 53 Resolver Endpoint. The internal name server will forward the request for <code>aws.myintra.net</code> to the Inbound Endpoint. The Inbound Endpoint is capable of resolving names from the Private Hosted Zone.</p><p>Resolving names in the other direction is possible as well. To do so, you need to create Outbound Endpoints and define rules that forward queries to your internal name server.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/route53_endpoints@730w.webp 730w, /images/2020/03/route53_endpoints@730w2x.webp 1460w, /images/2020/03/route53_endpoints@610w.webp 610w, /images/2020/03/route53_endpoints@610w2x.webp 1220w, /images/2020/03/route53_endpoints@450w.webp 450w, /images/2020/03/route53_endpoints@450w2x.webp 900w, /images/2020/03/route53_endpoints@330w.webp 330w, /images/2020/03/route53_endpoints@330w2x.webp 660w, /images/2020/03/route53_endpoints@545w.webp 545w, /images/2020/03/route53_endpoints@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/route53_endpoints@730w.png 730w, /images/2020/03/route53_endpoints@730w2x.png 1460w, /images/2020/03/route53_endpoints@610w.png 610w, /images/2020/03/route53_endpoints@610w2x.png 1220w, /images/2020/03/route53_endpoints@450w.png 450w, /images/2020/03/route53_endpoints@450w2x.png 900w, /images/2020/03/route53_endpoints@330w.png 330w, /images/2020/03/route53_endpoints@330w2x.png 660w, /images/2020/03/route53_endpoints@545w.png 545w, /images/2020/03/route53_endpoints@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/route53_endpoints.png" alt="Route 53 Endpoints" title="Route 53 Endpoints"></picture></p><p>In general, Route 53 Resolvers are a perfect fit for hybrid scenarios. Besides some minor technical changeless, that I will not discuss in this article.</p><p>However, Route 53 Resolvers are adding high costs to your networking infrastructure. You will pay $180 per month for an inbound endpoint deployed into two availability zones. The same is true for outbound endpoints. So the baseline costs for your name servers will be $360 per month.</p><p>On top of that, you will pay for recursive DNS queries.</p><ul><li>$0.40 per million queries (first 1 Billion queries per month)</li><li>$0.20 per million queries (over 1 Billion queries per month)</li></ul><p>From my point of view, there are two ways to minimize costs caused by Route 53 Resolvers.</p><ol><li>Share outbound resolvers with all your VPCs in the same region, even across accounts. Read <a href="https://aws.amazon.com/blogs/security/simplify-dns-management-in-a-multiaccount-environment-with-route-53-resolver/" target="_blank" rel="noopener">Simplify DNS management in a multi-account environment with Route 53 Resolver</a> to learn more.</li><li>Use a Public Hosted Zone instead of a Private Hosted Zone, which makes it unnecessary to create an inbound resolver.</li></ol><p>Instead of using a private name server to manage your records for <code>aws.intra.net</code> use a Public Hosted Zone, which allows you to resolve DNS requires over public name servers. Both from your VPC as well as from your corporate network.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/route53_public_hosted_zone@730w.webp 730w, /images/2020/03/route53_public_hosted_zone@730w2x.webp 1460w, /images/2020/03/route53_public_hosted_zone@610w.webp 610w, /images/2020/03/route53_public_hosted_zone@610w2x.webp 1220w, /images/2020/03/route53_public_hosted_zone@450w.webp 450w, /images/2020/03/route53_public_hosted_zone@450w2x.webp 900w, /images/2020/03/route53_public_hosted_zone@330w.webp 330w, /images/2020/03/route53_public_hosted_zone@330w2x.webp 660w, /images/2020/03/route53_public_hosted_zone@545w.webp 545w, /images/2020/03/route53_public_hosted_zone@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/route53_public_hosted_zone@730w.png 730w, /images/2020/03/route53_public_hosted_zone@730w2x.png 1460w, /images/2020/03/route53_public_hosted_zone@610w.png 610w, /images/2020/03/route53_public_hosted_zone@610w2x.png 1220w, /images/2020/03/route53_public_hosted_zone@450w.png 450w, /images/2020/03/route53_public_hosted_zone@450w2x.png 900w, /images/2020/03/route53_public_hosted_zone@330w.png 330w, /images/2020/03/route53_public_hosted_zone@330w2x.png 660w, /images/2020/03/route53_public_hosted_zone@545w.png 545w, /images/2020/03/route53_public_hosted_zone@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/route53_public_hosted_zone.png" alt="Route 53 Public Hosted Zone" title="Route 53 Public Hosted Zone"></picture></p><p>Some might say, that publishing private IP addresses to a public zone is a no go. I understand the argument. But I’m not sure if the argument is still valid in 2020. For example, many AWS services publish private IP addresses to public zones by default (e.g., ALB, RDS, …) as well.</p><p>In any case, do the math when adding Route 53 Endpoints to your architecture and balance the pros and cons according to your scenario.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Remember the following pitfalls when designing your AWS network architecture.</p><ol><li>A Transit Gateway simplifies peering VPCs. However, there is a baseline costs of $36.00 per month for each VPC attached to the Transit Gateway.</li><li>Adding NAT Gateways to your architecture costs $96.00 per month for 3 availability zones. Also, the costs for outbound network traffic will increase by 50%. From a economical point of view it makes sense to place workloads with high outgoing network throughput into public subnets.</li><li>Always add VPC Endpoints for S3 and DynamoDB. But do the math, when adding VPC Endpoints for other AWS services to your network. Using a NAT Gateway might be cheaper.</li><li>When choosing a CDN provider besides CloudFront, make sure you have taken into account the additional costs for outbound traffic.</li><li>Share Route 53 Outbound Resolvers between VPCs and AWS accounts to reduce costs. Think about using Public Hosted Zones instead of paying $180.00 per month for an Route 53 Inbound Resolver.</li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to analyze and reduce S3 storage usage?</title>
      <link>https://cloudonaut.io/how-to-analyze-and-reduce-s3-storage-usage/</link>
      <description>
        <![CDATA[<p>S3 is an object store, not a file system. Object storage comes with significant advantages: unlimited storage capacity, high availability]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/costs/">costs</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-analyze-and-reduce-s3-storage-usage/</guid>
      <pubDate>Fri, 06 Mar 2020 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>S3 is an object store, not a file system. Object storage comes with significant advantages: unlimited storage capacity, high availability, and durability. However, there are some disadvantages too. For example, it is cumbersome to calculate the storage usage of a specific prefix (also known as a folder) in S3.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/hdd@730w.webp 730w, /images/2020/03/hdd@730w2x.webp 1460w, /images/2020/03/hdd@610w.webp 610w, /images/2020/03/hdd@610w2x.webp 1220w, /images/2020/03/hdd@450w.webp 450w, /images/2020/03/hdd@450w2x.webp 900w, /images/2020/03/hdd@330w.webp 330w, /images/2020/03/hdd@330w2x.webp 660w, /images/2020/03/hdd@545w.webp 545w, /images/2020/03/hdd@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/hdd@730w.jpg 730w, /images/2020/03/hdd@730w2x.jpg 1460w, /images/2020/03/hdd@610w.jpg 610w, /images/2020/03/hdd@610w2x.jpg 1220w, /images/2020/03/hdd@450w.jpg 450w, /images/2020/03/hdd@450w2x.jpg 900w, /images/2020/03/hdd@330w.jpg 330w, /images/2020/03/hdd@330w2x.jpg 660w, /images/2020/03/hdd@545w.jpg 545w, /images/2020/03/hdd@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/hdd.jpg" alt="How to analyze and reduce S3 storage usage?" title="How to analyze and reduce S3 storage usage?"></picture></p><p>For example, I’m using an S3 bucket to store personal data.</p><figure class="highlight 1c"><table><tr><td class="code"><pre><span class="line"><span class="string">|- backups/</span></span><br><span class="line"><span class="string">|- photos/</span></span><br><span class="line"><span class="string">|- videos/</span></span><br><span class="line"><span class="string">|- documents/</span></span><br><span class="line"><span class="string">|- songsheets/</span></span><br><span class="line"><span class="string">|- ebooks/</span></span><br></pre></td></tr></table></figure><p>The CloudWatch metric <code>BucketSizeBytes</code> shows the storage usage of each S3 bucket broken down by storage class. Currently, it takes about 460 GB to store my data. That’s $5.60 per month.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/screenshot_s3_cloudwatch_metric@730w.webp 730w, /images/2020/03/screenshot_s3_cloudwatch_metric@730w2x.webp 1460w, /images/2020/03/screenshot_s3_cloudwatch_metric@610w.webp 610w, /images/2020/03/screenshot_s3_cloudwatch_metric@610w2x.webp 1220w, /images/2020/03/screenshot_s3_cloudwatch_metric@450w.webp 450w, /images/2020/03/screenshot_s3_cloudwatch_metric@450w2x.webp 900w, /images/2020/03/screenshot_s3_cloudwatch_metric@330w.webp 330w, /images/2020/03/screenshot_s3_cloudwatch_metric@330w2x.webp 660w, /images/2020/03/screenshot_s3_cloudwatch_metric@545w.webp 545w, /images/2020/03/screenshot_s3_cloudwatch_metric@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/screenshot_s3_cloudwatch_metric@730w.png 730w, /images/2020/03/screenshot_s3_cloudwatch_metric@730w2x.png 1460w, /images/2020/03/screenshot_s3_cloudwatch_metric@610w.png 610w, /images/2020/03/screenshot_s3_cloudwatch_metric@610w2x.png 1220w, /images/2020/03/screenshot_s3_cloudwatch_metric@450w.png 450w, /images/2020/03/screenshot_s3_cloudwatch_metric@450w2x.png 900w, /images/2020/03/screenshot_s3_cloudwatch_metric@330w.png 330w, /images/2020/03/screenshot_s3_cloudwatch_metric@330w2x.png 660w, /images/2020/03/screenshot_s3_cloudwatch_metric@545w.png 545w, /images/2020/03/screenshot_s3_cloudwatch_metric@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/screenshot_s3_cloudwatch_metric.png" alt="CloudWatch Metric showing S3 Storage Usage" title="CloudWatch Metric showing S3 Storage Usage"></picture></p><p>To reduce costs, I’d like to delete unused data. But where to start looking for data that is no longer valuable to me? My bucket contains more than 100,000 objects. Therefore, I want to find the prefixes (also known as a folder) with the highest storage consumption.</p><p>Doing so isn’t easy. Neither the AWS Management Console nor the S3 API provides a way to fetch the storage usage per prefix. Remember, S3 is an object store, not a file system. In theory, you could list all objects in the bucket, retrieve the object size, and calculate the storage usage per prefix on your own. But doing so is cumbersome, slow, and costly.</p><p>Luckily, there is a better way to analyze S3 storage usage.</p><ul><li><strong>S3 Inventory</strong> provides CSV, ORC, or Parquet files listing all the objects stored within an S3 bucket on a daily or weekly basis.</li><li><strong>Athena</strong> queries CSV, ORC, or Parquet files and analyzes data on-the-fly.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/s3_inventory_athena@730w.webp 730w, /images/2020/03/s3_inventory_athena@730w2x.webp 1460w, /images/2020/03/s3_inventory_athena@610w.webp 610w, /images/2020/03/s3_inventory_athena@610w2x.webp 1220w, /images/2020/03/s3_inventory_athena@450w.webp 450w, /images/2020/03/s3_inventory_athena@450w2x.webp 900w, /images/2020/03/s3_inventory_athena@330w.webp 330w, /images/2020/03/s3_inventory_athena@330w2x.webp 660w, /images/2020/03/s3_inventory_athena@545w.webp 545w, /images/2020/03/s3_inventory_athena@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/s3_inventory_athena@730w.png 730w, /images/2020/03/s3_inventory_athena@730w2x.png 1460w, /images/2020/03/s3_inventory_athena@610w.png 610w, /images/2020/03/s3_inventory_athena@610w2x.png 1220w, /images/2020/03/s3_inventory_athena@450w.png 450w, /images/2020/03/s3_inventory_athena@450w2x.png 900w, /images/2020/03/s3_inventory_athena@330w.png 330w, /images/2020/03/s3_inventory_athena@330w2x.png 660w, /images/2020/03/s3_inventory_athena@545w.png 545w, /images/2020/03/s3_inventory_athena@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/s3_inventory_athena.png" alt="S3 Inventory + Athena" title="S3 Inventory + Athena"></picture></p><p>Next, you will learn how to enable S3 Inventory, set up Athena, and analyze storage usage with Athena.</p><h2 id="Enabling-S3-Inventory"><a href="#Enabling-S3-Inventory" class="headerlink" title="Enabling S3 Inventory"></a>Enabling S3 Inventory</h2><p>Go through the following steps to enable S3 Inventory for a bucket.</p><ol><li>Open the AWS Management Console.</li><li>Go to Simple Storage Service (S3).</li><li>Create a new bucket to store the inventory files.</li><li>Open the bucket you want to analyze and reduce S3 storage usage.</li><li>Switch to the <code>Management</code> tab.</li><li>Select <code>Inventory</code>.</li><li>Press the <code>Add new</code> button.</li><li>Fill out the details, as shown in the following screenshot.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/screenshot_s3_inventory@730w.webp 730w, /images/2020/03/screenshot_s3_inventory@730w2x.webp 1460w, /images/2020/03/screenshot_s3_inventory@610w.webp 610w, /images/2020/03/screenshot_s3_inventory@610w2x.webp 1220w, /images/2020/03/screenshot_s3_inventory@450w.webp 450w, /images/2020/03/screenshot_s3_inventory@450w2x.webp 900w, /images/2020/03/screenshot_s3_inventory@330w.webp 330w, /images/2020/03/screenshot_s3_inventory@330w2x.webp 660w, /images/2020/03/screenshot_s3_inventory@545w.webp 545w, /images/2020/03/screenshot_s3_inventory@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/screenshot_s3_inventory@730w.png 730w, /images/2020/03/screenshot_s3_inventory@730w2x.png 1460w, /images/2020/03/screenshot_s3_inventory@610w.png 610w, /images/2020/03/screenshot_s3_inventory@610w2x.png 1220w, /images/2020/03/screenshot_s3_inventory@450w.png 450w, /images/2020/03/screenshot_s3_inventory@450w2x.png 900w, /images/2020/03/screenshot_s3_inventory@330w.png 330w, /images/2020/03/screenshot_s3_inventory@330w2x.png 660w, /images/2020/03/screenshot_s3_inventory@545w.png 545w, /images/2020/03/screenshot_s3_inventory@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/screenshot_s3_inventory.png" alt="Configure S3 Inventory" title="Configure S3 Inventory"></picture></p><p>Please note, it will take up to 24 hours until the first inventory files will show up in the inventory bucket.</p><h2 id="Setting-up-Athena"><a href="#Setting-up-Athena" class="headerlink" title="Setting up Athena"></a>Setting up Athena</h2><p>To run SQL queries to analyze the inventory data, you need to create an Athena table first. Go to the Athena service in the AWS Management Console to do so.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/screenshot_athena_create_table@730w.webp 730w, /images/2020/03/screenshot_athena_create_table@730w2x.webp 1460w, /images/2020/03/screenshot_athena_create_table@610w.webp 610w, /images/2020/03/screenshot_athena_create_table@610w2x.webp 1220w, /images/2020/03/screenshot_athena_create_table@450w.webp 450w, /images/2020/03/screenshot_athena_create_table@450w2x.webp 900w, /images/2020/03/screenshot_athena_create_table@330w.webp 330w, /images/2020/03/screenshot_athena_create_table@330w2x.webp 660w, /images/2020/03/screenshot_athena_create_table@545w.webp 545w, /images/2020/03/screenshot_athena_create_table@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/screenshot_athena_create_table@730w.png 730w, /images/2020/03/screenshot_athena_create_table@730w2x.png 1460w, /images/2020/03/screenshot_athena_create_table@610w.png 610w, /images/2020/03/screenshot_athena_create_table@610w2x.png 1220w, /images/2020/03/screenshot_athena_create_table@450w.png 450w, /images/2020/03/screenshot_athena_create_table@450w2x.png 900w, /images/2020/03/screenshot_athena_create_table@330w.png 330w, /images/2020/03/screenshot_athena_create_table@330w2x.png 660w, /images/2020/03/screenshot_athena_create_table@545w.png 545w, /images/2020/03/screenshot_athena_create_table@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/screenshot_athena_create_table.png" alt="Athena: Create table" title="Athena: Create table"></picture></p><p>Next, execute the following query to create the <code>inventory</code> table. Replace <code>$InventoryBucket</code>, <code>$InventoryPrefix</code>, <code>$Bucket</code>, and <code>$InventoryName</code> with the configuration from the previous section.</p><figure class="highlight n1ql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> EXTERNAL TABLE inventory(</span><br><span class="line">  <span class="keyword">bucket</span> <span class="keyword">string</span>,</span><br><span class="line">  <span class="keyword">key</span> <span class="keyword">string</span>,</span><br><span class="line">  size bigint,</span><br><span class="line">  last_modified_date timestamp,</span><br><span class="line">  e_tag <span class="keyword">string</span>,</span><br><span class="line">  storage_class <span class="keyword">string</span>,</span><br><span class="line">  is_multipart_uploaded <span class="keyword">boolean</span>,</span><br><span class="line">  replication_status <span class="keyword">string</span>,</span><br><span class="line">  encryption_status <span class="keyword">string</span></span><br><span class="line">  )</span><br><span class="line">  PARTITIONED <span class="keyword">BY</span> (dt <span class="keyword">string</span>)</span><br><span class="line">  ROW FORMAT SERDE <span class="string">&#x27;org.apache.hadoop.hive.ql.io.orc.OrcSerde&#x27;</span></span><br><span class="line">  STORED <span class="keyword">AS</span> INPUTFORMAT <span class="string">&#x27;org.apache.hadoop.hive.ql.io.SymlinkTextInputFormat&#x27;</span></span><br><span class="line">  OUTPUTFORMAT  <span class="string">&#x27;org.apache.hadoop.hive.ql.io.IgnoreKeyTextOutputFormat&#x27;</span></span><br><span class="line">  LOCATION <span class="string">&#x27;s3://$InventoryBucket/$InventoryPrefix/$Bucket/$InventoryName/hive&#x27;</span>;</span><br></pre></td></tr></table></figure><p>Also, you need to make sure that the partitions of the table are updated. Do so by executing the following command.</p><figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">MSCK REPAIR TABLE inventory<span class="comment">;</span></span><br></pre></td></tr></table></figure><p>That’s it; you have configured Athena. You are ready to analyze the inventory data.</p><h2 id="Analyzing-storage-usage-with-Athena"><a href="#Analyzing-storage-usage-with-Athena" class="headerlink" title="Analyzing storage usage with Athena"></a>Analyzing storage usage with Athena</h2><p>Execute the following query to analyze the storage usage by the first part of the prefix. Replace <code>$Yesterday</code> with yesterday’s timestamp (e.g., <code>2020-03-03-00-00</code>).</p><figure class="highlight n1ql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> prefix, <span class="built_in">SUM</span>(size)/<span class="number">1000</span>/<span class="number">1000</span>/<span class="number">1000</span> <span class="keyword">AS</span> total_size <span class="keyword">FROM</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> regexp_extract(i.<span class="keyword">key</span>, <span class="string">&#x27;([^\/]*\/).*&#x27;</span>, <span class="number">1</span>) <span class="keyword">AS</span> prefix, i.size </span><br><span class="line">  <span class="keyword">FROM</span> inventory <span class="keyword">AS</span> i <span class="keyword">WHERE</span> i.dt = <span class="string">&#x27;$Yesterday&#x27;</span></span><br><span class="line">) <span class="keyword">GROUP</span> <span class="keyword">BY</span> prefix <span class="keyword">ORDER</span> <span class="keyword">BY</span> total_size <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure><p>In my case, the objects with prefix <code>backups/</code> use 454 GB of storage. So I know where to look for data that is no longer valuable to me to reduce storage costs.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/03/screenshot_athena_query_inventory@730w.webp 730w, /images/2020/03/screenshot_athena_query_inventory@730w2x.webp 1460w, /images/2020/03/screenshot_athena_query_inventory@610w.webp 610w, /images/2020/03/screenshot_athena_query_inventory@610w2x.webp 1220w, /images/2020/03/screenshot_athena_query_inventory@450w.webp 450w, /images/2020/03/screenshot_athena_query_inventory@450w2x.webp 900w, /images/2020/03/screenshot_athena_query_inventory@330w.webp 330w, /images/2020/03/screenshot_athena_query_inventory@330w2x.webp 660w, /images/2020/03/screenshot_athena_query_inventory@545w.webp 545w, /images/2020/03/screenshot_athena_query_inventory@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/03/screenshot_athena_query_inventory@730w.png 730w, /images/2020/03/screenshot_athena_query_inventory@730w2x.png 1460w, /images/2020/03/screenshot_athena_query_inventory@610w.png 610w, /images/2020/03/screenshot_athena_query_inventory@610w2x.png 1220w, /images/2020/03/screenshot_athena_query_inventory@450w.png 450w, /images/2020/03/screenshot_athena_query_inventory@450w2x.png 900w, /images/2020/03/screenshot_athena_query_inventory@330w.png 330w, /images/2020/03/screenshot_athena_query_inventory@330w2x.png 660w, /images/2020/03/screenshot_athena_query_inventory@545w.png 545w, /images/2020/03/screenshot_athena_query_inventory@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/03/screenshot_athena_query_inventory.png" alt="Athena: Query inventory" title="Athena: Query inventory"></picture></p><p>Please note, I have used a personal bucket with less than 500 GB as an example in this blog post. However, the shown concept works for buckets with vast amounts of objects and data as well.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>On the way to sustainable open source projects</title>
      <link>https://cloudonaut.io/on-the-way-to-sustainable-open-source-projects/</link>
      <description>
        <![CDATA[<p>Open Source software is the heart of the modern world. Why do we love open source? Maybe, because we can review the source code? Or becau]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/on-the-way-to-sustainable-open-source-projects/</guid>
      <pubDate>Thu, 27 Feb 2020 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Open Source software is the heart of the modern world. Why do we love open source? Maybe, because we can review the source code? Or because we can modify it? The honest answer is: because it is free. Which is contradictory in a capitalist world, isn’t it? Why do people waste energy to create something that has $0 value? Whatever the answer is, it doesn’t pay the rent. In the long run, open source maintainers have trouble to keep their projects healthy without sacrificing their health. As a two-person team, Andreas and I maintain a <a href="https://github.com/widdix" target="_blank" rel="noopener">bunch of AWS related projects</a> that a lot of people rely on. It gets harder and harder to maintain them all for Andreas and me.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/02/sustainable@730w.webp 730w, /images/2020/02/sustainable@730w2x.webp 1460w, /images/2020/02/sustainable@610w.webp 610w, /images/2020/02/sustainable@610w2x.webp 1220w, /images/2020/02/sustainable@450w.webp 450w, /images/2020/02/sustainable@450w2x.webp 900w, /images/2020/02/sustainable@330w.webp 330w, /images/2020/02/sustainable@330w2x.webp 660w, /images/2020/02/sustainable@545w.webp 545w, /images/2020/02/sustainable@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/02/sustainable@730w.jpg 730w, /images/2020/02/sustainable@730w2x.jpg 1460w, /images/2020/02/sustainable@610w.jpg 610w, /images/2020/02/sustainable@610w2x.jpg 1220w, /images/2020/02/sustainable@450w.jpg 450w, /images/2020/02/sustainable@450w2x.jpg 900w, /images/2020/02/sustainable@330w.jpg 330w, /images/2020/02/sustainable@330w2x.jpg 660w, /images/2020/02/sustainable@545w.jpg 545w, /images/2020/02/sustainable@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/02/sustainable.jpg" alt="On the way to sustainable open source projects" title="On the way to sustainable open source projects"></picture></p><p>Today, we have two important announcements to make:</p><h2 id="AWS-sponsors-credits"><a href="#AWS-sponsors-credits" class="headerlink" title="AWS sponsors credits"></a>AWS sponsors credits</h2><p>We are delighted to announce that AWS sponsors credits to run the weekly integration tests for our <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a> project.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/02/aws-open-source@730w.webp 730w, /images/2020/02/aws-open-source@730w2x.webp 1460w, /images/2020/02/aws-open-source@610w.webp 610w, /images/2020/02/aws-open-source@610w2x.webp 1220w, /images/2020/02/aws-open-source@450w.webp 450w, /images/2020/02/aws-open-source@450w2x.webp 900w, /images/2020/02/aws-open-source@330w.webp 330w, /images/2020/02/aws-open-source@330w2x.webp 660w, /images/2020/02/aws-open-source@545w.webp 545w, /images/2020/02/aws-open-source@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/02/aws-open-source@730w.png 730w, /images/2020/02/aws-open-source@730w2x.png 1460w, /images/2020/02/aws-open-source@610w.png 610w, /images/2020/02/aws-open-source@610w2x.png 1220w, /images/2020/02/aws-open-source@450w.png 450w, /images/2020/02/aws-open-source@450w2x.png 900w, /images/2020/02/aws-open-source@330w.png 330w, /images/2020/02/aws-open-source@330w2x.png 660w, /images/2020/02/aws-open-source@545w.png 545w, /images/2020/02/aws-open-source@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/02/aws-open-source.png" alt="AWS Open Source" title="AWS Open Source"></picture></p><p>With the help of AWS, we run tests in multiple regions (<code>us-east-1</code>, <code>ap-southeast-2</code>, and <code>eu-central-1</code>) to keep the quality of the templates high. <a href="https://aws.amazon.com/blogs/opensource/aws-promotional-credits-open-source-projects/" target="_blank" rel="noopener">Find out more about the program</a>.</p><h2 id="GitHub-Sponsors"><a href="#GitHub-Sponsors" class="headerlink" title="GitHub Sponsors"></a>GitHub Sponsors</h2><p>Funding developers and organizations through GitHub Sponsors is one more way to contribute to open source projects you appreciate. Help projects get the resources they need and recognize contributors working behind the scenes to make open source better for everyone. GitHub will not charge fees for GitHub Sponsors.</p><p>Today, we ask all of you to become our <a href="https://github.com/sponsors/widdix" target="_blank" rel="noopener">GitHub Sponsors</a>. With your help (small and big), we can keep our projects in good shape. Over time, we can make them even better. If you (or the company you work for) rely on our work, consider to:</p><p><a href="https://github.com/sponsors/widdix" class="btn btn-primary">Become a Sponsor!</a> </p>]]>
      </content:encoded>
    </item>
    <item>
      <title>What's the best AWS Compute option for your project?</title>
      <link>https://cloudonaut.io/whats-the-best-aws-compute-option-for-your-project/</link>
      <description>
        <![CDATA[<p>There are many good reasons why you should move to the cloud and AWS in particular. Benefit from the latest innovations or consume sophis]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/whats-the-best-aws-compute-option-for-your-project/</guid>
      <pubDate>Tue, 25 Feb 2020 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>There are many good reasons why you should move to the cloud and AWS in particular. Benefit from the latest innovations or consume sophisticated technology as a commodity (relational databases, Hadoop clusters, data warehouses, …) to lower your time to market. You can automate every single task to increase the quality of your operations. Pay as you go by default for maximum freedom with options for longer-term commitments for lower prices. An elastic world allows new services and processes that will transform the way you think IT. Last but not least, you get the same capabilities across the globe. The only downside: AWS offers so many choices and freedom! What’s the best option to run your applications?</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/02/decision-tree@730w.webp 730w, /images/2020/02/decision-tree@730w2x.webp 1460w, /images/2020/02/decision-tree@610w.webp 610w, /images/2020/02/decision-tree@610w2x.webp 1220w, /images/2020/02/decision-tree@450w.webp 450w, /images/2020/02/decision-tree@450w2x.webp 900w, /images/2020/02/decision-tree@330w.webp 330w, /images/2020/02/decision-tree@330w2x.webp 660w, /images/2020/02/decision-tree@545w.webp 545w, /images/2020/02/decision-tree@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/02/decision-tree@730w.jpg 730w, /images/2020/02/decision-tree@730w2x.jpg 1460w, /images/2020/02/decision-tree@610w.jpg 610w, /images/2020/02/decision-tree@610w2x.jpg 1220w, /images/2020/02/decision-tree@450w.jpg 450w, /images/2020/02/decision-tree@450w2x.jpg 900w, /images/2020/02/decision-tree@330w.jpg 330w, /images/2020/02/decision-tree@330w2x.jpg 660w, /images/2020/02/decision-tree@545w.jpg 545w, /images/2020/02/decision-tree@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/02/decision-tree.jpg" alt="Decision Tree" title="Decision Tree"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/14-whats-the-best-aws-compute-option-for-your-project/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>You can run your application on virtual machines using EC2. If you prefer containers, ECS Fargate is your choice. But you can also use the latest Serverless capabilities to run your application on Lambda. But what’s the difference?</p><h2 id="EC2-in-a-nutshell"><a href="#EC2-in-a-nutshell" class="headerlink" title="EC2 in a nutshell"></a>EC2 in a nutshell</h2><p><a href="https://aws.amazon.com/ec2/" target="_blank" rel="noopener">Amazon EC2</a> is the service to spin up (virtual) machines in minutes. Windows and Linux are supported. Up to 448 cores, 26 TB memory, and 100 Gbps network, GPUs, and massive amounts of storage. Billed per second (Linux only). You will not find a missing feature. EC2 is the most mature option and has you covered!</p><h2 id="Fargate-in-a-nutshell"><a href="#Fargate-in-a-nutshell" class="headerlink" title="Fargate in a nutshell"></a>Fargate in a nutshell</h2><p><a href="https://aws.amazon.com/fargate/" target="_blank" rel="noopener">AWS Fargate</a> runs your Docker containers. No need to install, configure, or patch a cluster. It feels like EC2, but you get lightweight containers. Up to 4 cores, and 30 GB memory. With flexible per second billing. Fargate is the easiest way to run Docker containers on AWS. You have to use either the ECS or Kubernetes (EKS) API to interact with Fargate.</p><h2 id="Lambda-in-a-nutshell"><a href="#Lambda-in-a-nutshell" class="headerlink" title="Lambda in a nutshell"></a>Lambda in a nutshell</h2><p><a href="https://aws.amazon.com/lambda/" target="_blank" rel="noopener">AWS Lambda</a> is the latest technology and evolves quickly. Upload your source code, and AWS runs it for you. Lambda is to most convenient way to execute source code on AWS. The convenience comes with limitations: You have to follow the event-driven programming paradigm, 15 minutes are all you get in a single execution, and the number of supported programming languages is limited.</p><p>With so many choices, you need guidance. That’s why I created a decision tree!</p><p>Download the free <a href="/download/files/aws-compute-decision-tree.pdf"><strong>AWS Compute Decision Tree</strong></a> (PDF format). I hope you find it useful!</p><p>The following list helps you to choose the best compute option for your project:</p><table class="table table-striped table-responsive"><thead><tr><th>Criteria</th><th>Possible Options</th></tr></thead><tbody><tr><td>Windows only?</td><td>EC2</td></tr><tr><td>&gt; 30 GB memory required</td><td>EC2</td></tr><tr><td>&gt; 4 cores required</td><td>EC2</td></tr><tr><td>&gt; 1 Gbits networking required</td><td>EC2</td></tr><tr><td>Requires GPUs</td><td>EC2</td></tr><tr><td>Stateful application</td><td>EC2</td></tr><tr><td>Shared file system</td><td>EC2</td></tr><tr><td>Many concurrent users</td><td>EC2</td></tr><tr><td>Risk-averse when using new technologies</td><td>EC2 or Fargate</td></tr><tr><td>Lift&amp;Shift project</td><td>EC2 or Fargate</td></tr><tr><td>New In-house development</td><td>Fargate or Lambda</td></tr><tr><td>Stateless application</td><td>Fargate or Lambda</td></tr><tr><td>Infrequently used</td><td>Lambda</td></tr></tbody></table><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>AWS offers many choices to run your application. As AWS users, we have to pick the option that is best for us. Lucky you, if your requirements map to a single choice (e.g., Windows workload &#x3D;&gt; EC2). Otherwise, choose the option that matches most often.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>GitHub Actions vs. AWS CodePipeline</title>
      <link>https://cloudonaut.io/github-actions-vs-aws-codepipeline/</link>
      <description>
        <![CDATA[<p>Deployment pipelines first! When launching a new project, start with building a deployment pipeline. Automating the process from changing]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/codepipeline/">codepipeline</category>
      <category domain="https://cloudonaut.io/tag/codebuild/">codebuild</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <category domain="https://cloudonaut.io/tag/github/">github</category>
      <guid isPermaLink="true">https://cloudonaut.io/github-actions-vs-aws-codepipeline/</guid>
      <pubDate>Fri, 21 Feb 2020 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Deployment pipelines first! When launching a new project, start with building a deployment pipeline. Automating the process from changing the source code to shipping your application to the production environment is a success factor.</p><p>But how to build a deployment pipeline? This post compares two different approaches: GitHub Actions and AWS CodePipeline.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/02/package@730w.webp 730w, /images/2020/02/package@730w2x.webp 1460w, /images/2020/02/package@610w.webp 610w, /images/2020/02/package@610w2x.webp 1220w, /images/2020/02/package@450w.webp 450w, /images/2020/02/package@450w2x.webp 900w, /images/2020/02/package@330w.webp 330w, /images/2020/02/package@330w2x.webp 660w, /images/2020/02/package@545w.webp 545w, /images/2020/02/package@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/02/package@730w.jpg 730w, /images/2020/02/package@730w2x.jpg 1460w, /images/2020/02/package@610w.jpg 610w, /images/2020/02/package@610w2x.jpg 1220w, /images/2020/02/package@450w.jpg 450w, /images/2020/02/package@450w2x.jpg 900w, /images/2020/02/package@330w.jpg 330w, /images/2020/02/package@330w2x.jpg 660w, /images/2020/02/package@545w.jpg 545w, /images/2020/02/package@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/02/package.jpg" alt="GitHub Actions vs. AWS CodePipeline" title="GitHub Actions vs. AWS CodePipeline"></picture></p><p>GitHub has been hosting source code for more than ten years. On top of that, GitHub announced their CI&#x2F;CD service called GitHub Actions to the public in November 2019.</p><p>AWS empowers developers with its continuous delivery service CodePipeline since July 2015. About a year later, AWS announced an essential add-on: CodeBuild. When I write about CodePipeline in the following, I always refer to a combination of CodePipeline and CodeBuild. So to be more accurate, the title of this blog post should be: GitHub Actions vs. CodePipeline and CodeBuild.</p><h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><p>Both GitHub Actions and AWS CodePipeline use similar concepts to provide a deployment pipeline:</p><ul><li><strong>Workflow Management:</strong> customize the deployment workflow to your needs by arranging actions in parallel or order. </li><li><strong>Isolated Job Execution:</strong> use pre-build or custom container images to provide an isolated environment for executing actions (e.g., a build job).</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/02/deployment-pipeline-overview@730w.webp 730w, /images/2020/02/deployment-pipeline-overview@730w2x.webp 1460w, /images/2020/02/deployment-pipeline-overview@610w.webp 610w, /images/2020/02/deployment-pipeline-overview@610w2x.webp 1220w, /images/2020/02/deployment-pipeline-overview@450w.webp 450w, /images/2020/02/deployment-pipeline-overview@450w2x.webp 900w, /images/2020/02/deployment-pipeline-overview@330w.webp 330w, /images/2020/02/deployment-pipeline-overview@330w2x.webp 660w, /images/2020/02/deployment-pipeline-overview@545w.webp 545w, /images/2020/02/deployment-pipeline-overview@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/02/deployment-pipeline-overview@730w.png 730w, /images/2020/02/deployment-pipeline-overview@730w2x.png 1460w, /images/2020/02/deployment-pipeline-overview@610w.png 610w, /images/2020/02/deployment-pipeline-overview@610w2x.png 1220w, /images/2020/02/deployment-pipeline-overview@450w.png 450w, /images/2020/02/deployment-pipeline-overview@450w2x.png 900w, /images/2020/02/deployment-pipeline-overview@330w.png 330w, /images/2020/02/deployment-pipeline-overview@330w2x.png 660w, /images/2020/02/deployment-pipeline-overview@545w.png 545w, /images/2020/02/deployment-pipeline-overview@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/02/deployment-pipeline-overview.png" alt="Deployment Pipeline: Overview" title="Deployment Pipeline: Overview"></picture></p><p>In summary, the main concepts are similar, but what are the differences?</p><h2 id="Comparison"><a href="#Comparison" class="headerlink" title="Comparison"></a>Comparison</h2><p>The following table compares GitHub Actions and AWS CodePipeline. To achieve better comparability, I examine the GitHub-hosted runner with 2 CPU and 7 GB memory with the CodeBuild compute type <code>general1.medium</code>, which comes with 4 CPU and 7 GB memory.</p><div class="table-responsive"><table class="table table-striped"><thead><tr><th></th><th>GitHub Actions</th><th>AWS CodePipeline</th></tr></thead><tbody><tr><td>Free Tier (Linux)</td><td><p>free for public repositories<br>GitHub Free: 2,000 build minutes per month<br>GitHub Pro: 3,000 build minutes<br>GitHub Team: 10,000 build minutes</p></td><td><p>50 minutes per month</p></td></tr><tr><td>Costs for Pipeline</td><td><p>free</p></td><td><p>$1.00 per month</p></td></tr><tr><td>Costs for Build Job</td><td><p>$0.008 per minute</p></td><td><p>$0.010 per minute</p></td></tr><tr><td>Environments</td><td><p>Linux, Windows, macOS</p></td><td><p>Linux, (Windows)</p></td></tr><tr><td>Source Code Repository</td><td><p>GitHub</p></td><td><p>GitHub, Bitbucket, AWS CodeCommit, Amazon S3</p></td></tr><tr><td>Deployment Target</td><td><p>Any environment, works best for public cloud providers.</p></td><td><p>Mainly AWS, in theory you could deploy to other cloud providers as well.</p></td></tr><tr><td>AWS Authentication</td><td><p>Store access keys of IAM user in GitHub Secrets</p></td><td><p>IAM role, no need to manage access keys</p></td></tr><tr><td>Developer Experience</td><td><p>🏖 Easy to use</p></td><td><p>🤷‍♀️ Difficult to get started</p></td></tr><tr><td>Integrations</td><td><p>Open marketplace providing all kinds of integrations of mixed quality.</p></td><td><p>Pre-built and high quality integrations into the AWS ecosystem.</p></td></tr><tr><td>Self-hosted Workers?</td><td><p>✅</p></td><td><p>⚠️ with custom actions and custom code</p></td></tr><tr><td>Serialize pipeline executions</td><td><p>✅</p></td><td><p>✅</p></td></tr><tr><td>Support for multiple branches?</td><td><p>✅</p></td><td><p>❌</p></td></tr><tr><td>Pipeline as Code</td><td><p>✅</p></td><td><p>⚠️ with custom CloudFormation or Terraform and CodeBuild action</p></td></tr></tbody></table></div><p>GitHub Actions offers an outstanding developer experience. As long as you host your source code on GitHub, the solution is flexible, not only because of the integrations (aka actions) offered in the open marketplace. AWS CodePipeline integrates very well into the AWS ecosystem. Being able to use IAM roles for authentication instead of fiddling around with access keys for IAM users is a big plus.</p><h2 id="Integrations"><a href="#Integrations" class="headerlink" title="Integrations"></a>Integrations</h2><p>AWS CodePiline comes with the following pre-built integrations:</p><ul><li><strong>AWS CloudFormation</strong> deploy your Infrastructure as Code templates</li><li><strong>AWS CodeBuild</strong> run any kind of build job inside a container</li><li><strong>AWS CodeDeploy</strong> deploy to a fleet of EC2 instances</li><li><strong>Amazon Elastic Container Service (ECS)</strong> deploy your containers with ECS</li><li><strong>AWS Elastic Beanstalk</strong> deploy your app on Amazon’s PaaS platform</li><li><strong>AWS OpsWorks Stacks</strong>  deploy your app based on Chef cookooks and recipes</li><li><strong>AWS Service Catalog</strong> deploy a product</li><li><strong>Alexa Skills Kit</strong> deploy your skill</li><li><strong>AWS Lambda</strong> invoke customized source code</li><li><strong>AWS Device Farm</strong> run UI tests on mobile devices</li></ul><p>It worth mentioning that the integration with AWS CodeBuild allows you to run any script inside a container based on a pre-built or customized image. This provides maximum flexibility. On top of that, a few 3rd party services are integrated as well.<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup></p><p>GitHub has taken a different approach: its open marketplace lists more than 2,000 integrations (aka actions)<sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup>. Well known and trusted organizations (e.g., GitHub itself also AWS), as well as individuals, publish their actions. A few examples:</p><ul><li><a href="https://github.com/marketplace/actions/cfn-lint-action" target="_blank" rel="noopener">cfn-lint-action</a> by Scott Brenner enables arbitrary actions for interacting with CloudFormation Linter.</li><li><a href="https://github.com/aws-actions/amazon-ecr-login" target="_blank" rel="noopener">amazon-ecr-login</a> by AWS logs in the local Docker client to one or more ECR registries.</li><li><a href="https://github.com/marketplace/actions/cache" target="_blank" rel="noopener">cache</a> by GitHub allows caching dependencies and build outputs to improve workflow execution time.</li></ul><p>Be careful when adding 3rd party actions to your deployment pipeline. First of all, you need to 100% trust the code that you are adding to your deployment pipeline. In theory, someone could insert malicious code or steal your AWS credentials by publishing a trojan horse to the GitHub Marketplace. Also, a 3rd party action could be removed from the GitHub Marketplace, which will break your deployment pipeline out of a sudden.</p><p>Luckily, it is not very hard to implement your own GitHub Actions.</p><ul><li><strong>Node.js</strong> run some JavaScript code</li><li><strong>Docker</strong> run anything that you have packaged into a Docker image before</li></ul><p>The following example shows how to build a GitHub Action to deploy a CloudFormation stack. I’ve decided to use a Docker-based action, as this approach is very flexible.</p><p>First of all, you need to define a <code>Dockerfile</code>. The <code>Dockerfile</code> defines the base image, installs build dependencies, and adds the <code>entrypoint.sh</code> script, which will be executed on each action invocation.</p><figure class="highlight docker"><figcaption><span>Dockerfile</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># Base image to start from</span></span><br><span class="line"><span class="keyword">FROM</span> amazonlinux:<span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Installs the AWS CLI</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> yum install -y awscli</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Adds custom entrypoint</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> entrypoint.sh /entrypoint.sh</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Execute custom entrypoint by default when starting the container</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;/entrypoint.sh&quot;</span>] </span></span><br></pre></td></tr></table></figure><p>Use the <code>entrypoint.sh</code> script to customize your deployment. In this simple example, all we need to do is to execute the <code>aws cloudformation deploy</code> command.</p><figure class="highlight bash"><figcaption><span>entrypoing.sh</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="built_in">set</span> -ex</span><br><span class="line"></span><br><span class="line">aws cloudformation deploy ...</span><br></pre></td></tr></table></figure><p>One tiny bit is missing. GitHub Actions requires you to describe your action in an <code>action.yml</code> file. The configuration file includes a name and description for your action. On top of that, the <code>action.yml</code> file tells GitHub to run the action inside a Docker container. GitHub builds the container on-the-fly with the help of your <code>Dockerfile</code>.</p><figure class="highlight yaml"><figcaption><span>action.yml</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">&#x27;Deploy CloudFormation Stack&#x27;</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">&#x27;Deploying a CoudFormation Stack&#x27;</span></span><br><span class="line"><span class="attr">runs:</span></span><br><span class="line">  <span class="attr">using:</span> <span class="string">&#x27;docker&#x27;</span></span><br><span class="line">  <span class="attr">image:</span> <span class="string">&#x27;Dockerfile&#x27;</span></span><br></pre></td></tr></table></figure><p>That is a simple way to define custom actions for your deployment pipeline.</p><h2 id="Configuration"><a href="#Configuration" class="headerlink" title="Configuration"></a>Configuration</h2><p>How to configure a deployment pipeline? Define your pipeline as code. My blog post <a href="https://cloudonaut.io/delivery-pipeline-as-code-aws-cloudformation-codepipeline/">Delivery Pipeline as Code: AWS CloudFormation and AWS CodePipeline</a> is still relevant. A lot of know-how and learnings will be baked into your deployment pipeline. Make sure that knowledge is written down in code so that you can reproduce it whenever necessary.</p><p>GitHub requires you to define your workflow within your source code repository. The following code snippet shows the definition of the deployment pipeline used to deploy this website.</p><ol><li>Check out the source code.</li><li>Read the access keys for AWS from the repository secrets.</li><li>Execute the custom deploy action described in the previous section.</li></ol><figure class="highlight yaml"><figcaption><span>.github&#x2F;workflows&#x2F;main.yml</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">master</span></span><br><span class="line"><span class="attr">name:</span> <span class="string">deploy</span></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">deploy:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">&#x27;Deploy&#x27;</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span></span><br><span class="line">      <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Configure</span> <span class="string">AWS</span> <span class="string">Credentials</span></span><br><span class="line">      <span class="attr">uses:</span> <span class="string">aws-actions/configure-aws-credentials@v4</span></span><br><span class="line">      <span class="attr">with:</span></span><br><span class="line">        <span class="attr">aws-access-key-id:</span> <span class="string">$</span></span><br><span class="line">        <span class="attr">aws-secret-access-key:</span> <span class="string">$</span></span><br><span class="line">        <span class="attr">aws-region:</span> <span class="string">eu-west-1</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Deploy</span></span><br><span class="line">      <span class="attr">uses:</span> <span class="string">./.github/actions/deploy/</span></span><br><span class="line">      <span class="attr">id:</span> <span class="string">deploy</span></span><br></pre></td></tr></table></figure><p>Use AWS CloudFormation or Terraform to define your AWS CodePipeline. The following example shows a CodePipeline written down as a CloudFormation template.</p><ol><li>Checkout source code.</li><li>Deploy the CloudFormation stack.</li></ol><figure class="highlight yaml"><figcaption><span>pipeline.yml</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># [...]</span></span><br><span class="line"><span class="attr">CodePipeline:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodePipeline::Pipeline&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">ArtifactStore:</span></span><br><span class="line">      <span class="attr">Location:</span> <span class="type">!Ref</span> <span class="string">ArtifactStore</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">S3</span></span><br><span class="line">    <span class="attr">RoleArn:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;CodePipelineIAMRole.Arn&#125;&#x27;</span></span><br><span class="line">    <span class="attr">Stages:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="attr">Actions:</span> </span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">          <span class="attr">ActionTypeId:</span></span><br><span class="line">            <span class="attr">Category:</span> <span class="string">Source</span> </span><br><span class="line">            <span class="attr">Owner:</span> <span class="string">ThirdParty</span> </span><br><span class="line">            <span class="attr">Version:</span> <span class="number">1</span> </span><br><span class="line">            <span class="attr">Provider:</span> <span class="string">GitHub</span></span><br><span class="line">          <span class="attr">OutputArtifacts:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">          <span class="attr">Configuration:</span></span><br><span class="line">            <span class="attr">Owner:</span> <span class="type">!Ref</span> <span class="string">GitHubOwner</span></span><br><span class="line">            <span class="attr">Repo:</span> <span class="type">!Ref</span> <span class="string">GitHubRepo</span></span><br><span class="line">            <span class="attr">Branch:</span> <span class="string">master</span></span><br><span class="line">            <span class="attr">OAuthToken:</span> <span class="type">!Ref</span> <span class="string">GitHubOAuthToken</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Deploy</span></span><br><span class="line">        <span class="attr">Actions:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Deploy</span></span><br><span class="line">          <span class="attr">ActionTypeId:</span></span><br><span class="line">            <span class="attr">Category:</span> <span class="string">Deploy</span> </span><br><span class="line">            <span class="attr">Owner:</span> <span class="string">AWS</span> </span><br><span class="line">            <span class="attr">Version:</span> <span class="number">1</span> </span><br><span class="line">            <span class="attr">Provider:</span> <span class="string">CloudFormation</span></span><br><span class="line">          <span class="attr">InputArtifacts:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">          <span class="attr">Configuration:</span></span><br><span class="line">            <span class="attr">ActionMode:</span> <span class="string">CREATE_UPDATE</span></span><br><span class="line">            <span class="attr">TemplatePath:</span> <span class="string">&#x27;Source::infrastructure.yml&quot;</span></span><br><span class="line"><span class="string">            Capabilities: CAPABILITY_IAM</span></span><br><span class="line"><span class="string">            StackName: &#x27;</span><span class="string">example&#x27;</span></span><br></pre></td></tr></table></figure><p>For me, it is most important to define your deployment pipeline as code. Both approaches are similar to my point of view.</p><h2 id="AWS-Credentials"><a href="#AWS-Credentials" class="headerlink" title="AWS Credentials"></a>AWS Credentials</h2><p>Your deployment pipelines need administrator access to be able to deploy your application and infrastructure to AWS. In general, you should avoid IAM users with access keys wherever possible. However, using access keys to authenticate an IAM user with far-reaching authorizations is the only option to execute a deployment with GitHub Actions. Of course, GitHub offers a way to store the AWS access keys in a secure and encrypted manner. A residual risk remains. On top of that, you need to rotate the access keys regularly to follow security best practices and compliance rules.</p><p>AWS CodePipeline has an advantage here. Instead of using static access keys, make use of IAM roles, which provide short-living access keys out-of-the-box. You can even define different IAM roles for various actions in your pipeline, which allows you to implement the least privilege principle.</p><h2 id="Vendor-Lock-in"><a href="#Vendor-Lock-in" class="headerlink" title="Vendor Lock-in"></a>Vendor Lock-in</h2><p>In my view, there is no significant vendor lock-in for deployment pipelines. Make sure to put the real magic to custom actions running inside Docker containers. Doing so allows you to switch between CI&#x2F;CD solutions very quickly. For example, I’ve migrated the deployment pipeline for this website from Jenkins to GitHub Actions within 2 hours.</p><p>It is worth mentioning that GitHub Actions works with your source code hosted on GitHub only. AWS CodePipeline works with your source code hosted on AWS CodeCommit, GitHub, or Amazon S3 (can be used as a workaround to integrate with any source code repository).</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>I like the experience that GitHub Actions are providing for building deployment pipelines. The deep integration into my development workflow (e.g., pull requests) is excellent. Also, GitHub Actions are easy to use. On the other hand, I’m missing the integrations into the AWS ecosystem that I’m used to from using AWS CodePipeline. However, the main concepts behind GitHub Actions and AWS CodePipeline are similar. Therefore, the details are decisive in the selection.</p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. https://docs.aws.amazon.com/codepipeline/latest/userguide/integrations-action-type.html <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. https://github.com/marketplace?type=actions <a href="#fnref:2" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Review: Amazon Connect – A Programmable Telephone System</title>
      <link>https://cloudonaut.io/review-amazon-connect-programmable-telephone-system/</link>
      <description>
        <![CDATA[<p>Do you provide services to consumer or business clients? Which channels do you provide for clients to get support, leave feedback, or let]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/review/">Review</category>
      <category domain="https://cloudonaut.io/tag/review/">review</category>
      <category domain="https://cloudonaut.io/tag/connect/">connect</category>
      <guid isPermaLink="true">https://cloudonaut.io/review-amazon-connect-programmable-telephone-system/</guid>
      <pubDate>Fri, 14 Feb 2020 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Do you provide services to consumer or business clients? Which channels do you provide for clients to get support, leave feedback, or let off frustration?</p><p>Amazon Connect provides a contact center solution in the cloud. Your clients contact you via phone or chat. A group of agents answers their phone calls and chat conversations. The workflows are fully customizable to your specific needs.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/02/phone@730w.webp 730w, /images/2020/02/phone@730w2x.webp 1460w, /images/2020/02/phone@610w.webp 610w, /images/2020/02/phone@610w2x.webp 1220w, /images/2020/02/phone@450w.webp 450w, /images/2020/02/phone@450w2x.webp 900w, /images/2020/02/phone@330w.webp 330w, /images/2020/02/phone@330w2x.webp 660w, /images/2020/02/phone@545w.webp 545w, /images/2020/02/phone@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/02/phone@730w.jpg 730w, /images/2020/02/phone@730w2x.jpg 1460w, /images/2020/02/phone@610w.jpg 610w, /images/2020/02/phone@610w2x.jpg 1220w, /images/2020/02/phone@450w.jpg 450w, /images/2020/02/phone@450w2x.jpg 900w, /images/2020/02/phone@330w.jpg 330w, /images/2020/02/phone@330w2x.jpg 660w, /images/2020/02/phone@545w.jpg 545w, /images/2020/02/phone@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/02/phone.jpg" alt="Review: Amazon Connect" title="Review: Amazon Connect"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/13-review-amazon-connect/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>This review puts Amazon Connect to the test. I set up Amazon Connect for our consulting agency <a href="https://widdix.net/" target="_blank" rel="noopener">widdix</a>, recently. While doing so, I had a look into the technical details as well.</p><p>Before we go any further, a small disclaimer: I’ve never operated a call center with tens or hundreds of agents. I’m reviewing Amazon Connect from my point of view as an AWS consultant and software engineer.</p><h2 id="How-It-Works"><a href="#How-It-Works" class="headerlink" title="How It Works"></a>How It Works</h2><p>To get started with Amazon Connect, you have to launch a new Connect instance. As shown in the following, figure an instance connects your clients with your agents. Clients call in or start a chat conversation. Your agents log in the instance and start answering incoming phone calls or chat conversations.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/02/connect-instance@730w.webp 730w, /images/2020/02/connect-instance@730w2x.webp 1460w, /images/2020/02/connect-instance@610w.webp 610w, /images/2020/02/connect-instance@610w2x.webp 1220w, /images/2020/02/connect-instance@450w.webp 450w, /images/2020/02/connect-instance@450w2x.webp 900w, /images/2020/02/connect-instance@330w.webp 330w, /images/2020/02/connect-instance@330w2x.webp 660w, /images/2020/02/connect-instance@545w.webp 545w, /images/2020/02/connect-instance@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/02/connect-instance@730w.png 730w, /images/2020/02/connect-instance@730w2x.png 1460w, /images/2020/02/connect-instance@610w.png 610w, /images/2020/02/connect-instance@610w2x.png 1220w, /images/2020/02/connect-instance@450w.png 450w, /images/2020/02/connect-instance@450w2x.png 900w, /images/2020/02/connect-instance@330w.png 330w, /images/2020/02/connect-instance@330w2x.png 660w, /images/2020/02/connect-instance@545w.png 545w, /images/2020/02/connect-instance@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/02/connect-instance.png" alt="Amazon Connect: Instance" title="Amazon Connect: Instance"></picture></p><p>The main components of Amazon Connect are:</p><ul><li><strong>Customer</strong>: a customer calls or starts a chat conversation.</li><li><strong>Phone Number</strong>: inbound and outbound phone number, toll-free and direct-in-dial (DID), bring your own or claim a number.</li><li><strong>Flow</strong>: defines the customer experience of your contact center.</li><li><strong>Queue</strong>: customers wait in a queue before they are routed to an agent.</li><li><strong>Agent</strong>: a human being answering phone calls and chat conversations with the help of the built-in Contact Control Panel (CCP).</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/02/connect-flow-queue@730w.webp 730w, /images/2020/02/connect-flow-queue@730w2x.webp 1460w, /images/2020/02/connect-flow-queue@610w.webp 610w, /images/2020/02/connect-flow-queue@610w2x.webp 1220w, /images/2020/02/connect-flow-queue@450w.webp 450w, /images/2020/02/connect-flow-queue@450w2x.webp 900w, /images/2020/02/connect-flow-queue@330w.webp 330w, /images/2020/02/connect-flow-queue@330w2x.webp 660w, /images/2020/02/connect-flow-queue@545w.webp 545w, /images/2020/02/connect-flow-queue@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/02/connect-flow-queue@730w.png 730w, /images/2020/02/connect-flow-queue@730w2x.png 1460w, /images/2020/02/connect-flow-queue@610w.png 610w, /images/2020/02/connect-flow-queue@610w2x.png 1220w, /images/2020/02/connect-flow-queue@450w.png 450w, /images/2020/02/connect-flow-queue@450w2x.png 900w, /images/2020/02/connect-flow-queue@330w.png 330w, /images/2020/02/connect-flow-queue@330w2x.png 660w, /images/2020/02/connect-flow-queue@545w.png 545w, /images/2020/02/connect-flow-queue@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/02/connect-flow-queue.png" alt="Amazon Connect: Client, Phone Number, Flow, Queue, Agent" title="Amazon Connect: Client, Phone Number, Flow, Queue, Agent"></picture></p><p>Amazon Connect supports inbound and outbound calls. More advanced features allow you to define routing profiles for agents, hours of operation, and call recording, for example.</p><p>On top of that, Amazon Connect integrates with AWS and 3rd party services:</p><ul><li>AWS Lambda: customize your contact center by writing source code.</li><li>Amazon Lex: build a voice or chatbot to interact with your customers.</li><li>Amazon Kinesis Video Streams: record voice in realtime and process later (e.g., transcribe).</li><li>Salesforce: integrate with your 3rd party CRM.</li><li>Zendesk: integrate with your 3rd party CRM.</li></ul><p>In short, Amazon Connect is designed to operate a contact center at a small or large scale.</p><h2 id="More-Use-Cases"><a href="#More-Use-Cases" class="headerlink" title="More Use Cases"></a>More Use Cases</h2><p>With a little imagination, you can find more use cases for Amazon Connect. Being able to define custom flows and the call AWS Lambda functions opens up many possibilities. A few examples:</p><ul><li>Implement a voice bot accessible via phone (e.g., to request data or execute a transaction).</li><li>Implement a routing logic that forwards incoming calls to landline or mobile numbers (e.g., round-robin incoming calls to 2-5 mobile numbers).</li><li>Implement a blacklist to block promotional calls.</li></ul><p>Amazon Connect is a programmable telephone system.</p><h2 id="Pricing"><a href="#Pricing" class="headerlink" title="Pricing"></a>Pricing</h2><p>With Amazon Connect, there are almost no fixed costs. You only pay for voice and chat usage.</p><table class="table table-striped table-responsive"><thead><tr><th style="text-align:left">Type</th><th style="text-align:left">Pricing</th></tr></thead><tbody><tr><td style="text-align:left">Voice Usage</td><td style="text-align:left">$0.018 per minute</td></tr><tr><td style="text-align:left">Chat Usage</td><td style="text-align:left">$0.004 per message</td></tr></tbody></table><p>There is a fixed fee for phone numbers. Prices vary according to region, type, and country — a few examples.</p><table class="table table-striped table-responsive"><thead><tr><th style="text-align:left">AWS Region</th><th style="text-align:left">Type</th><th style="text-align:left">Country</th><th style="text-align:right">Monthly Costs</th></tr></thead><tbody><tr><td style="text-align:left">US East (N. Virginia)</td><td style="text-align:left">Direct Inward Dial</td><td style="text-align:left">United States of America</td><td style="text-align:right">$0.90</td></tr><tr><td style="text-align:left">US East (N. Virginia)</td><td style="text-align:left">Toll Free</td><td style="text-align:left">United States of America</td><td style="text-align:right">$1.80</td></tr><tr><td style="text-align:left">Europe (Frankfurt)</td><td style="text-align:left">Direct Inward Dial</td><td style="text-align:left">Germany</td><td style="text-align:right">$3.00</td></tr><tr><td style="text-align:left">Europe (Frankfurt)</td><td style="text-align:left">Toll Free</td><td style="text-align:left">Germany</td><td style="text-align:right">$3.90</td></tr><tr><td style="text-align:left">Asia Pacific (Sydney</td><td style="text-align:left">Direct Inward Dial</td><td style="text-align:left">Australia</td><td style="text-align:right">$3.00</td></tr><tr><td style="text-align:left">Asia Pacific (Sydney</td><td style="text-align:left">Toll Free</td><td style="text-align:left">Australia</td><td style="text-align:right">$15.21</td></tr></tbody></table><p>On top of that, you pay for the inbound usage of your phone numbers.</p><table class="table table-striped table-responsive"><thead><tr><th style="text-align:left">AWS Region</th><th style="text-align:left">Type</th><th style="text-align:left">Country</th><th style="text-align:right">Per Minute</th></tr></thead><tbody><tr><td style="text-align:left">US East (N. Virginia)</td><td style="text-align:left">Direct Inward Dial</td><td style="text-align:left">United States of America</td><td style="text-align:right">$0.0022</td></tr><tr><td style="text-align:left">US East (N. Virginia)</td><td style="text-align:left">Toll Free</td><td style="text-align:left">United States of America</td><td style="text-align:right">$0.0120</td></tr><tr><td style="text-align:left">Europe (Frankfurt)</td><td style="text-align:left">Direct Inward Dial</td><td style="text-align:left">Germany</td><td style="text-align:right">$0.0040</td></tr><tr><td style="text-align:left">Europe (Frankfurt)</td><td style="text-align:left">Toll Free</td><td style="text-align:left">Germany</td><td style="text-align:right">$0.0387</td></tr><tr><td style="text-align:left">Asia Pacific (Sydney</td><td style="text-align:left">Direct Inward Dial</td><td style="text-align:left">Australia</td><td style="text-align:right">$0.0050</td></tr><tr><td style="text-align:left">Asia Pacific (Sydney</td><td style="text-align:left">Toll Free</td><td style="text-align:left">Australia</td><td style="text-align:right">$0.0540</td></tr></tbody></table><p>Outbound usage of phone numbers incurs costs as well. See <a href="https://aws.amazon.com/connect/pricing/" target="_blank" rel="noopener">Amazon Connect pricing</a> for details.</p><p>I’ve estimated the costs for Amazon Connect at our consulting agency <a href="https://widdix.net/" target="_blank" rel="noopener">widdix</a>. We expect potential customers and partners calling in from time to time.</p><ul><li>Region: Europe (Frankfurt)</li><li>Phone Numbers: 3 Direct Inward Dial numbers</li><li>Voice Usage: 30 minutes per day (inbound only)</li></ul><table class="table table-striped table-responsive"><thead><tr><th style="text-align:left">Type</th><th style="text-align:right">Monthly Costs</th></tr></thead><tbody><tr><td style="text-align:left">Voice Usage</td><td style="text-align:right">$16.20</td></tr><tr><td style="text-align:left">Phone Number Usage</td><td style="text-align:right">$3.60</td></tr><tr><td style="text-align:left">Claimed Phone Numbers</td><td style="text-align:right">$9.00</td></tr><tr><td style="text-align:left"><strong>Total Costs</strong></td><td style="text-align:right"><strong>$28.80</strong></td></tr></tbody></table><p>I would like to emphasize that there are practically no fixed costs when using Amazon Connect. Pay-per-use only!</p><h2 id="Lambda-Integration"><a href="#Lambda-Integration" class="headerlink" title="Lambda Integration"></a>Lambda Integration</h2><p>Amazon Connect integrates with AWS Lambda. Implement Lambda functions to extend the functionality and customize the customer experience to your needs.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/02/connect-lambda@730w.webp 730w, /images/2020/02/connect-lambda@730w2x.webp 1460w, /images/2020/02/connect-lambda@610w.webp 610w, /images/2020/02/connect-lambda@610w2x.webp 1220w, /images/2020/02/connect-lambda@450w.webp 450w, /images/2020/02/connect-lambda@450w2x.webp 900w, /images/2020/02/connect-lambda@330w.webp 330w, /images/2020/02/connect-lambda@330w2x.webp 660w, /images/2020/02/connect-lambda@545w.webp 545w, /images/2020/02/connect-lambda@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/02/connect-lambda@730w.png 730w, /images/2020/02/connect-lambda@730w2x.png 1460w, /images/2020/02/connect-lambda@610w.png 610w, /images/2020/02/connect-lambda@610w2x.png 1220w, /images/2020/02/connect-lambda@450w.png 450w, /images/2020/02/connect-lambda@450w2x.png 900w, /images/2020/02/connect-lambda@330w.png 330w, /images/2020/02/connect-lambda@330w2x.png 660w, /images/2020/02/connect-lambda@545w.png 545w, /images/2020/02/connect-lambda@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/02/connect-lambda.png" alt="Amazon Connect: AWS Lambda" title="Amazon Connect: AWS Lambda"></picture></p><p>Typical use cases for a Lambda function are:</p><ul><li>Enrich the contact information by fetching data from a database or 3rd party service.</li><li>Interact with sub-systems based on customer input.</li><li>Built integrations with your CRM.</li><li>Send analytics data to a database or analytics platform.</li></ul><p>I’ve used a Lambda function and a DynamoDB table to implement a blacklist.</p><ol><li>The contact flow invokes a Lambda function.</li><li>The Lambda function checks whether the caller ID is stored in DynamoDB.</li><li>The Lambda function adds a <code>Blacklisted</code> attribute to the customer.</li><li>The next step checks for the <code>Blacklisted</code> attribute and terminates the call for blacklisted customers.</li></ol><p>Keep in mind the following limits when implementing Lambda functions for Amazon Connect:</p><ul><li>The invocation timeout is 3 seconds per default and 8 seconds maximum.</li><li>There will be silence during the invocation of a Lambda function. Keep that in mind, when chaining Lambda function actions.</li><li>The duration of a sequence of Lambda functions is limited to 20 seconds.</li><li>The Lambda function must return a flat object of key&#x2F;value pairs.</li><li>The result object must include keys that include alphanumeric, dash, and underscore characters only.</li><li>The result must not exceed 32 KByte of UTF-8 data.</li></ul><h2 id="User-Interface"><a href="#User-Interface" class="headerlink" title="User Interface"></a>User Interface</h2><p>I do like the user interface to manage a connect instance. Getting started with Amazon Connect is simple, as the user interface is self-explanatory and guides you through all required steps.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/02/connect-screenshot-flow@730w.webp 730w, /images/2020/02/connect-screenshot-flow@730w2x.webp 1460w, /images/2020/02/connect-screenshot-flow@610w.webp 610w, /images/2020/02/connect-screenshot-flow@610w2x.webp 1220w, /images/2020/02/connect-screenshot-flow@450w.webp 450w, /images/2020/02/connect-screenshot-flow@450w2x.webp 900w, /images/2020/02/connect-screenshot-flow@330w.webp 330w, /images/2020/02/connect-screenshot-flow@330w2x.webp 660w, /images/2020/02/connect-screenshot-flow@545w.webp 545w, /images/2020/02/connect-screenshot-flow@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/02/connect-screenshot-flow@730w.png 730w, /images/2020/02/connect-screenshot-flow@730w2x.png 1460w, /images/2020/02/connect-screenshot-flow@610w.png 610w, /images/2020/02/connect-screenshot-flow@610w2x.png 1220w, /images/2020/02/connect-screenshot-flow@450w.png 450w, /images/2020/02/connect-screenshot-flow@450w2x.png 900w, /images/2020/02/connect-screenshot-flow@330w.png 330w, /images/2020/02/connect-screenshot-flow@330w2x.png 660w, /images/2020/02/connect-screenshot-flow@545w.png 545w, /images/2020/02/connect-screenshot-flow@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/02/connect-screenshot-flow.png" alt="Amazon Connect: User Interface" title="Amazon Connect: User Interface"></picture></p><p>The Contact Control Panel (CCP), the user interface used by agents to accept incoming calls, chat with customers, and initiate outbound calls, provides a solid experience as well.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/02/connect-screenshot-cpc@730w.webp 730w, /images/2020/02/connect-screenshot-cpc@730w2x.webp 1460w, /images/2020/02/connect-screenshot-cpc@610w.webp 610w, /images/2020/02/connect-screenshot-cpc@610w2x.webp 1220w, /images/2020/02/connect-screenshot-cpc@450w.webp 450w, /images/2020/02/connect-screenshot-cpc@450w2x.webp 900w, /images/2020/02/connect-screenshot-cpc@330w.webp 330w, /images/2020/02/connect-screenshot-cpc@330w2x.webp 660w, /images/2020/02/connect-screenshot-cpc@545w.webp 545w, /images/2020/02/connect-screenshot-cpc@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/02/connect-screenshot-cpc@730w.png 730w, /images/2020/02/connect-screenshot-cpc@730w2x.png 1460w, /images/2020/02/connect-screenshot-cpc@610w.png 610w, /images/2020/02/connect-screenshot-cpc@610w2x.png 1220w, /images/2020/02/connect-screenshot-cpc@450w.png 450w, /images/2020/02/connect-screenshot-cpc@450w2x.png 900w, /images/2020/02/connect-screenshot-cpc@330w.png 330w, /images/2020/02/connect-screenshot-cpc@330w2x.png 660w, /images/2020/02/connect-screenshot-cpc@545w.png 545w, /images/2020/02/connect-screenshot-cpc@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/02/connect-screenshot-cpc.png" alt="Amazon Connect: Contact Control Panel (CCP)" title="Amazon Connect: Contact Control Panel (CCP)"></picture></p><p>There is one little snag: Amazon Connect supports Chrome and Firefox only.<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup></p><h2 id="Management-API-and-Infrastructure-as-Code"><a href="#Management-API-and-Infrastructure-as-Code" class="headerlink" title="Management API and Infrastructure as Code"></a>Management API and Infrastructure as Code</h2><p>Unfortunately, Amazon Connect does not offer an API to configure your instance. Therefore, you cannot use Infrastructure as Code tools like CloudFormation or Terraform to spin up your contact center. That’s too bad and prevents us from deploying our contact center in an automated and tested way.</p><p>After all, there is a function for exporting and importing flows. Unfortunately, that is not even close to having a complete management API. Also, the export&#x2F;import feature is still marked as <strong>beta</strong>.</p><h2 id="SSO-with-SAML"><a href="#SSO-with-SAML" class="headerlink" title="SSO with SAML"></a>SSO with SAML</h2><p>There are three ways to authenticate agents for Amazon Connect.</p><ol><li>The built-in user management.</li><li>The AWS Directory Service (Microsoft Active Directory, Active Directory Connector, and Simple Active Directory).</li><li>The SAML 2.0-based authentication.</li></ol><p>I had a more in-depth look at the SAML 2.0-based authentication and was disappointed.</p><p>First of all, it is possible to use SAML 2.0 for authentication. However, you have to add users to your Connect instance manually. Otherwise, a user is not able to log in via SAML.<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup></p><p>In my opinion, SSO with SAML is not fully implemented.</p><h2 id="Transferring-a-Phone-Number"><a href="#Transferring-a-Phone-Number" class="headerlink" title="Transferring a Phone Number"></a>Transferring a Phone Number</h2><p>There are two options to add a phone number to your contact center:</p><ul><li>Claim a phone number provided by AWS.</li><li>Transfer an existing phone number.</li></ul><p>Transferring a phone number is an adventure. I’ve gone through the process for a few German phone numbers <code>+49</code>. Keep in mind that the process varies from country to country.</p><p>Transferring a phone number starts with creating an AWS support case. No need to pay for AWS support, though.</p><ol><li>Open the AWS Support Center.</li><li>Click the <em>Create case</em> button.</li><li>Select <em>Service limit increase</em>.</li><li>Choose limit type <em>Amazon Connect</em>.</li><li>Type in the ARN of your contact center instance.</li><li>Select the region of your contact center instance.</li><li>Choose <em>Phone Number Porting</em> from the list of limit types.</li><li>Describe your transfer request.</li></ol><p>AWS will send back a PDF that you need to fill out. The process of transferring a phone number seems to involve many manual steps. The support ticket went back and forth several times. In some cases, the AWS support team had to wait for answers from the Amazon Connect service team.</p><p>Overall, the transferring of our phone numbers took me almost two months. My advice is, therefore: plan enough time.</p><h2 id="Missing-Features"><a href="#Missing-Features" class="headerlink" title="Missing Features"></a>Missing Features</h2><p>The feature set of Amazon Connect seems quite complete to me. However, I’m missing the following:</p><ul><li>Chat Widget: So far, there is no easy way to integrate the chat channel into your websites. Besides some examples, there is nothing more than the API documentation for implementing the frontend part. It is even necessary to implement a little bit of backend logic. I’m expecting a ready-to-use widget that I can add to my website with a few lines of HTML&#x2F;JavaScript code here.</li><li>At the moment, it is not possible to transfer phone numbers between Connect instances without contacting AWS support. There should be an API and user interface for that.</li><li>It is not possible to switch the authentication mechanism of a Connect instance right now (e.g., from built-in user database to SAML).</li><li>Amazon Lex should be available in the same regions as Amazon Connect.</li></ul><h2 id="Service-Quotas"><a href="#Service-Quotas" class="headerlink" title="Service Quotas"></a>Service Quotas</h2><p>As usual, Amazon Connect comes with service quotas.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">Default Quota</th><th style="text-align:center">Type</th></tr></thead><tbody><tr><td>Users per instance</td><td style="text-align:right">500</td><td style="text-align:center">Soft Limit</td></tr><tr><td>Lambda functions per instance</td><td style="text-align:right">35</td><td style="text-align:center">Soft Limit</td></tr><tr><td>Active chats per agent</td><td style="text-align:right">5</td><td style="text-align:center">Hard Limit</td></tr><tr><td>Characters per chat message</td><td style="text-align:right">1024</td><td style="text-align:center">Hard Limit</td></tr></tbody></table><p>See <a href="https://docs.aws.amazon.com/connect/latest/adminguide/amazon-connect-service-limits.html" target="_blank" rel="noopener">Amazon Connect Service Quotas</a> for more detailed information.</p><p>A word about soft limits: it is possible to increase soft limits by filing a support ticket. Sadly, AWS does not provide any information about the upper limit for those soft limits. It seems to be an internal policy not to tell customers upfront about the upper limit. This is unfair and non-transparent. One should, therefore, understand the default quotas as an upper limit.</p><h2 id="Service-Maturity-Table"><a href="#Service-Maturity-Table" class="headerlink" title="Service Maturity Table"></a>Service Maturity Table</h2><p>The following table summarizes the maturity of the service:</p><table class="table table-striped table-responsive"><thead><tr><th>Criteria</th><th>Support</th><th style="text-align:right">Score</th></tr></thead><tbody><tr><td>Feature Completeness</td><td>✅</td><td style="text-align:right">8</td></tr><tr><td>Documentation Detailedness</td><td>✅</td><td style="text-align:right">7</td></tr><tr><td>Tags (Grouping + Billing)</td><td>❌</td><td style="text-align:right">0</td></tr><tr><td>CloudFormation + Terraform support</td><td>❌</td><td style="text-align:right">0</td></tr><tr><td>Emits CloudWatch Events</td><td>❌<sup><a href="#fn:3" id="fnref:3" class="footnote-ref">3</a></sup></td><td style="text-align:right">0</td></tr><tr><td>IAM granularity</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>Integrated with AWS Config</td><td>❌<sup><a href="#fn:4" id="fnref:4" class="footnote-ref">4</a></sup></td><td style="text-align:right">0</td></tr><tr><td>Auditing via AWS CloudTrail</td><td>✅<sup><a href="#fn:5" id="fnref:5" class="footnote-ref">5</a></sup></td><td style="text-align:right">8</td></tr><tr><td>Available in all commercial regions</td><td>⚠️</td><td style="text-align:right">6</td></tr><tr><td>SLA</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td><strong>Total Maturity Score (0-10)</strong></td><td>⚠️</td><td style="text-align:right"><strong>5.4</strong></td></tr></tbody></table><p>Our maturity score for Amazon Connect is 5.4 on a scale from 0 to 10.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Amazon Connect is a programmable phone system and contact center solution. I like that there are almost no fixed costs associated with Amazon Connect. The solution is flexible, not only because of the integration with AWS Lambda and other AWS services. The biggest bummer: there is no management API and, therefore, no CloudFormation or Terraform support available. The contact center features seem to be stable and mature. However, the integration into the rest of the AWS ecosystem is mediocre (Tags, CloudWatch Events, AWS Config, …).</p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. https://docs.aws.amazon.com/connect/latest/adminguide/what-is-amazon-connect.html#browsers <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. https://docs.aws.amazon.com/connect/latest/adminguide/configure-saml.html#saml-add-users <a href="#fnref:2" class="footnote-backref">↩</a></p></li><li id="fn:3"><p>3. https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html <a href="#fnref:3" class="footnote-backref">↩</a></p></li><li id="fn:4"><p>4. https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html <a href="#fnref:4" class="footnote-backref">↩</a></p></li><li id="fn:5"><p>5. https://docs.aws.amazon.com/connect/latest/adminguide/logging-using-cloudtrail.html <a href="#fnref:5" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Show your Tool: Parliament</title>
      <link>https://cloudonaut.io/show-your-tool-parliament/</link>
      <description>
        <![CDATA[<p>In this series, we present AWS tooling from the community for the community. We talk directly with the tool makers. Who are they? What pr]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/show-your-tool/">Show your Tool</category>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <guid isPermaLink="true">https://cloudonaut.io/show-your-tool-parliament/</guid>
      <pubDate>Tue, 04 Feb 2020 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>In this series, we present AWS tooling from the community for the community. We talk directly with the tool makers. Who are they? What problem does the tool solve? And what motivates them to contribute to open-source AWS tooling.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/02/show-your-tool-parliament@730w.webp 730w, /images/2020/02/show-your-tool-parliament@730w2x.webp 1460w, /images/2020/02/show-your-tool-parliament@610w.webp 610w, /images/2020/02/show-your-tool-parliament@610w2x.webp 1220w, /images/2020/02/show-your-tool-parliament@450w.webp 450w, /images/2020/02/show-your-tool-parliament@450w2x.webp 900w, /images/2020/02/show-your-tool-parliament@330w.webp 330w, /images/2020/02/show-your-tool-parliament@330w2x.webp 660w, /images/2020/02/show-your-tool-parliament@545w.webp 545w, /images/2020/02/show-your-tool-parliament@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/02/show-your-tool-parliament@730w.jpg 730w, /images/2020/02/show-your-tool-parliament@730w2x.jpg 1460w, /images/2020/02/show-your-tool-parliament@610w.jpg 610w, /images/2020/02/show-your-tool-parliament@610w2x.jpg 1220w, /images/2020/02/show-your-tool-parliament@450w.jpg 450w, /images/2020/02/show-your-tool-parliament@450w2x.jpg 900w, /images/2020/02/show-your-tool-parliament@330w.jpg 330w, /images/2020/02/show-your-tool-parliament@330w2x.jpg 660w, /images/2020/02/show-your-tool-parliament@545w.jpg 545w, /images/2020/02/show-your-tool-parliament@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/02/show-your-tool-parliament.jpg" alt="Show your Tool" title="Show your Tool"></picture></p><p>This time, we talk with Scott Piper about <a href="https://github.com/duo-labs/parliament" target="_blank" rel="noopener">Parliament</a>. You can connect with Scott on <a href="https://x.com/0xdabbad00" target="_blank" rel="noopener">Twitter</a> or <a href="https://www.linkedin.com/in/scott-piper-security/" target="_blank" rel="noopener">LinkedIn</a>.</p><p><em>cloudonaut: Tell us a little about yourself, your history with AWS, and your motivation to develop AWS tooling.</em><br>Scott Piper: My history with AWS didn’t really start until a little over three years ago, when I released <a href="http://flaws.cloud/" target="_blank" rel="noopener">flaws.cloud</a>, which is a free CTF of sorts, that I built to teach myself and the DevOps folks at the company I was with, about AWS security. I released it publicly and thought that if I got lucky, maybe a dozen people would check it out.  Instead, in the first month 30,000 people tried it out and it has continued to be a popular site for learning about AWS security in a hands-on way. And it continues to be relevant! One of the levels (again, this was created three years ago) is nearly the exact same as the scenario that resulted in the Capital One breach a few months ago.</p><p>As a result of the popularity of flaws.cloud and some other things, I decided to start a <a href="https://summitroute.com/" target="_blank" rel="noopener">consulting business focused on AWS security</a>. I was lucky enough to be able to work with Duo Security with whom I developed <a href="https://github.com/duo-labs/cloudmapper" target="_blank" rel="noopener">CloudMapper</a>, <a href="https://github.com/duo-labs/cloudtracker" target="_blank" rel="noopener">CloudTracker</a>, <a href="https://github.com/duo-labs/parliament" target="_blank" rel="noopener">Parliament</a>, and some other things.</p><p>The tools I’ve developed have largely been the result of working with clients and identifying problems they had for which there weren’t any existing solutions for.  Recently, I shifted my business to focusing on providing AWS security training privately to companies, but I’ll continue building tools as a result of conversations with these companies and as a great way for me personally to learn and stay on the leading edge of AWS security.</p><p><em>cloudonaut: What problem does your tool solve?</em><br>Scott Piper: Parliament is a linter for AWS IAM policy policies. What this means is that the JSON document you write to define the privileges and access someone has on AWS can have spelling mistakes and other problems in it, but will still be valid (ie. it will still save), so a tool was needed to check for these problems.</p><p>I had started monitoring the changes that AWS was making to their managed IAM policies and noticed that every once in a while they would make a correction to what had been a mistake in a policy.  For example, a common problem was using “s3:ListBuckets” (which is the name of an API) instead of the correct name for the privilege “s3:ListAllMyBuckets”.  I then manually reviewed every single AWS managed IAM policy by hand to try to find every possible mistake they had made. I found a lot of mistakes (and missed a lot as I later realized), and reported those in.</p><p>I figured there were a couple of common types of mistakes that even AWS was making so customers had to be making even more mistakes, and I should build a tool to find these.  Since then, the tool has continued to evolve from finding what are basically spelling mistakes to looking for privilege escalations and the ability to create custom auditors to identify concerns that are specific to a customer’s environment, such as which policies grant access to a specific sensitive bucket.</p><p><em>cloudonaut: Who should use your tool? Who should not?</em><br>Scott Piper: Anyone using AWS who wants to review their IAM policies should use this tool.  Parliament exists as a stand-alone tool and also as a library. If you build a tool that does any sort of auditing and checking of AWS, or anything related to IAM, you should integrate Parliament into it.  </p><p><em>cloudonaut: Show us a short demo of your tool</em><br>Scott Piper: As a simple example, create a file with the following IAM policy:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2012-10-17&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Statement&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="string">&quot;s3:GetObject&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:s3:::mybucket&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Pass this to Parliament, and you’ll get the following output:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ parliament --file test.json</span><br><span class="line">MEDIUM - No resources match <span class="keyword">for</span> the given action -  - [&#123;<span class="string">&#x27;action&#x27;</span>: <span class="string">&#x27;s3:GetObject&#x27;</span>, <span class="string">&#x27;required_format&#x27;</span>: <span class="string">&#x27;arn:*:s3:::*/*&#x27;</span>&#125;] - &#123;<span class="string">&#x27;actions&#x27;</span>: [<span class="string">&#x27;s3:GetObject&#x27;</span>], <span class="string">&#x27;filepath&#x27;</span>: <span class="string">&#x27;test.json&#x27;</span>&#125;</span><br></pre></td></tr></table></figure><p>The reason for this finding is that s3:GetObject requires a resource that refers to S3 objects (it should have a “&#x2F;“ in it), whereas the resource given refers to an S3 bucket.  As a result, this policy actually doesn’t grant any privileges because of the mismatch.</p><p><em>cloudonaut: How can we use your tool</em><br>Scott Piper: <code>pip install parliament</code></p><p><em>cloudonaut: Where can we find more information about your tool?</em><br>Scott Piper: <a href="https://github.com/duo-labs/parliament" target="_blank" rel="noopener">https://github.com/duo-labs/parliament</a></p><p><em>cloudonaut: Are you aware of tools that solve a similar problem than yours? What’s the difference?</em><br>Scott Piper: A few tools, such as the CloudFormation linter <a href="https://github.com/aws-cloudformation/cfn-python-lint/" target="_blank" rel="noopener">cfn-python-lint</a>, have some ability to spot misspelled actions used in an IAM policy, but there are no open-source (or vendor) tools that are as in-depth as Parliament. For example, Parliament can also identify when a Condition is used that does not make sense for the given actions.  That’s a much harder problem and requires a more in-depth data set of knowledge than any other tool uses.  To get that data, I had to write a scraper for the IAM docs and have worked with the AWS security team, reporting over a hundred issues that no other tool was capable of finding in AWS’s own policies, in their docs, and elsewhere.</p><p>The AWS web console does have some advanced abilities to spot problems in IAM policies, but you have to click through the web UI to see these, and can’t extract that ability out into a stand-alone tool or library like Parliament.</p><p><em>cloudonaut: What’s the roadmap for your tool? Are you planning any significant releases?</em><br>Scott Piper: I have been working with Kinnaird McQuade from Salesforce’s security team, who developed <a href="https://github.com/salesforce/policy_sentry" target="_blank" rel="noopener">Policy Sentry</a>, to integrate some of the functionality from that tool into Parliament.  Specifically, Kinnaird is an active user of Parliament’s ability to create custom auditors, so we will be integrating in his work that detects various high risk privileges into Parliament.</p><p><em>cloudonaut: How do you stay motivated to maintain your open source project?</em><br>Scott Piper: I’ve been lucky a few times to have had contract work that directly involved my open-source projects, either building them in the first place, or adding functionality to them.  If given the choice between doing a thing that privately helps one company, or doing a thing that can be released publicly to help lot’s of companies in addition to the one paying me, I’m going to choose the latter contract.  My clients like working with me because of that. They know that I’m genuinely trying to help improve security for everyone.</p><p>It also helps me learn.  I know so much more about how IAM works now because of building Parliament, I know how people use IAM policies because of public Github issues and private conversations that resulted from people using it, and I have a good relationship with a few people at AWS that have opened up back channels for me to have discussions with them and make requests for things from them, that I wouldn’t have been able to by not doing this public work.</p><p><em>cloudonaut: Are you attending any conferences within the next few months where the community can get in touch?</em><br>Scott Piper: I’m actually one of the organizers of <a href="https://fwdcloudsec.org/" target="_blank" rel="noopener">fwd:cloudsec</a>, which is a new, not-for-profit, cloud security conference taking place in Houston, Texas, the day before re:Inforce on June 29.  There are a half dozen organizers from various companies and industries that are working to put it together.  We’ve already had a lot of interest from some of the top people in cloud security who want to speak at it, so we’re really excited about it.</p><p>–</p><p>What tools do you use to make your AWS work easier? <a href="mailto:&#x68;&#101;&#108;&#108;&#111;&#x40;&#x63;&#108;&#x6f;&#117;&#100;&#111;&#110;&#x61;&#x75;&#x74;&#46;&#x69;&#111;">Share your favorite tool with us</a>!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>EC2 Instances 2.0 - Time to Update Your Toolbox</title>
      <link>https://cloudonaut.io/ec2-instances-2-0-time-to-update-your-toolbox/</link>
      <description>Managing a mutable EC2 instance comes with many responsibilities. In this post, I show you how to solve everyday challenges by leveraging the latest and greatest capabilities of the AWS platform.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/ssm/">ssm</category>
      <category domain="https://cloudonaut.io/tag/backup/">backup</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/ec2-instances-2-0-time-to-update-your-toolbox/</guid>
      <pubDate>Tue, 28 Jan 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Amazon Elastic Compute Cloud (EC2) has more than 13 years of public history and is one of the <a href="https://en.wikipedia.org/wiki/Timeline_of_Amazon_Web_Services#Full_timeline" target="_blank" rel="noopener">oldest AWS services</a>. EC2 is a mature service that reinvented itself many times:</p><ul><li>From <a href="https://aws.amazon.com/blogs/aws/introducing-amazon-virtual-private-cloud-vpc/" target="_blank" rel="noopener">EC2 classic to Amazon VPC</a>.</li><li>From <a href="https://aws.amazon.com/blogs/aws/new-session-manager/" target="_blank" rel="noopener">SSH access to AWS SSM Session Manager</a>.</li><li>From <a href="https://aws.amazon.com/about-aws/whats-new/2020/01/aws-backup-adds-support-amazon-elastic-cloud-compute-instance-backup/" target="_blank" rel="noopener">self-managed backup solution to AWS Backup</a>.</li><li>More powerful instance families.</li><li>New pricing options.</li><li>And much more.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/01/data-center@730w.webp 730w, /images/2020/01/data-center@730w2x.webp 1460w, /images/2020/01/data-center@610w.webp 610w, /images/2020/01/data-center@610w2x.webp 1220w, /images/2020/01/data-center@450w.webp 450w, /images/2020/01/data-center@450w2x.webp 900w, /images/2020/01/data-center@330w.webp 330w, /images/2020/01/data-center@330w2x.webp 660w, /images/2020/01/data-center@545w.webp 545w, /images/2020/01/data-center@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/01/data-center@730w.jpg 730w, /images/2020/01/data-center@730w2x.jpg 1460w, /images/2020/01/data-center@610w.jpg 610w, /images/2020/01/data-center@610w2x.jpg 1220w, /images/2020/01/data-center@450w.jpg 450w, /images/2020/01/data-center@450w2x.jpg 900w, /images/2020/01/data-center@330w.jpg 330w, /images/2020/01/data-center@330w2x.jpg 660w, /images/2020/01/data-center@545w.jpg 545w, /images/2020/01/data-center@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/01/data-center.jpg" alt="Refresh your EC2 knowledge" title="Refresh your EC2 knowledge"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/12-ec2-instances-2-0-time-to-update-your-toolbox/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>But there are still two approaches when it comes to managing EC2 instances: mutable and immutable.</p><p>A <strong>mutable</strong> EC2 instance is created once and then lives for many years. Humans log on to the machine (e.g., via SSH or RDP) and do their work. OS updates are applied to the running system; new packages are installed from time to time; configuration files are modified when needed. Deployments happen while the EC2 instance is running.</p><p>An <strong>immutable</strong> EC2 instance is never changed after creation. If you want to update the OS, you create a new EC2 instance that starts from a fresher image (AMI). If new packages are needed, a new AMI is created that contains those packages. If a new deployment is necessary, a new AMI is built and rolled out be replacing the EC2 instances. The EC2 instance is ephemeral and must not be used to persists data!</p><p>In this blog post, I will focus on the <strong>mutable</strong> approach and show you how to solve everyday challenges with the tools and features that AWS provides in 2020:</p><ul><li>Patching</li><li>Backup and Restore</li><li>Remote Access</li><li>Software Deployments</li><li>Monitoring</li><li>Logs</li><li>Single Point of Failure</li></ul><blockquote><p>If you prefer the immutable approach, <a href="https://www.packer.io/" target="_blank" rel="noopener">Packer by HashiCorp</a> is still the best approach to create AMIs.</p></blockquote><p>You can find a link to a CloudFormation template with the implementation of all best practices at the end of the article.</p><h2 id="Patching"><a href="#Patching" class="headerlink" title="Patching"></a>Patching</h2><p>As soon as you launch an EC2 instance, you have to ask yourself one question: How can I keep this machine up-to-date? The best option today is provided by <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/what-is-systems-manager.html" target="_blank" rel="noopener">AWS Systems Manager (SSM)</a>. A combination of the following capabilities (aka Patch Manager) allows us to patch EC2 instances during a predefined window in a configurable way:</p><ul><li><a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/about-patch-baselines.html" target="_blank" rel="noopener">Patch Baseline</a>: Defines which patches are approved for installation on your instance (e.g., install critical patches 7 days after they are releases).</li><li><a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/patch-manager-ssm-documents.html" target="_blank" rel="noopener">Document <code>AWS-RunPatchBaseline</code></a>: The script that installs the patches approved by the baseline.</li><li><a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-maintenance.html" target="_blank" rel="noopener">Maintenance Window</a>: Executs the document on a set of EC2 instances within a recurring time window.</li></ul><p>The default patch baseline for Amazon Linux 2 looks like this:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/01/ssm-patch-baseline-al2@730w.webp 730w, /images/2020/01/ssm-patch-baseline-al2@730w2x.webp 1460w, /images/2020/01/ssm-patch-baseline-al2@610w.webp 610w, /images/2020/01/ssm-patch-baseline-al2@610w2x.webp 1220w, /images/2020/01/ssm-patch-baseline-al2@450w.webp 450w, /images/2020/01/ssm-patch-baseline-al2@450w2x.webp 900w, /images/2020/01/ssm-patch-baseline-al2@330w.webp 330w, /images/2020/01/ssm-patch-baseline-al2@330w2x.webp 660w, /images/2020/01/ssm-patch-baseline-al2@545w.webp 545w, /images/2020/01/ssm-patch-baseline-al2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/01/ssm-patch-baseline-al2@730w.png 730w, /images/2020/01/ssm-patch-baseline-al2@730w2x.png 1460w, /images/2020/01/ssm-patch-baseline-al2@610w.png 610w, /images/2020/01/ssm-patch-baseline-al2@610w2x.png 1220w, /images/2020/01/ssm-patch-baseline-al2@450w.png 450w, /images/2020/01/ssm-patch-baseline-al2@450w2x.png 900w, /images/2020/01/ssm-patch-baseline-al2@330w.png 330w, /images/2020/01/ssm-patch-baseline-al2@330w2x.png 660w, /images/2020/01/ssm-patch-baseline-al2@545w.png 545w, /images/2020/01/ssm-patch-baseline-al2@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/01/ssm-patch-baseline-al2.png" alt="Default Patch Baseline for Amazon Linux 2" title="Default Patch Baseline for Amazon Linux 2"></picture></p><p>The maintenance window is configured to run every day at 12:35 UTC (this is one of the few places in AWS where you can set your timezone!)</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/01/ssm-maintenance-window@730w.webp 730w, /images/2020/01/ssm-maintenance-window@730w2x.webp 1460w, /images/2020/01/ssm-maintenance-window@610w.webp 610w, /images/2020/01/ssm-maintenance-window@610w2x.webp 1220w, /images/2020/01/ssm-maintenance-window@450w.webp 450w, /images/2020/01/ssm-maintenance-window@450w2x.webp 900w, /images/2020/01/ssm-maintenance-window@330w.webp 330w, /images/2020/01/ssm-maintenance-window@330w2x.webp 660w, /images/2020/01/ssm-maintenance-window@545w.webp 545w, /images/2020/01/ssm-maintenance-window@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/01/ssm-maintenance-window@730w.png 730w, /images/2020/01/ssm-maintenance-window@730w2x.png 1460w, /images/2020/01/ssm-maintenance-window@610w.png 610w, /images/2020/01/ssm-maintenance-window@610w2x.png 1220w, /images/2020/01/ssm-maintenance-window@450w.png 450w, /images/2020/01/ssm-maintenance-window@450w2x.png 900w, /images/2020/01/ssm-maintenance-window@330w.png 330w, /images/2020/01/ssm-maintenance-window@330w2x.png 660w, /images/2020/01/ssm-maintenance-window@545w.png 545w, /images/2020/01/ssm-maintenance-window@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/01/ssm-maintenance-window.png" alt="Maintenance Window" title="Maintenance Window"></picture></p><p>You also get full insights into the executions of the maintenance window executions.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/01/ssm-maintenance-window-executions@730w.webp 730w, /images/2020/01/ssm-maintenance-window-executions@730w2x.webp 1460w, /images/2020/01/ssm-maintenance-window-executions@610w.webp 610w, /images/2020/01/ssm-maintenance-window-executions@610w2x.webp 1220w, /images/2020/01/ssm-maintenance-window-executions@450w.webp 450w, /images/2020/01/ssm-maintenance-window-executions@450w2x.webp 900w, /images/2020/01/ssm-maintenance-window-executions@330w.webp 330w, /images/2020/01/ssm-maintenance-window-executions@330w2x.webp 660w, /images/2020/01/ssm-maintenance-window-executions@545w.webp 545w, /images/2020/01/ssm-maintenance-window-executions@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/01/ssm-maintenance-window-executions@730w.png 730w, /images/2020/01/ssm-maintenance-window-executions@730w2x.png 1460w, /images/2020/01/ssm-maintenance-window-executions@610w.png 610w, /images/2020/01/ssm-maintenance-window-executions@610w2x.png 1220w, /images/2020/01/ssm-maintenance-window-executions@450w.png 450w, /images/2020/01/ssm-maintenance-window-executions@450w2x.png 900w, /images/2020/01/ssm-maintenance-window-executions@330w.png 330w, /images/2020/01/ssm-maintenance-window-executions@330w2x.png 660w, /images/2020/01/ssm-maintenance-window-executions@545w.png 545w, /images/2020/01/ssm-maintenance-window-executions@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/01/ssm-maintenance-window-executions.png" alt="Maintenance Window executions" title="Maintenance Window executions"></picture></p><p>Use CloudWatch Event Rules to subscribe to failures. Our <a href="https://marbot.io/" target="_blank" rel="noopener">Slack bot marbot can set up the CloudWatch Event Rules for you</a>.</p><h2 id="Backup-and-Restore"><a href="#Backup-and-Restore" class="headerlink" title="Backup and Restore"></a>Backup and Restore</h2><p>Mutable EC2 instances likely contain data that needs to be backed up. The best way to perform backups of EC2 instances is <a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/whatisbackup.html" target="_blank" rel="noopener">AWS Backup</a>. AWS Backup allows us to backup EC2 instances during a predefined window and manages the lifecycle of a backup as well (e.g., delete backups after 30 days).</p><p>The following screenshot shows a list of daily backups. You can restore any of these backups right through AWS Backup.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/01/backup-backups@730w.webp 730w, /images/2020/01/backup-backups@730w2x.webp 1460w, /images/2020/01/backup-backups@610w.webp 610w, /images/2020/01/backup-backups@610w2x.webp 1220w, /images/2020/01/backup-backups@450w.webp 450w, /images/2020/01/backup-backups@450w2x.webp 900w, /images/2020/01/backup-backups@330w.webp 330w, /images/2020/01/backup-backups@330w2x.webp 660w, /images/2020/01/backup-backups@545w.webp 545w, /images/2020/01/backup-backups@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/01/backup-backups@730w.png 730w, /images/2020/01/backup-backups@730w2x.png 1460w, /images/2020/01/backup-backups@610w.png 610w, /images/2020/01/backup-backups@610w2x.png 1220w, /images/2020/01/backup-backups@450w.png 450w, /images/2020/01/backup-backups@450w2x.png 900w, /images/2020/01/backup-backups@330w.png 330w, /images/2020/01/backup-backups@330w2x.png 660w, /images/2020/01/backup-backups@545w.png 545w, /images/2020/01/backup-backups@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/01/backup-backups.png" alt="AWS Backup backups" title="AWS Backup backups"></picture></p><p>You also get full insights into the backup jobs.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/01/backup-jobs@730w.webp 730w, /images/2020/01/backup-jobs@730w2x.webp 1460w, /images/2020/01/backup-jobs@610w.webp 610w, /images/2020/01/backup-jobs@610w2x.webp 1220w, /images/2020/01/backup-jobs@450w.webp 450w, /images/2020/01/backup-jobs@450w2x.webp 900w, /images/2020/01/backup-jobs@330w.webp 330w, /images/2020/01/backup-jobs@330w2x.webp 660w, /images/2020/01/backup-jobs@545w.webp 545w, /images/2020/01/backup-jobs@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/01/backup-jobs@730w.png 730w, /images/2020/01/backup-jobs@730w2x.png 1460w, /images/2020/01/backup-jobs@610w.png 610w, /images/2020/01/backup-jobs@610w2x.png 1220w, /images/2020/01/backup-jobs@450w.png 450w, /images/2020/01/backup-jobs@450w2x.png 900w, /images/2020/01/backup-jobs@330w.png 330w, /images/2020/01/backup-jobs@330w2x.png 660w, /images/2020/01/backup-jobs@545w.png 545w, /images/2020/01/backup-jobs@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/01/backup-jobs.png" alt="AWS Backup jobs" title="AWS Backup jobs"></picture></p><p>Use <a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/sns-notifications.html" target="_blank" rel="noopener">AWS Backup Events</a> to subscribe to failures.</p><blockquote><p>Keep in mind that EC2 backups performed by AWS Backup are “crash consistent”. <a href="/review-aws-backup/">Writes not flushed to disk can cause data corruption</a>.</p></blockquote><h2 id="Remote-Access"><a href="#Remote-Access" class="headerlink" title="Remote Access"></a>Remote Access</h2><p>To modify a mutable EC2 instance, you likely want to open an SSH&#x2F;RDP connection to your instance. Remote access comes with several challenges:</p><ul><li>configuration of security groups</li><li>distribution of credentials</li><li>rotation of credentials</li><li>SSH client needs to be installed and configured on your machine</li></ul><p>The less painful approach is to use <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html" target="_blank" rel="noopener">AWS SSM Session Manager</a>. Session Manager is integrated into the AWS Management Console and can also be used in your terminal.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/01/ssm-session-manager@730w.webp 730w, /images/2020/01/ssm-session-manager@730w2x.webp 1460w, /images/2020/01/ssm-session-manager@610w.webp 610w, /images/2020/01/ssm-session-manager@610w2x.webp 1220w, /images/2020/01/ssm-session-manager@450w.webp 450w, /images/2020/01/ssm-session-manager@450w2x.webp 900w, /images/2020/01/ssm-session-manager@330w.webp 330w, /images/2020/01/ssm-session-manager@330w2x.webp 660w, /images/2020/01/ssm-session-manager@545w.webp 545w, /images/2020/01/ssm-session-manager@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/01/ssm-session-manager@730w.png 730w, /images/2020/01/ssm-session-manager@730w2x.png 1460w, /images/2020/01/ssm-session-manager@610w.png 610w, /images/2020/01/ssm-session-manager@610w2x.png 1220w, /images/2020/01/ssm-session-manager@450w.png 450w, /images/2020/01/ssm-session-manager@450w2x.png 900w, /images/2020/01/ssm-session-manager@330w.png 330w, /images/2020/01/ssm-session-manager@330w2x.png 660w, /images/2020/01/ssm-session-manager@545w.png 545w, /images/2020/01/ssm-session-manager@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/01/ssm-session-manager.png" alt="AWS Backup backups" title="AWS Backup backups"></picture></p><blockquote><p>Keep in mind that your IAM permissions now also manage who can become <a href="/aws-ssm-is-a-trojan-horse-fix-it-now/">root on any EC2 instance</a>. </p></blockquote><h2 id="Software-Deployments"><a href="#Software-Deployments" class="headerlink" title="Software Deployments"></a>Software Deployments</h2><p>Deploying a new software release is a risky task. Instead of uploading a new release to the EC2 instance manually, I recommend to using <a href="https://docs.aws.amazon.com/codedeploy/latest/userguide/welcome.html" target="_blank" rel="noopener">AWS CodeDeploy</a>. CodeDeploy helps you to deploy your software in an automated way with automatic rollback if things go wrong.</p><h2 id="Monitoring"><a href="#Monitoring" class="headerlink" title="Monitoring"></a>Monitoring</h2><p>A lot of useful information is published to CloudWatch by default:</p><ul><li>CPU utilization</li><li>Network IO</li><li>Disk IO</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/01/cloudwatch-cpu@730w.webp 730w, /images/2020/01/cloudwatch-cpu@730w2x.webp 1460w, /images/2020/01/cloudwatch-cpu@610w.webp 610w, /images/2020/01/cloudwatch-cpu@610w2x.webp 1220w, /images/2020/01/cloudwatch-cpu@450w.webp 450w, /images/2020/01/cloudwatch-cpu@450w2x.webp 900w, /images/2020/01/cloudwatch-cpu@330w.webp 330w, /images/2020/01/cloudwatch-cpu@330w2x.webp 660w, /images/2020/01/cloudwatch-cpu@545w.webp 545w, /images/2020/01/cloudwatch-cpu@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/01/cloudwatch-cpu@730w.png 730w, /images/2020/01/cloudwatch-cpu@730w2x.png 1460w, /images/2020/01/cloudwatch-cpu@610w.png 610w, /images/2020/01/cloudwatch-cpu@610w2x.png 1220w, /images/2020/01/cloudwatch-cpu@450w.png 450w, /images/2020/01/cloudwatch-cpu@450w2x.png 900w, /images/2020/01/cloudwatch-cpu@330w.png 330w, /images/2020/01/cloudwatch-cpu@330w2x.png 660w, /images/2020/01/cloudwatch-cpu@545w.png 545w, /images/2020/01/cloudwatch-cpu@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/01/cloudwatch-cpu.png" alt="AWS CloudWatch CPU utilization" title="AWS CloudWatch CPU utilization"></picture></p><p>What information is missing?</p><ul><li>Memory</li><li>Disk usage</li></ul><p>The missing metrics can be collected with the <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html" target="_blank" rel="noopener">Unified CloudWatch Agent</a> best <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/installing-cloudwatch-agent-ssm.html" target="_blank" rel="noopener">installed via SSM</a>.</p><p>Create CloudWatch Alarms to monitor if a metric reaches a threshold.</p><h2 id="Logs"><a href="#Logs" class="headerlink" title="Logs"></a>Logs</h2><p>Mutable EC2 instances are around for some time. You can search tough the logs as usual: Open a remote session and open the log files on your editor of choice.</p><p>If you want to centralize your logs, I recommend to ship them to CloudWatch Logs. The Unified CloudWatch Agent that you learned about before can pipe the logs from the EC2 instance to CloudWatch Logs. With CloudWatch Logs Insights, you can search and visualize the logs with ease.</p><h2 id="Single-Point-of-Failure"><a href="#Single-Point-of-Failure" class="headerlink" title="Single Point of Failure"></a>Single Point of Failure</h2><p>Remember that a single EC2 instance is always a single point of failure (SPOF). The risk of a failing hypervisor can be limited by configuring <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-recover.html" target="_blank" rel="noopener">automatic instance recovery</a>. Instance recovery does not protect your instance from Availability Zones outages.</p><blockquote><p>Keep in mind that the EC2 SLA does not cover single instances.</p></blockquote><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Managing a mutable EC2 instance comes with many responsibilities. In this post, I showed you how to solve everyday challenges by leveraging the latest and greatest capabilities of the AWS platform.</p><p>Find a full implementation codified into two CloudFormation templates (<code>al2-mutable-public.yaml</code> and <code>al2-mutable-private.yaml</code>) on Github: <a href="https://github.com/widdix/aws-cf-templates/tree/master/ec2" target="_blank" rel="noopener">https://github.com/widdix/aws-cf-templates/tree/master/ec2</a></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to secure your DevOps tools with ALB authentication?</title>
      <link>https://cloudonaut.io/how-to-secure-your-devops-tools-with-alb-authentication/</link>
      <description>Secure your DevOps tools by adding an extra layer of security: authentication provided by the Application Load Balancer (ALB)</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/alb/">alb</category>
      <category domain="https://cloudonaut.io/tag/cognito/">cognito</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-secure-your-devops-tools-with-alb-authentication/</guid>
      <pubDate>Tue, 21 Jan 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Are you hosting any DevOps tools like GitLab, Jenkins, Kibana, Grafana, or phpMyAdmin yourself? On the one hand, it is convenient to provide access to those tools via the Internet. On the other hand, those tools add high-risk attack vectors to your infrastructure. Therefore, I recommend securing your DevOps tools by adding an extra layer of security: authentication provided by the Application Load Balancer (ALB).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/01/lock@730w.webp 730w, /images/2020/01/lock@730w2x.webp 1460w, /images/2020/01/lock@610w.webp 610w, /images/2020/01/lock@610w2x.webp 1220w, /images/2020/01/lock@450w.webp 450w, /images/2020/01/lock@450w2x.webp 900w, /images/2020/01/lock@330w.webp 330w, /images/2020/01/lock@330w2x.webp 660w, /images/2020/01/lock@545w.webp 545w, /images/2020/01/lock@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/01/lock@730w.jpg 730w, /images/2020/01/lock@730w2x.jpg 1460w, /images/2020/01/lock@610w.jpg 610w, /images/2020/01/lock@610w2x.jpg 1220w, /images/2020/01/lock@450w.jpg 450w, /images/2020/01/lock@450w2x.jpg 900w, /images/2020/01/lock@330w.jpg 330w, /images/2020/01/lock@330w2x.jpg 660w, /images/2020/01/lock@545w.jpg 545w, /images/2020/01/lock@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/01/lock.jpg" alt="How to secure your DevOps tools with ALB Authentication?" title="How to secure your DevOps tools with ALB Authentication?"></picture></p><p>Use the ALB to make sure incoming requests are authenticated before they are forwarded to one of your DevOps tools. Right now, the ALB supports two authentication methods:</p><ul><li><strong>Cognito User Pool</strong> comes with a built-in user database and supports user federation (Google, Facebook, SAML, OICD, …) as well.</li><li><strong>OpenID Connect (OICD)</strong> integrates with any OICD-compliant identity provider.</li></ul><p>In short, the process works as follows:</p><ol><li>The engineer accesses Grafana by pointing the browser to <code>https://grafana.example.com</code>.</li><li>The ALB redirects the engineer to the login page provided by AWS Cognito User Pool.</li><li>The engineer authenticates with username, password, and optionally a one-time-password.</li><li>AWS Cognito User Pool redirects the engineer to <code>https://grafana.example.com</code>.</li><li>The ALB verifies the authentication information and forwards the request to one of the targets running Grafana.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/01/alb-authentication@730w.webp 730w, /images/2020/01/alb-authentication@730w2x.webp 1460w, /images/2020/01/alb-authentication@610w.webp 610w, /images/2020/01/alb-authentication@610w2x.webp 1220w, /images/2020/01/alb-authentication@450w.webp 450w, /images/2020/01/alb-authentication@450w2x.webp 900w, /images/2020/01/alb-authentication@330w.webp 330w, /images/2020/01/alb-authentication@330w2x.webp 660w, /images/2020/01/alb-authentication@545w.webp 545w, /images/2020/01/alb-authentication@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/01/alb-authentication@730w.png 730w, /images/2020/01/alb-authentication@730w2x.png 1460w, /images/2020/01/alb-authentication@610w.png 610w, /images/2020/01/alb-authentication@610w2x.png 1220w, /images/2020/01/alb-authentication@450w.png 450w, /images/2020/01/alb-authentication@450w2x.png 900w, /images/2020/01/alb-authentication@330w.png 330w, /images/2020/01/alb-authentication@330w2x.png 660w, /images/2020/01/alb-authentication@545w.png 545w, /images/2020/01/alb-authentication@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/01/alb-authentication.png" alt="ALB Authentication" title="ALB Authentication"></picture></p><p>In the following, you will learn how to add ALB authentication to protect your DevOps tools from all kinds of attacks. The implementations are intended for small organizations.</p><h2 id="Requirements"><a href="#Requirements" class="headerlink" title="Requirements"></a>Requirements</h2><p>Before you start, make sure the following requirements are met.</p><ul><li>A custom domain name pointing to your DevOps tool (e.g., grafana.example.com).</li><li>A valid SSL certificate (e.g., Amazon Certificate Manager) for the custom domain name.</li></ul><h2 id="Example-Cognito-User-Pool"><a href="#Example-Cognito-User-Pool" class="headerlink" title="Example: Cognito User Pool"></a>Example: Cognito User Pool</h2><p>The following CloudFormation template shows how to configure an ALB to authenticate incoming requests against a Cognito User Pool. The Cognito User Pool provides a separate user database for your DevOps tools. Check out the <code># inline comments</code> for details.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&#x27;ALB Authentication | Cognito | cloudonaut.io&#x27;</span></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">VpcId:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::VPC::Id&#x27;</span></span><br><span class="line">  <span class="attr">SubnetIds:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;List&lt;AWS::EC2::Subnet::Id&gt;&#x27;</span></span><br><span class="line">  <span class="attr">PublicDomainName:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;String&#x27;</span> <span class="comment"># the custom domain name (e.g., grafana.example.com)</span></span><br><span class="line">  <span class="attr">CertificateArn:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;String&#x27;</span> <span class="comment"># the ARN of a ACM certificate valid for the custom domain name</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">LoadBalancerSecurityGroup:</span> <span class="comment"># Security Group for Load Balancer allows incoming HTTPS requests</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::SecurityGroup&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">GroupDescription:</span> <span class="string">&#x27;Load Balancer&#x27;</span></span><br><span class="line">      <span class="attr">VpcId:</span> <span class="type">!Ref</span> <span class="string">VpcId</span></span><br><span class="line">      <span class="attr">SecurityGroupIngress:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">IpProtocol:</span> <span class="string">tcp</span></span><br><span class="line">        <span class="attr">FromPort:</span> <span class="number">443</span></span><br><span class="line">        <span class="attr">ToPort:</span> <span class="number">443</span></span><br><span class="line">        <span class="attr">CidrIp:</span> <span class="string">&#x27;0.0.0.0/0&#x27;</span></span><br><span class="line">  <span class="attr">LoadBalancer:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::ElasticLoadBalancingV2::LoadBalancer&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">SecurityGroups:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">LoadBalancerSecurityGroup</span></span><br><span class="line">      <span class="attr">Subnets:</span> <span class="type">!Ref</span> <span class="string">SubnetIds</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">application</span></span><br><span class="line">  <span class="attr">Listener:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::ElasticLoadBalancingV2::Listener&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Certificates:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">CertificateArn:</span> <span class="type">!Ref</span> <span class="string">CertificateArn</span></span><br><span class="line">      <span class="attr">DefaultActions:</span> <span class="comment"># 1. Authenticate against Cognito User Pool</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Type:</span> <span class="string">&#x27;authenticate-cognito&#x27;</span></span><br><span class="line">        <span class="attr">AuthenticateCognitoConfig:</span></span><br><span class="line">          <span class="attr">OnUnauthenticatedRequest:</span> <span class="string">&#x27;authenticate&#x27;</span> <span class="comment"># Redirect unauthenticated clients to Cognito login page</span></span><br><span class="line">          <span class="attr">Scope:</span> <span class="string">&#x27;openid&#x27;</span></span><br><span class="line">          <span class="attr">UserPoolArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;UserPool.Arn&#x27;</span></span><br><span class="line">          <span class="attr">UserPoolClientId:</span> <span class="type">!Ref</span> <span class="string">UserPoolClient</span></span><br><span class="line">          <span class="attr">UserPoolDomain:</span> <span class="type">!Ref</span> <span class="string">UserPoolDomain</span></span><br><span class="line">        <span class="attr">Order:</span> <span class="number">1</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Type:</span> <span class="string">forward</span> <span class="comment"># 2. Forward request to target group (e.g., EC2 instances)</span></span><br><span class="line">        <span class="attr">TargetGroupArn:</span> <span class="type">!Ref</span> <span class="string">TargetGroup</span></span><br><span class="line">        <span class="attr">Order:</span> <span class="number">2</span></span><br><span class="line">      <span class="attr">LoadBalancerArn:</span> <span class="type">!Ref</span> <span class="string">LoadBalancer</span></span><br><span class="line">      <span class="attr">Port:</span> <span class="number">443</span></span><br><span class="line">      <span class="attr">Protocol:</span> <span class="string">&#x27;HTTPS&#x27;</span></span><br><span class="line">  <span class="attr">TargetGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::ElasticLoadBalancingV2::TargetGroup&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Port:</span> <span class="number">80</span></span><br><span class="line">      <span class="attr">Protocol:</span> <span class="string">HTTP</span></span><br><span class="line">      <span class="attr">TargetType:</span> <span class="string">ip</span></span><br><span class="line">      <span class="attr">VpcId:</span> <span class="type">!Ref</span> <span class="string">VpcId</span></span><br><span class="line">  <span class="attr">UserPool:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Cognito::UserPool&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AdminCreateUserConfig:</span></span><br><span class="line">        <span class="attr">AllowAdminCreateUserOnly:</span> <span class="literal">true</span> <span class="comment"># Disable self-registration</span></span><br><span class="line">        <span class="attr">InviteMessageTemplate:</span></span><br><span class="line">          <span class="attr">EmailSubject:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;: temporary password&#x27;</span></span><br><span class="line">          <span class="attr">EmailMessage:</span> <span class="string">&#x27;Use the username &#123;username&#125; and the temporary password &#123;####&#125; to log in for the first time.&#x27;</span></span><br><span class="line">          <span class="attr">SMSMessage:</span> <span class="string">&#x27;Use the username &#123;username&#125; and the temporary password &#123;####&#125; to log in for the first time.&#x27;</span></span><br><span class="line">      <span class="attr">AutoVerifiedAttributes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">email</span></span><br><span class="line">      <span class="attr">UsernameAttributes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">email</span></span><br><span class="line">      <span class="attr">Policies:</span></span><br><span class="line">        <span class="attr">PasswordPolicy:</span></span><br><span class="line">          <span class="attr">MinimumLength:</span> <span class="number">16</span></span><br><span class="line">          <span class="attr">RequireLowercase:</span> <span class="literal">false</span></span><br><span class="line">          <span class="attr">RequireNumbers:</span> <span class="literal">false</span></span><br><span class="line">          <span class="attr">RequireSymbols:</span> <span class="literal">false</span></span><br><span class="line">          <span class="attr">RequireUppercase:</span> <span class="literal">false</span></span><br><span class="line">          <span class="attr">TemporaryPasswordValidityDays:</span> <span class="number">21</span></span><br><span class="line">  <span class="attr">UserPoolDomain:</span> <span class="comment"># Provides Cognito Login Page</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Cognito::UserPoolDomain&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">UserPoolId:</span> <span class="type">!Ref</span> <span class="string">UserPool</span></span><br><span class="line">      <span class="attr">Domain:</span> <span class="type">!Select</span> [<span class="number">2</span>, <span class="type">!Split</span> [<span class="string">&#x27;/&#x27;</span>, <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackId&#x27;</span>]] <span class="comment"># Generates a unique domain name</span></span><br><span class="line">  <span class="attr">UserPoolClient:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Cognito::UserPoolClient&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AllowedOAuthFlows:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">code</span> <span class="comment"># Required for ALB authentication</span></span><br><span class="line">      <span class="attr">AllowedOAuthFlowsUserPoolClient:</span> <span class="literal">true</span> <span class="comment"># Required for ALB authentication</span></span><br><span class="line">      <span class="attr">AllowedOAuthScopes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">email</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">openid</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">profile</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">aws.cognito.signin.user.admin</span></span><br><span class="line">      <span class="attr">CallbackURLs:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Sub</span> <span class="string">https://$&#123;PublicDomainName&#125;/oauth2/idpresponse</span> <span class="comment"># Redirects to the ALB</span></span><br><span class="line">      <span class="attr">GenerateSecret:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">SupportedIdentityProviders:</span> <span class="comment"># Optional: add providers for identity federation</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">COGNITO</span></span><br><span class="line">      <span class="attr">UserPoolId:</span> <span class="type">!Ref</span> <span class="string">UserPool</span></span><br></pre></td></tr></table></figure><p>What else is needed?</p><ol><li>Create a CNAME&#x2F;Alias record for your custom domain name (e.g., <code>grafana.example.com</code>) pointing to the ALB’s DNS name (e.g., <code>cloud-LoadB-MTNUUM0SEVMO-1111111111.eu-west-1.elb.amazonaws.com</code>).</li><li>Open the AWS Management Console and add a user to the Cognito User Pool created by CloudFormation.</li><li>Browse to your custom domain name (e.g., <code>grafana.example.com</code>).</li><li>Log in with the user you created in the penultimate step.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/01/login-cognito@730w.webp 730w, /images/2020/01/login-cognito@730w2x.webp 1460w, /images/2020/01/login-cognito@610w.webp 610w, /images/2020/01/login-cognito@610w2x.webp 1220w, /images/2020/01/login-cognito@450w.webp 450w, /images/2020/01/login-cognito@450w2x.webp 900w, /images/2020/01/login-cognito@330w.webp 330w, /images/2020/01/login-cognito@330w2x.webp 660w, /images/2020/01/login-cognito@545w.webp 545w, /images/2020/01/login-cognito@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/01/login-cognito@730w.png 730w, /images/2020/01/login-cognito@730w2x.png 1460w, /images/2020/01/login-cognito@610w.png 610w, /images/2020/01/login-cognito@610w2x.png 1220w, /images/2020/01/login-cognito@450w.png 450w, /images/2020/01/login-cognito@450w2x.png 900w, /images/2020/01/login-cognito@330w.png 330w, /images/2020/01/login-cognito@330w2x.png 660w, /images/2020/01/login-cognito@545w.png 545w, /images/2020/01/login-cognito@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/01/login-cognito.png" alt="Login with Cognito User Pool" title="Login with Cognito User Pool"></picture></p><p>By the way, it is possible to create users with CloudFormation as well. Alternatively, you might want to use an existing user database. One option to do so is OpenID Connect, which you will learn about next.</p><h2 id="Example-OpenID-Connect-OICD-with-Google"><a href="#Example-OpenID-Connect-OICD-with-Google" class="headerlink" title="Example: OpenID Connect (OICD) with Google"></a>Example: OpenID Connect (OICD) with Google</h2><p>The following CloudFormation template shows how to configure an ALB use Google user accounts to authenticate incoming requests. Check out the <code># inline comments</code> for details.</p><p>Before you proceed, you need to configure the OAuth consent screen of your application and create an OAuth Client. Open the <a href="https://console.developers.google.com/" target="_blank" rel="noopener">Google API Console</a> to do so.</p><blockquote><p><strong>Warning</strong> Choose user type <code>Internal</code> when configuring the OAuth consent screen. By doing so, only G Suite users within your organization are allowed to log in. When choosing <code>External</code> anyone with a Google account can log in.</p></blockquote><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&#x27;ALB Authentication | Google OICD | cloudonaut.io&#x27;</span></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">VpcId:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::VPC::Id&#x27;</span></span><br><span class="line">  <span class="attr">SubnetIds:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;List&lt;AWS::EC2::Subnet::Id&gt;&#x27;</span></span><br><span class="line">  <span class="attr">CertificateArn:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;String&#x27;</span></span><br><span class="line">  <span class="attr">GoogleClientId:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;String&#x27;</span> <span class="comment"># The OAuth Client ID from the Google API Console</span></span><br><span class="line">  <span class="attr">GoogleClientSecret:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;String&#x27;</span> <span class="comment"># The OAuth Client Secret from the Google API Console</span></span><br><span class="line">    <span class="attr">NoEcho:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">LoadBalancerSecurityGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::SecurityGroup&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">GroupDescription:</span> <span class="string">&#x27;Load Balancer&#x27;</span></span><br><span class="line">      <span class="attr">VpcId:</span> <span class="type">!Ref</span> <span class="string">VpcId</span></span><br><span class="line">      <span class="attr">SecurityGroupIngress:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">IpProtocol:</span> <span class="string">tcp</span></span><br><span class="line">        <span class="attr">FromPort:</span> <span class="number">443</span></span><br><span class="line">        <span class="attr">ToPort:</span> <span class="number">443</span></span><br><span class="line">        <span class="attr">CidrIp:</span> <span class="string">&#x27;0.0.0.0/0&#x27;</span></span><br><span class="line">  <span class="attr">LoadBalancer:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::ElasticLoadBalancingV2::LoadBalancer&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">SecurityGroups:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">LoadBalancerSecurityGroup</span></span><br><span class="line">      <span class="attr">Subnets:</span> <span class="type">!Ref</span> <span class="string">SubnetIds</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">application</span></span><br><span class="line">  <span class="attr">Listener:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::ElasticLoadBalancingV2::Listener&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Certificates:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">CertificateArn:</span> <span class="type">!Ref</span> <span class="string">CertificateArn</span></span><br><span class="line">      <span class="attr">DefaultActions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Type:</span> <span class="string">&#x27;authenticate-oidc&#x27;</span> <span class="comment"># 1. Authenticate with OICD</span></span><br><span class="line">        <span class="attr">AuthenticateOidcConfig:</span></span><br><span class="line">          <span class="attr">OnUnauthenticatedRequest:</span> <span class="string">&#x27;authenticate&#x27;</span></span><br><span class="line">          <span class="attr">Issuer:</span> <span class="string">&#x27;https://accounts.google.com&#x27;</span></span><br><span class="line">          <span class="attr">AuthorizationEndpoint:</span> <span class="string">&#x27;https://accounts.google.com/o/oauth2/v2/auth&#x27;</span></span><br><span class="line">          <span class="attr">TokenEndpoint:</span> <span class="string">&#x27;https://oauth2.googleapis.com/token&#x27;</span></span><br><span class="line">          <span class="attr">UserInfoEndpoint:</span> <span class="string">&#x27;https://openidconnect.googleapis.com/v1/userinfo&#x27;</span></span><br><span class="line">          <span class="attr">ClientId:</span> <span class="type">!Ref</span> <span class="string">GoogleClientId</span></span><br><span class="line">          <span class="attr">ClientSecret:</span> <span class="type">!Ref</span> <span class="string">GoogleClientSecret</span></span><br><span class="line">        <span class="attr">Order:</span> <span class="number">1</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Type:</span> <span class="string">forward</span> <span class="comment"># 2. Forward request to target group (e.g., EC2 instances)</span></span><br><span class="line">        <span class="attr">TargetGroupArn:</span> <span class="type">!Ref</span> <span class="string">TargetGroup</span></span><br><span class="line">        <span class="attr">Order:</span> <span class="number">2</span></span><br><span class="line">      <span class="attr">LoadBalancerArn:</span> <span class="type">!Ref</span> <span class="string">LoadBalancer</span></span><br><span class="line">      <span class="attr">Port:</span> <span class="number">443</span></span><br><span class="line">      <span class="attr">Protocol:</span> <span class="string">&#x27;HTTPS&#x27;</span></span><br><span class="line">  <span class="attr">TargetGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::ElasticLoadBalancingV2::TargetGroup&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Port:</span> <span class="number">80</span></span><br><span class="line">      <span class="attr">Protocol:</span> <span class="string">HTTP</span></span><br><span class="line">      <span class="attr">TargetType:</span> <span class="string">ip</span></span><br><span class="line">      <span class="attr">VpcId:</span> <span class="type">!Ref</span> <span class="string">VpcId</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>What else is needed?</p><ol><li>Create a CNAME&#x2F;Alias record for your custom domain name (e.g., <code>grafana.example.com</code>) pointing to the ALB’s DNS name (e.g., <code>cloud-LoadB-MTNUUM0SEVMO-1111111111.eu-west-1.elb.amazonaws.com</code>).</li><li>Browse to your custom domain name (e.g., <code>grafana.example.com</code>).</li><li>Log in with your Google account.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/01/login-google@730w.webp 730w, /images/2020/01/login-google@730w2x.webp 1460w, /images/2020/01/login-google@610w.webp 610w, /images/2020/01/login-google@610w2x.webp 1220w, /images/2020/01/login-google@450w.webp 450w, /images/2020/01/login-google@450w2x.webp 900w, /images/2020/01/login-google@330w.webp 330w, /images/2020/01/login-google@330w2x.webp 660w, /images/2020/01/login-google@545w.webp 545w, /images/2020/01/login-google@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/01/login-google@730w.png 730w, /images/2020/01/login-google@730w2x.png 1460w, /images/2020/01/login-google@610w.png 610w, /images/2020/01/login-google@610w2x.png 1220w, /images/2020/01/login-google@450w.png 450w, /images/2020/01/login-google@450w2x.png 900w, /images/2020/01/login-google@330w.png 330w, /images/2020/01/login-google@330w2x.png 660w, /images/2020/01/login-google@545w.png 545w, /images/2020/01/login-google@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/01/login-google.png" alt="Login with Google" title="Login with Google"></picture></p><p>See <a href="https://developers.google.com/identity/protocols/OpenIDConnect" target="_blank" rel="noopener">Google Identity Platform: Guides: OpenID Connect</a> to learn more.</p><h2 id="What-is-next"><a href="#What-is-next" class="headerlink" title="What is next?"></a>What is next?</h2><p>Be aware that the ALB only handles authentication. Anyone with a user account can log in and access your DevOps tools. The ALB does not offer additional authorization. Make sure you understand which users can log in when configuring an OpenID Connect provider. It might be anyone with a user account. Or only user accounts that belong to your organization (see the warning for OpenID Connect (OICD) with Google).</p><p>By default, your DevOps tool will not integrate with the ALB authentication. However, the ALB adds headers with the user information to all forwarded requests (e.g., <code>x-amzn-oidc-identity</code> contains the user name of the authenticated user). You might be able to configure your DevOps tool to use these headers for authentication and authorization. See <a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-authenticate-users.html" target="_blank" rel="noopener">Authenticate Users Using an Application Load Balancer</a> to learn more.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Use authentication on the load balancer to add another layer of security to your DevOps tools like GitLab, Jenkins, Kibana, Grafana, or phpMyAdmin. Doing so reduced the attack surface to your infrastructure. Implementing ALB authentication for small organizations is quite simple by using either Cognito User Pools or Google via OpenID Connect.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>10 Success Factors for Starting Your Cloud Journey</title>
      <link>https://cloudonaut.io/10-success-factors-for-starting-your-cloud-journey/</link>
      <description>Are you planning to start the cloud journey for your organization soon? Learn from others to turn your initiative into a huge success.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <category domain="https://cloudonaut.io/tag/transformation/">transformation</category>
      <guid isPermaLink="true">https://cloudonaut.io/10-success-factors-for-starting-your-cloud-journey/</guid>
      <pubDate>Wed, 15 Jan 2020 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Are you planning to start the cloud journey for your organization soon? Learn from others to turn your initiative into a huge success. Michael and I have accompanied medium-sized businesses and enterprises in their transformation projects and would like to share our learnings with you. We are consultants focusing 100% on Amazon Web Services (AWS). Our goal is technical excellence, as well as business outcomes.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/01/road@730w.webp 730w, /images/2020/01/road@730w2x.webp 1460w, /images/2020/01/road@610w.webp 610w, /images/2020/01/road@610w2x.webp 1220w, /images/2020/01/road@450w.webp 450w, /images/2020/01/road@450w2x.webp 900w, /images/2020/01/road@330w.webp 330w, /images/2020/01/road@330w2x.webp 660w, /images/2020/01/road@545w.webp 545w, /images/2020/01/road@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/01/road@730w.jpg 730w, /images/2020/01/road@730w2x.jpg 1460w, /images/2020/01/road@610w.jpg 610w, /images/2020/01/road@610w2x.jpg 1220w, /images/2020/01/road@450w.jpg 450w, /images/2020/01/road@450w2x.jpg 900w, /images/2020/01/road@330w.jpg 330w, /images/2020/01/road@330w2x.jpg 660w, /images/2020/01/road@545w.jpg 545w, /images/2020/01/road@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/01/road.jpg" alt="10 Success Factors for Starting Your Cloud Journey" title="10 Success Factors for Starting Your Cloud Journey"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/11-10-success-factors-for-starting-your-cloud-journey/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="Explain-the-“why”"><a href="#Explain-the-“why”" class="headerlink" title="Explain the “why”"></a>Explain the “why”</h2><p>Why is adopting cloud technologies important for your business? Explain the reasons for starting your cloud journey to all parts of your organization.</p><p>A few examples:</p><ul><li><strong>Global Infrastructure</strong>: AWS provides data centers all over the world.</li><li><strong>Automation</strong>: the tools offered by AWS allow you to increase the level of automation.</li><li>** Out-sourcing**: decrease friction and enhance the quality of data center operations.</li><li><strong>Technology</strong>: benefit from stunning services like Big Data, Machine Learning, and many more.</li><li><strong>Pay-per-Use</strong>: reduce costs by scaling infrastructure automatically.</li></ul><h2 id="Form-a-team-of-subject-matter-experts"><a href="#Form-a-team-of-subject-matter-experts" class="headerlink" title="Form a team of subject-matter experts"></a>Form a team of subject-matter experts</h2><p>Start your cloud journey by forming a team of subject-matter experts for AWS. Diversity matters! Therefore, put together a group of people from different backgrounds.</p><p>Look for the following when choosing the candidates:</p><ul><li>Curious about new technology in general and cloud in particular.</li><li>Deep knowledge about on-premises infrastructure or applications.</li><li>Open-minded and creative when it comes to solving challenges.</li><li>Speaking a programming language and expertise in Linux&#x2F;UNIX is a plus but not required.</li></ul><p>Make sure all team members have a reason to stay. Great opportunities and head hunters are waiting everywhere.</p><p>Change the way you think about out-sourcing your IT infrastructure: out-source the boring part of managing data centers and provide essential services but in-source the knowledge about your systems.</p><p>Taking cell division as an example is an option to spread the knowledge into your organization later. </p><h2 id="Invest-in-training-and-coaching"><a href="#Invest-in-training-and-coaching" class="headerlink" title="Invest in training and coaching"></a>Invest in training and coaching</h2><p>AWS offers a wide variety of services and features. Therefore, it is a challenge to get started. Investing in training and coaching pays 1,000 times as you avoid costly mistakes.</p><p>Start with offering essential learning experiences, for example:</p><ul><li>Classroom Training: Amazon’s <a href="https://www.aws.training/SessionSearch?pageNumber=1&courseId=10002" target="_blank" rel="noopener">Architecting on AWS</a> </li><li>Workshops: our <a href="https://widdix.net/" target="_blank" rel="noopener">customized workshops</a></li><li>Books: our book <a href="https://www.manning.com/books/amazon-web-services-in-action" target="_blank" rel="noopener">Amazon Web Services in Action</a></li><li>Online Courses: A Cloud Guru’s AWS Certified Solutions Architect Associate 2020</li></ul><p>On top of that, you should look for a technical coach that accompanies your team for the first 12-24 months. Make sure the coach does guide your team but does not take over the implementation. By the way, <a href="https://widdix.net/" target="_blank" rel="noopener">let us know if you are looking for a coach</a>. We have successfully fulfilled that role in various projects.</p><h2 id="Keep-the-network-simple"><a href="#Keep-the-network-simple" class="headerlink" title="Keep the network simple"></a>Keep the network simple</h2><p>There are two options to connect your on-premises network with AWS: site-to-site VPN or dedicated network connection. However, doing so increases the complexity of your cloud journey a lot. And it get’s even worse, having to deal with on-premises networking adds a lot of dependencies as well. Think of firewall changes, routing, and peering.</p><p>Therefore, start with projects that provide Internet-facing endpoints only. For example, a website or a REST API. If data exchange between on-premises systems and systems in the cloud is necessary implement public endpoints with strong encryption and authentication.</p><h2 id="Focus-on-security-from-the-beginning"><a href="#Focus-on-security-from-the-beginning" class="headerlink" title="Focus on security from the beginning"></a>Focus on security from the beginning</h2><p>Security should be a priority from the beginning. It is often hard to undo wrong decisions caused by time pressure or missing knowledge later. Therefore, make sure to ask an expert in AWS security for help in the beginning.</p><p>Focus on the basics instead of buying snake oil:</p><ol><li>Implement the Least Privilege principle for administrating and accessing AWS resources.</li><li>Automate rolling-out virtual machine images for patching vulnerabilities.</li><li>Secure the private network in the cloud (e.g., firewall rules).</li></ol><p>Typically, the first point is the hardest. Make sure to build strong expertise in <em>AWS Identity and Access Management (IAM)</em>.</p><h2 id="Managed-services-first"><a href="#Managed-services-first" class="headerlink" title="Managed services first!"></a>Managed services first!</h2><p>Leverage the managed services offered by AWS:</p><ul><li>RDS offers managed databases (PostgreSQL, Oracle, MS SQL, MySQL, and MariaDB).</li><li>Elasticache offers managed in-memory databases (memcached and Redis).</li><li>Elasticsearch offers a managed search and analytics engine.</li><li>ELB offers managed load balancers.</li></ul><p>Push back the tendency to replicate the on-premises infrastructure with virtual machines (EC2) on AWS instead of using the managed services. There might be limitations of the managed services that do not allow a simple lift&amp;shift of an application. In many cases, it is worth to invest some time and money to modify the app instead of replacing a manged service with self-managed services running on virtual machines.</p><p>And please do not build your Kubernetes or OpenShift cluster. Use ECS or EKS instead.</p><h2 id="Leverage-Infrastructure-as-Code"><a href="#Leverage-Infrastructure-as-Code" class="headerlink" title="Leverage Infrastructure as Code"></a>Leverage Infrastructure as Code</h2><p>Automation is a game-changer! Speed and quality will increase a lot when adapting Infrastructure as Code. The effort to master and implement Infrastructure as Code will pay off many times in the future.</p><p>In my opinion, the only valid tool choices are:</p><ul><li><a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html" target="_blank" rel="noopener">AWS CloudFormation</a></li><li><a href="https://www.terraform.io/" target="_blank" rel="noopener">HashiCorp Terraform</a></li><li><a href="https://docs.aws.amazon.com/cdk/latest/guide/home.html" target="_blank" rel="noopener">AWS Cloud Development Kit (CDK)</a></li><li><a href="https://www.pulumi.com/" target="_blank" rel="noopener">pulumi</a></li></ul><p>Leverage production-ready modules to avoid having to reinvent the wheel constantly. Check out our CloudFormation <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">templates</a> or <a href="https://github.com/cfn-modules/docs" target="_blank" rel="noopener">modules</a>, for example.</p><h2 id="Level-up-by-using-containers"><a href="#Level-up-by-using-containers" class="headerlink" title="Level up by using containers"></a>Level up by using containers</h2><p>Do not only automate managing your cloud infrastructure but the insides of your machines as well. Switching from virtual machines to containers allows you to do so. Also, AWS comes with first-class support for containers.</p><p>Therefore, we recommend using containers for Lift&amp;Shift projects as well as for greenfield projects.</p><p>In my opinion, the best choice to run containers on AWS is to use ECS and Fargate.</p><h2 id="Build-deployment-pipelines"><a href="#Build-deployment-pipelines" class="headerlink" title="Build deployment pipelines"></a>Build deployment pipelines</h2><p>There is one more thing that needs automation: the process of deploying changes to your cloud infrastructure and applications. Again, automation will increase quality and speed and decrease risks and failures. On top of that, you will be able to spread the knowledge about how to deploy a change.</p><ol><li>Use Git to store the source code of the application as well as the Infrastructure as Code.</li><li>Use a CI&#x2F;CD tool to deploy the application and infrastructure automatically.</li><li>Implement automated tests (e.g., Unit Tests, Integration Tests, Infrastructure Tests, …).</li></ol><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Start with the why. Next, try to create as much freedom for your cloud journey as possible. Which means, implement barriers to the on-premises way of doing things. Also invest heavily in training, coaching, and automation.</p><p>All the best for starting your cloud journey!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Cost Optimization 101</title>
      <link>https://cloudonaut.io/aws-cost-optimization-101/</link>
      <description>The beginning of the year is the perfect time to clean up and optimize. This also applies to your AWS bill. I've composed practical tips on how to cut costs with small effort.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/costs/">costs</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-cost-optimization-101/</guid>
      <pubDate>Thu, 09 Jan 2020 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The beginning of the year is the perfect time to clean up and optimize. This also applies to your AWS bill. I’ve composed practical tips on how to cut costs with small effort.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/01/cut-costs@730w.webp 730w, /images/2020/01/cut-costs@730w2x.webp 1460w, /images/2020/01/cut-costs@610w.webp 610w, /images/2020/01/cut-costs@610w2x.webp 1220w, /images/2020/01/cut-costs@450w.webp 450w, /images/2020/01/cut-costs@450w2x.webp 900w, /images/2020/01/cut-costs@330w.webp 330w, /images/2020/01/cut-costs@330w2x.webp 660w, /images/2020/01/cut-costs@545w.webp 545w, /images/2020/01/cut-costs@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/01/cut-costs@730w.jpg 730w, /images/2020/01/cut-costs@730w2x.jpg 1460w, /images/2020/01/cut-costs@610w.jpg 610w, /images/2020/01/cut-costs@610w2x.jpg 1220w, /images/2020/01/cut-costs@450w.jpg 450w, /images/2020/01/cut-costs@450w2x.jpg 900w, /images/2020/01/cut-costs@330w.jpg 330w, /images/2020/01/cut-costs@330w2x.jpg 660w, /images/2020/01/cut-costs@545w.jpg 545w, /images/2020/01/cut-costs@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/01/cut-costs.jpg" alt="AWS Cost Optimization 101" title="AWS Cost Optimization 101"></picture></p><p>The good thing about AWS: you typically pay per usage. The bad thing about AWS: understanding the pricing models of all the AWS services is hard. Little self-promotion: our consulting firm <a href="https://widdix.net/" target="_blank" rel="noopener">widdix</a> offers analyzing and optimizing your AWS bill.</p><p>The following mind map provides guidance for reducing costs based on my experience from analyzing and reducing AWS bills for various clients. <a href="/images/2020/01/aws-cost-optimization.pdf">Download the mind map as a PDF file</a> for better readability.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2020/01/aws-cost-optimization@730w.webp 730w, /images/2020/01/aws-cost-optimization@730w2x.webp 1460w, /images/2020/01/aws-cost-optimization@610w.webp 610w, /images/2020/01/aws-cost-optimization@610w2x.webp 1220w, /images/2020/01/aws-cost-optimization@450w.webp 450w, /images/2020/01/aws-cost-optimization@450w2x.webp 900w, /images/2020/01/aws-cost-optimization@330w.webp 330w, /images/2020/01/aws-cost-optimization@330w2x.webp 660w, /images/2020/01/aws-cost-optimization@545w.webp 545w, /images/2020/01/aws-cost-optimization@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2020/01/aws-cost-optimization@730w.png 730w, /images/2020/01/aws-cost-optimization@730w2x.png 1460w, /images/2020/01/aws-cost-optimization@610w.png 610w, /images/2020/01/aws-cost-optimization@610w2x.png 1220w, /images/2020/01/aws-cost-optimization@450w.png 450w, /images/2020/01/aws-cost-optimization@450w2x.png 900w, /images/2020/01/aws-cost-optimization@330w.png 330w, /images/2020/01/aws-cost-optimization@330w2x.png 660w, /images/2020/01/aws-cost-optimization@545w.png 545w, /images/2020/01/aws-cost-optimization@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2020/01/aws-cost-optimization.png" alt="Mind Map: AWS Cost Optimization" title="Mind Map: AWS Cost Optimization"></picture></p><p>Let me start with the process of analyzing your AWS bill.</p><ol><li>Use the Cost Explorer to aggregate costs by service.</li><li>Which services cause the highest costs?</li><li>Do the costs per service match with your assumptions? For example, it is quite unlikely that you want to spend 2x more on CloudWatch than on EC2.</li><li>Visualize your spending among the last 12 months.</li><li>Are costs increasing by a similar amount each month for a specific service? If so, you might be piling up unused resources (e.g., EBS snapshots).</li><li>Any high cost increases not caused by changes to your cloud infrastructure?</li><li>Does the cost increase per month match with your revenue numbers?</li><li>Open your AWS bill for the last three months and drill down into the details.</li><li>Which resources of a service cause high costs? Justify the costs.</li><li>Do the costs per service match with your estimations?</li><li>Are there any hints for expenses caused by unused resources?</li></ol><p>Primarily, you should watch out for the following aspects.</p><h2 id="EC2"><a href="#EC2" class="headerlink" title="EC2"></a>EC2</h2><p>Purchase Savings Plans for baseline capacity. The deal is simple: you commit to a monthly usage of computing capacity, AWS grants a discount on the on-demand price. Read <a href="https://cloudonaut.io/reduce-your-aws-bill-with-savings-plans/">Reduce your AWS bill with Savings Plans</a> to learn more.</p><p>Identify and terminate unused instances. Boring but very efficient.</p><p>Verify that instance type still reflects the current workload. Check the CloudWatch metrics for CPU, Storage I&#x2F;O, and networking to come up with a first guess. After that, experiment to test your assumption.</p><p>Verify that the maximum I&#x2F;O performance of the instance matches the performance of your EBS volumes. Remember that there is a network between your EC2 instance and your EBS volume. The instance type limits the maximum throughput to all attached EBS volumes. Make sure that matches with the configuration of your EBS volumes, where the volume type and provisioned IOPS define the maximum throughput. See <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html#ebs-volume-characteristics" target="_blank" rel="noopener">EBS Volume Types</a> and <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-optimized.html" target="_blank" rel="noopener">EBS–Optimized Instances</a> for more details.</p><p>Use Spot Instances for stateless and non-production workloads. Keep in mind that AWS might terminate your spot instance anytime before using them in production. Read <a href="https://cloudonaut.io/3-simple-ways-of-saving-up-to-90-of-ec2-costs/">3 simple ways of saving up to 90% of EC2 costs</a> to learn more.</p><p>Switching to the latest instance types often cuts costs. For example, migrating from <code>m4.large</code> to <code>m5.large?</code> reduces the costs by 4%. On top of that, you get a small performance improvement as well.</p><p>Using AMD- or ARM-based instance types in favor of Intel-based instance types is also worth a look.</p><ul><li>Savings potential for AMD-based instance types (e.g., <code>t3a</code>, <code>m5a</code>, and <code>r5a</code>):  10%</li><li>Savings potential for ARM-based instance types: 40%</li></ul><p>On the one hand, it is a little bit more work to migrate to an Open Source operating system. Our operating system of choice on AWS is Amazon Linux, a free of charge Linux image maintained by Amazon. On the other hand, the cost savings are enormous.</p><h2 id="EBS"><a href="#EBS" class="headerlink" title="EBS"></a>EBS</h2><p>Commonly, EBS snapshots are piling up. Therefore, delete snapshots created to backup data that are no longer needed. Also, check whether your backup solution deletes old snapshots. Have you written a backup solution with Lambda? Replace it with AWS Backup. Read <a href="https://cloudonaut.io/review-aws-backup/">Review: AWS Backup - A centralized place for managing backups?</a> to learn more.</p><p>Delete snapshots belonging to unused AMIs. A typical waste management problem when your deployment pipeline builds AMIs for every commit.</p><p>Search for unused volumes and delete them. Check whether someone (script, Kubernetes, …) creates volumes automatically and does not clean them up.</p><h2 id="S3"><a href="#S3" class="headerlink" title="S3"></a>S3</h2><p>It’s obvious but still valid: delete unnecessary objects and buckets.</p><p>Consider using S3 Intelligent Tiering. Or, if you need to archive data, check out Glacier Deep Archive. Read <a href="https://cloudonaut.io/6-new-ways-to-reduce-your-aws-bill-with-little-effort/">6 new ways to reduce your AWS bill with little effort</a> to learn more.</p><p>Configure life-cycle policies define a retention period for objects. Read <a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lifecycle-mgmt.html" target="_blank" rel="noopener">Object Lifecycle Management</a> to learn more.</p><h2 id="VPC"><a href="#VPC" class="headerlink" title="VPC"></a>VPC</h2><p>Check costs for NAT gateways. I’ve seen scenarios where placing EC2 instances into a public subnet was the only option to avoid horrendous traffic costs.</p><p>Also, create VPC endpoints for S3 and DynamoDB. Doing so reduces the traffic processed by your NAT gateways.</p><p>Traffic within the VPC is free? No, it is not. Traffic between Availability Zones is charged at $0.02&#x2F;GB. Check the traffic costs and think about making changes to your architecture when necessary.</p><p>A VPC endpoint costs $7.20 per AZ and month in US East (N. Virginia). Adding VPC endpoints for ten services in 3 AZs costs  $216 per month. And the processed data is not even included. In general, avoid VPC endpoints when possible. Read <a href="https://cloudonaut.io/6-new-ways-to-reduce-your-aws-bill-with-little-effort/">6 new ways to reduce your AWS bill with little effort</a> to learn more.</p><h2 id="CloudWatch"><a href="#CloudWatch" class="headerlink" title="CloudWatch"></a>CloudWatch</h2><p>Configure a retention period for all log groups. For example, delete log messages after 30 days.</p><p>Check costs for metrics API calls caused by 3rd party tools (e.g., Prometheus, Datadog, …). Make sure you are only polling metrics that you need and set the polling interval to 5 minutes. </p><p>Each CloudWatch alarms cost $0.10 per month; a CloudWatch dashboard costs $3.00 per month. Therefore, delete needless alarms and dashboards.</p><p>Identify unnecessary custom metrics. For example, configure the CloudWatch Agent only to send metrics that you need for monitoring.</p><p>Check costs for log ingestion. It might be necessary to reduce or filter the log events that you send to CloudWatch.</p><h2 id="Serverless"><a href="#Serverless" class="headerlink" title="Serverless"></a>Serverless</h2><p>Optimize memory configuration for Lambda functions. Check out <a href="https://github.com/alexcasalboni/aws-lambda-power-tuning" target="_blank" rel="noopener">AWS Lambda Power Tuning</a>.</p><p>Use Provisioned Concurrency to reduce costs for high traffic Lambda functions and evaluate HTTP APIs as an alternative to API Gateway. Read <a href="https://cloudonaut.io/reinvent-recap-2019-aws-announcements/">All you need to know about AWS re:Invent in 2019</a> to learn more.</p><h2 id="ECS"><a href="#ECS" class="headerlink" title="ECS"></a>ECS</h2><p>Using Fargate allows you to get rid of an overprovisioned fleet of EC2 instances. If using Fargate is not an option, check out the ECS Capacity Provider to scale the fleet of EC2 instances easily. Read <a href="https://cloudonaut.io/ecs-vs-fargate-whats-the-difference/">ECS vs. Fargate: What’s the difference?</a> to learn more.</p><p>Purchase Savings Plans for Fargate. Read <a href="https://cloudonaut.io/reduce-your-aws-bill-with-savings-plans/">Reduce your AWS bill with Savings Plans</a> to learn more.</p><p>Use Fargate Spot for non-production workloads. Read <a href="https://aws.amazon.com/blogs/aws/aws-fargate-spot-now-generally-available/" target="_blank" rel="noopener">AWS Fargate Spot Now Generally Available</a> to learn more.</p><h2 id="RDS"><a href="#RDS" class="headerlink" title="RDS"></a>RDS</h2><p>Enable RDS Storage Auto Scaling instead of over-provisioning storage capacity.</p><p>Consider switching to Aurora Serverless for unsteady. Check out <a href="https://cloudonaut.io/review-amazon-aurora-serverless/">our review</a> to learn about the pros and cons.</p><p>And don’t forget to verify that the instance type of your database still reflects the current workload. Also, check that the maximum I&#x2F;O performance of the compute layer matches the storage layer.</p><p>License costs for traditional database systems are tremendous. Migrating to an Open Source database should be on your long or short term TODO list.</p><h2 id="DynamoDB"><a href="#DynamoDB" class="headerlink" title="DynamoDB"></a>DynamoDB</h2><p>Switch to On-demand capacity mode for unsteady workloads. Read <a href="https://cloudonaut.io/cost-savings-with-dynamodb-on-demand-essons-learned/">Cost savings with DynamoDB On-Demand: Lessons learned</a> to learn more.</p><p>If on-demand capacity is not for you, use auto-scaling to adjust the provisioned capacity to the workload.</p><h2 id="Elasticsearch"><a href="#Elasticsearch" class="headerlink" title="Elasticsearch"></a>Elasticsearch</h2><p>Make use of Reserved Instances were planning one year is feasible.</p><p>Evaluate UltraWarm tier (Preview) to retain large amounts of data at lower costs. Read <a href="https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/ultrawarm.html" target="_blank" rel="noopener">UltraWarm for Amazon Elasticsearch Service</a> to learn more.</p><h2 id="Route-53"><a href="#Route-53" class="headerlink" title="Route 53"></a>Route 53</h2><p>Increase TTL for records to reduce queries.</p><p>Are you using Route 53 resolver endpoints? You typically pay $270 per month for endpoints in 3 AZs. Therefore, you might want to consolidate your resolver endpoints from multiple AWS accounts.</p><h2 id="CloudFront"><a href="#CloudFront" class="headerlink" title="CloudFront"></a>CloudFront</h2><p>Check the hit&#x2F;miss ratio of the cache and adjust your configuration and TTL accordingly.</p><p>Bypassing the CloudFront cache and loading assets directly from S3 is more expensive. Therefore, restrict access to S3 by using an Origin Access Identity.</p><h2 id="CloudTrail"><a href="#CloudTrail" class="headerlink" title="CloudTrail"></a>CloudTrail</h2><p>Simple: delete unnecessary trails. Keep in mind that configuring more than one trail results in additional costs.</p><p>Check costs for data events (S3 and Lambda). Read <a href="https://cloudonaut.io/aws-cloudtrail-your-audit-log-is-incomplete/">AWS CloudTrail: your audit log is incomplete</a> to learn more.</p><p>Now it is up to you. Go and reduce your AWS bill!</p><p>One more thing: make sure you have created a budget alarm to get notified about unexpected costs in advance. Consult the <a href="https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/budgets-managing-costs.html" target="_blank" rel="noopener">AWS documentation</a> or learn how to <a href="https://marbot.io/help/setup-integration-aws-budget-notification.html" target="_blank" rel="noopener">receive AWS budget alarms via Slack</a>.</p><p><strong>Special thanks to <a href="https://www.taimos.de/" target="_blank" rel="noopener">Thorsten Höger from Taimos</a> for reviewing this blog post.</strong></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>2019: Review and Outlook</title>
      <link>https://cloudonaut.io/2019-review-outlook/</link>
      <description>The year is coming to an end. We shipped a lot of stuff and we also have plans for next year.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/retrospective/">retrospective</category>
      <guid isPermaLink="true">https://cloudonaut.io/2019-review-outlook/</guid>
      <pubDate>Mon, 23 Dec 2019 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The year is coming to an end. We want to say “thank you” to our clients, partners, supporters, and readers. In this blog post, we look back on the past year and dare a look at the next year.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/12/newyear@730w.webp 730w, /images/2019/12/newyear@730w2x.webp 1460w, /images/2019/12/newyear@610w.webp 610w, /images/2019/12/newyear@610w2x.webp 1220w, /images/2019/12/newyear@450w.webp 450w, /images/2019/12/newyear@450w2x.webp 900w, /images/2019/12/newyear@330w.webp 330w, /images/2019/12/newyear@330w2x.webp 660w, /images/2019/12/newyear@545w.webp 545w, /images/2019/12/newyear@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/12/newyear@730w.jpg 730w, /images/2019/12/newyear@730w2x.jpg 1460w, /images/2019/12/newyear@610w.jpg 610w, /images/2019/12/newyear@610w2x.jpg 1220w, /images/2019/12/newyear@450w.jpg 450w, /images/2019/12/newyear@450w2x.jpg 900w, /images/2019/12/newyear@330w.jpg 330w, /images/2019/12/newyear@330w2x.jpg 660w, /images/2019/12/newyear@545w.jpg 545w, /images/2019/12/newyear@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/12/newyear.jpg" alt="Happy New Year" title="Happy New Year"></picture></p><h2 id="Review"><a href="#Review" class="headerlink" title="Review"></a>Review</h2><p>We love blogging and published 53 articles on <a href="https://cloudonaut.io/">cloudonaut.io</a> this year. Unbelievable, but those articles have been read more than 1,000,000 times in 2019.</p><p>Our favorite articles this year:</p><ul><li><a href="/review-aws-backup/">Review: AWS Backup - A centralized place for managing backups?</a></li><li><a href="/aws-sla-are-you-able-to-keep-your-availability-promise/">AWS SLA: Are you able to keep your availability promise?</a></li><li><a href="/ecs-vs-fargate-whats-the-difference/">ECS vs. Fargate: What’s the difference?</a></li><li><a href="/6-new-ways-to-reduce-your-aws-bill-with-little-effort/">6 new ways to reduce your AWS bill with little effort</a></li><li><a href="/s3-security-best-practice/">How to avoid S3 data leaks?</a></li></ul><p>We are proud of the projects we were able to implement for our clients this year:</p><ul><li>Coaching teams to get started with AWS in an enterprise environment.</li><li>Bootstrapping the infrastructure for several startups.</li><li>Designing a Serverless architecture for a mid-size company.</li><li>Automating the deployment process for various clients.</li><li>Reviewing cloud infrastructures with a focus on security, cost-effectiveness, and reliability for all kinds of organizations.</li><li>Implementing a Serverless application for a retail business.</li></ul><p>By the way, we have relaunched <a href="https://widdix.net/" target="_blank" rel="noopener">widdix.net</a>, the website of our boutique consulting firm.</p><p>Our incident management chatbot <a href="https://marbot.io/" target="_blank" rel="noopener">marbot</a> is available in the AWS Marketplace since June. On top of that, we improved our integrations with various AWS services to simplify monitoring your cloud infrastructure as much as possible.</p><p>In August, we launched the cloudonaut podcast. Since then, we released ten episodes and received motivating feedback from the community. Make sure you have subscribed to our podcast on <a href="https://podcasts.apple.com/podcast/id1476505149" target="_blank" rel="noopener">Apple Podcasts</a>, <a href="https://www.google.com/podcasts?feed=aHR0cHM6Ly9wb2RjYXN0LmNsb3Vkb25hdXQuaW8vZmVlZC9hYWM=" target="_blank" rel="noopener">Google Podcasts</a>, <a href="https://open.spotify.com/show/1M44gYEuSZs3YX6zDUcVZs" target="_blank" rel="noopener">Spotify</a>, or wherever you listen to podcasts.</p><p>We released our 2nd book Rapid Docker on AWS in October. The book is written for DevOps engineers and web developers who want to run dockerized web applications on AWS with minimal effort and overhead.</p><p>We benefit from Open Source software every day and are happy to contribute to the community ourselves. Our most important projects are:</p><ul><li><a href="https://github.com/cfn-modules" target="_blank" rel="noopener">cfn-modules: Rapid CloudFormation: modular, production ready, open source</a> (133 stars and 17 forks)</li><li><a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">widdix&#x2F;aws-cf-templates: Free Templates for AWS CloudFormation</a> (1700 stars and 815 forks)</li><li><a href="https://github.com/widdix/aws-ec2-ssh" target="_blank" rel="noopener">widdix&#x2F;aws-ec2-ssh: Manage AWS EC2 SSH access with IAM</a> (734 stars and 261 forks)</li><li><a href="https://github.com/widdix/aws-s3-virusscan" target="_blank" rel="noopener">widdix&#x2F;aws-s3-virusscan: Free Antivirus for S3 Buckets</a> (306 stars and 85 forks)</li></ul><h2 id="Outlook"><a href="#Outlook" class="headerlink" title="Outlook"></a>Outlook</h2><p>We plan to write another book in 2020. We haven’t decided on the topic and title yet. Please write in to tell us about the book you want to read from us next year.</p><p>In February 2020, we will launch the Rapid Docker on AWS online seminar, a 4-week author-led workshop.</p><p>We will build a software product next year as well. We are currently thinking about investing more energy into <a href="https://marbot.io/" target="_blank" rel="noopener">marbot</a> or to create a new product from scratch. One idea is to develop a portfolio analysis solution for semi-professional investors.</p><p>On top of that, we are looking for projects where we can bring in our expertise as freelancers. We would be especially pleased to be able to work on a project with the following focus:</p><ul><li>Building a cloud infrastructure on the Google Cloud Platform. </li><li>Implementing a Serverless application on AWS.</li><li>Coaching a team to build a deployment pipeline (CI&#x2F;CD) for AWS.</li><li>Setting up a Serverless data pipeline and analytics platform on AWS.</li></ul><p>If you are looking for help with a project in 2020, please let us know. Our calendar fills up quickly.</p><p>That’s it from us for 2019. We are taking a 2-week break. See you in 2020.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Dockerizing Ruby on Rails</title>
      <link>https://cloudonaut.io/dockerizing-ruby-on-rails/</link>
      <description>Did you dockerize your Ruby on Rails application already? You definitely should! It simplifies deploying your application a lot. Learn how to create a Dockerfile to build a Docker image for your application.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <guid isPermaLink="true">https://cloudonaut.io/dockerizing-ruby-on-rails/</guid>
      <pubDate>Thu, 19 Dec 2019 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Did you dockerize your Ruby on Rails application already? You definitely should! Read on to learn why and how.</p><p>Shipping software is a challenge. Endless installation instructions explain in detail how to install and configure an application as well as all its dependencies. But in practice, following installation instructions ends with frustration: the required version of Ruby is not available to install from the repository, the configuration file is located in another directory, the installation instructions do not cover the operating system you need or want to use, etc.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/12/docker-intro@730w.webp 730w, /images/2019/12/docker-intro@730w2x.webp 1460w, /images/2019/12/docker-intro@610w.webp 610w, /images/2019/12/docker-intro@610w2x.webp 1220w, /images/2019/12/docker-intro@450w.webp 450w, /images/2019/12/docker-intro@450w2x.webp 900w, /images/2019/12/docker-intro@330w.webp 330w, /images/2019/12/docker-intro@330w2x.webp 660w, /images/2019/12/docker-intro@545w.webp 545w, /images/2019/12/docker-intro@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/12/docker-intro@730w.jpg 730w, /images/2019/12/docker-intro@730w2x.jpg 1460w, /images/2019/12/docker-intro@610w.jpg 610w, /images/2019/12/docker-intro@610w2x.jpg 1220w, /images/2019/12/docker-intro@450w.jpg 450w, /images/2019/12/docker-intro@450w2x.jpg 900w, /images/2019/12/docker-intro@330w.jpg 330w, /images/2019/12/docker-intro@330w2x.jpg 660w, /images/2019/12/docker-intro@545w.jpg 545w, /images/2019/12/docker-intro@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/12/docker-intro.jpg" alt="Dockerizing Ruby on Rails" title="Dockerizing Ruby on Rails"></picture></p><p>And it gets worse: to be able to scale on-demand and recover from failure, we need to automate the installation and configuration of our application and its runtime environment. Implementing the required automation with the wrong tools is very time-consuming and error-prone.</p><p>But what if you could bundle your application with all its dependencies and run it on any machine: your MacBook, your Windows PC, your test server, your on-premises server, and your cloud infrastructure? That’s what Docker is all about.</p><p>In short: Docker is a toolkit to deliver software.</p><p>The most important part of the Docker toolkit is the container. A container is an isolated runtime environment preventing an application from accessing resources from other applications running on the same operating system. The concept of a jail - later called a container - had been around on UNIX systems for years. Docker uses the same ideas but makes them a lot easier to use. </p><p>With Docker containers, the differences between different platforms like your developer machine, your test system, and your production system are hidden under an abstraction layer. But how do you distribute your application with all its dependencies to multiple platforms? By creating a Docker image. A Docker image is similar to a virtual machine image, such as an Amazon Machine Image (AMI) that is used to launch an EC2 instance. The Docker image contains an operating system, the runtime environment, 3rd party libraries, and your application. The following figure illustrates how you can fetch an image and start a container on any platform.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/12/docker_image@730w.webp 730w, /images/2019/12/docker_image@730w2x.webp 1460w, /images/2019/12/docker_image@610w.webp 610w, /images/2019/12/docker_image@610w2x.webp 1220w, /images/2019/12/docker_image@450w.webp 450w, /images/2019/12/docker_image@450w2x.webp 900w, /images/2019/12/docker_image@330w.webp 330w, /images/2019/12/docker_image@330w2x.webp 660w, /images/2019/12/docker_image@545w.webp 545w, /images/2019/12/docker_image@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/12/docker_image@730w.jpg 730w, /images/2019/12/docker_image@730w2x.jpg 1460w, /images/2019/12/docker_image@610w.jpg 610w, /images/2019/12/docker_image@610w2x.jpg 1220w, /images/2019/12/docker_image@450w.jpg 450w, /images/2019/12/docker_image@450w2x.jpg 900w, /images/2019/12/docker_image@330w.jpg 330w, /images/2019/12/docker_image@330w2x.jpg 660w, /images/2019/12/docker_image@545w.jpg 545w, /images/2019/12/docker_image@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/12/docker_image.jpg" alt="Distribute your application among multiple machines with a Docker image" title="Distribute your application among multiple machines with a Docker image"></picture></p><p>But how do you create a Docker image for your web application? By creating a script that builds the image step by step: a so-called <code>Dockerfile</code>.</p><p>Next, you will learn how to dockerize a typical Ruby on Rails application.</p><p>The project structure of a typical Ruby on Rails project looks like this:</p><figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">├── Gemfile</span><br><span class="line">├── README<span class="selector-class">.md</span></span><br><span class="line">├── Rakefile</span><br><span class="line">├── app</span><br><span class="line">├── babel<span class="selector-class">.config</span><span class="selector-class">.js</span></span><br><span class="line">├── bin</span><br><span class="line">├── config</span><br><span class="line">├── config<span class="selector-class">.ru</span></span><br><span class="line">├── db</span><br><span class="line">├── lib</span><br><span class="line">├── log</span><br><span class="line">├── package<span class="selector-class">.json</span></span><br><span class="line">├── postcss<span class="selector-class">.config</span><span class="selector-class">.js</span></span><br><span class="line">├── public</span><br><span class="line">├── storage</span><br><span class="line">├── test</span><br><span class="line">├── tmp</span><br><span class="line">├── vendor</span><br><span class="line">└── yarn.lock</span><br></pre></td></tr></table></figure><p>How to bundle an application with the described project structure? Let’s have a look at the <code>Dockerfile</code>:</p><ol><li>Based on Amazon Linux 2.</li><li>Installs <a href="https://nodejs.org/en" target="_blank" rel="noopener">Node.js</a>, required by <a href="https://yarnpkg.com/" target="_blank" rel="noopener">yarn</a>, required by Ruby on Rails.</li><li>Installs <a href="https://www.ruby-lang.org/" target="_blank" rel="noopener">Ruby</a> and <a href="https://rubyonrails.org/" target="_blank" rel="noopener">Ruby on Rails</a> with all needed dependencies.</li><li>Installs <a href="https://github.com/vishnubob/wait-for-it" target="_blank" rel="noopener">wait-for-it</a> - a helper to make sure that the MySQL database is up and running before the Ruby container is started with <code>docker-compose</code>.</li><li>Copies all files except the ignores defined in the file <code>.dockerignore</code>.</li><li>Installs all Ruby dependencies of the application and generates the static assets.</li><li>Configured a custom entry point that runs the database migrations when the container starts before the application is started.</li><li>Exposes port 3000 and defines the default command to run the application.</li></ol><figure class="highlight docker"><figcaption><span>Dockerfile</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">FROM</span> amazonlinux:<span class="number">2.0</span>.<span class="number">20190508</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">mkdir</span> /usr/src/app</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /usr/src/app</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Install Node.js (needed for yarn)</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> curl -sL https://rpm.nodesource.com/setup_10.x | bash -</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> yum -y install nodejs gcc-c++ make</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Install Ruby &amp; Rails</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> curl -sL -o /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> amazon-linux-extras <span class="built_in">enable</span> ruby2.6 &amp;&amp; yum -y clean metadata \</span></span><br><span class="line"><span class="language-bash">  &amp;&amp; yum -y install git tar gzip yarn zlib-devel sqlite-devel mariadb-devel ruby-devel rubygems-devel rubygem-bundler rubygem-io-console rubygem-irb rubygem-json rubygem-minitest rubygem-net-http-persistent rubygem-net-telnet rubygem-power_assert rubygem-rake rubygem-test-unit rubygem-thor rubygem-xmlrpc \</span></span><br><span class="line"><span class="language-bash">  &amp;&amp; gem install rails</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Install wait-for-it</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> docker/wait-for-it.sh /usr/local/bin/</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">chmod</span> u+x /usr/local/bin/wait-for-it.sh</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Copy Ruby files (see .dockerignore)</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Install Ruby dependencies</span></span><br><span class="line"><span class="keyword">ENV</span> RAILS_ENV production</span><br><span class="line"><span class="keyword">ENV</span> RAILS_LOG_TO_STDOUT enabled</span><br><span class="line"><span class="keyword">ENV</span> RAILS_SERVE_STATIC_FILES enabled</span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> bin/bundle install --deployment --without development <span class="built_in">test</span></span></span><br><span class="line"><span class="comment"># see https://github.com/rails/rails/issues/32947 for SECRET_KEY_BASE workaround</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> SECRET_KEY_BASE=dummy bin/rails assets:precompile</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Configure custom entrypoint to run migrations</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> docker/custom-entrypoint /usr/local/bin/</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">chmod</span> u+x /usr/local/bin/custom-entrypoint</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;custom-entrypoint&quot;</span>]</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Expose port 3000 and start Rails server</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">3000</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;bin/rails&quot;</span>, <span class="string">&quot;server&quot;</span>, <span class="string">&quot;--binding=0.0.0.0&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>To limit the amount of data that needs to be sent to Docker, the <code>.dockeringore</code> file defines an exclude list for files and directories that you typically do not need to include in the Docker image.</p><figure class="highlight bash"><figcaption><span>.dockerignore</span></figcaption><table><tr><td class="code"><pre><span class="line">aws/*</span><br><span class="line"><span class="built_in">log</span>/*</span><br><span class="line">storage/*</span><br><span class="line">tmp/*</span><br><span class="line">public/assets/*</span><br><span class="line">public/packs/*</span><br></pre></td></tr></table></figure><p>It is a best practice when dockerizing applications to use environment variables instead of configuration files. Do not use files to store the configuration for your application. Use environment variables instead. Luckily, Ruby on Rails comes with it’s <a href="https://guides.rubyonrails.org/configuring.html" target="_blank" rel="noopener">own configuration mechanism</a> that supports environment variables out of the box. Check out the file <code>config/database.rb</code> to see how environment variables are used to configure the database connection.</p><figure class="highlight ruby"><figcaption><span>config&#x2F;database.rb</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># ...</span></span><br><span class="line"><span class="symbol">production:</span></span><br><span class="line">  &lt;&lt;: *default</span><br><span class="line">  <span class="symbol">host:</span> &lt;%= <span class="variable constant_">ENV</span>[<span class="string">&#x27;DATABASE_HOST&#x27;</span>] %&gt;</span><br><span class="line">  <span class="symbol">database:</span> &lt;%= <span class="variable constant_">ENV</span>[<span class="string">&#x27;DATABASE_NAME&#x27;</span>] %&gt;</span><br><span class="line">  <span class="symbol">username:</span> &lt;%= <span class="variable constant_">ENV</span>[<span class="string">&#x27;DATABASE_USER&#x27;</span>] %&gt;</span><br><span class="line">  <span class="symbol">password:</span> &lt;%= <span class="variable constant_">ENV</span>[<span class="string">&#x27;DATABASE_PASSWORD&#x27;</span>] %&gt;</span><br></pre></td></tr></table></figure><p>One more thing: it is necessary to run the database migration each time you roll out a new version of your application. The easiest way to do so is to execute <code>db:migrate</code> each time you start the Docker container. To do so, the <code>Dockerfile</code> adds a so-called <code>ENTRYPOINT</code> which references the shell script <code>custom-entrypoint</code>. Each time you start the Docker container, the entry point script gets executed as well.</p><p>The <code>custom-entrypoint</code> script:</p><ol><li>Waits until it is possible to establish a database connection.</li><li>Executes the database migration by calling <code>db:migrate</code>.</li><li>Starts the <code>puma</code> web server.</li></ol><figure class="highlight bash"><figcaption><span>custom-entrypoint</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ -n <span class="string">&quot;<span class="variable">$&#123;WAIT_FOR_IT&#125;</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">  wait-for-it.sh mysql:3306</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;running migrations&quot;</span></span><br><span class="line">bin/rails db:migrate</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;starting <span class="variable">$@</span>&quot;</span></span><br><span class="line"><span class="built_in">exec</span> <span class="string">&quot;<span class="variable">$@</span>&quot;</span></span><br></pre></td></tr></table></figure><p>That’s it! You are ready to build a Docker image bundling your Ruby on Rails application.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker build -t myapp:latest .</span><br></pre></td></tr></table></figure><p>Start a container based on the image …</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker run -p 3000:3000 myapp:latest</span><br></pre></td></tr></table></figure><p>… and open <a href="http://localhost:9000/" target="_blank" rel="noopener">http://localhost:3000</a>.</p><p>You have successfully dockerized your Ruby on Rails application. What is next?</p><ul><li>Push the Docker image into a private registry (e.g., Amazon ECR).</li><li>Deploy your application in the cloud (e.g., with ECS and Fargate).</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>Combine CloudWatch metrics for Auto Scaling or to reduce costs</title>
      <link>https://cloudonaut.io/combine-cloudwatch-metrics-autoscaling-reduce-costs/</link>
      <description>Each and every part of your AWS infrastructure emits utilization metrics. Amazon CloudWatch collects these metrics and allows you to visualize them as well as to define alarms. AWS announced an interesting new features allowing you to combine multiple metrics recently: IF/AND/OR statements for metric math.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <guid isPermaLink="true">https://cloudonaut.io/combine-cloudwatch-metrics-autoscaling-reduce-costs/</guid>
      <pubDate>Tue, 17 Dec 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Every part of your AWS infrastructure emits utilization metrics. Amazon CloudWatch collects these metrics and allows you to visualize them as well as to define alarms. AWS announced an exciting new feature allowing you to combine multiple metrics recently: <a href="https://aws.amazon.com/about-aws/whats-new/2019/11/amazon-cloudwatch-metric-math-now-supports-additional-functions/" target="_blank" rel="noopener">IF&#x2F;AND&#x2F;OR statements for metric math</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/12/math-expression@730w.webp 730w, /images/2019/12/math-expression@730w2x.webp 1460w, /images/2019/12/math-expression@610w.webp 610w, /images/2019/12/math-expression@610w2x.webp 1220w, /images/2019/12/math-expression@450w.webp 450w, /images/2019/12/math-expression@450w2x.webp 900w, /images/2019/12/math-expression@330w.webp 330w, /images/2019/12/math-expression@330w2x.webp 660w, /images/2019/12/math-expression@545w.webp 545w, /images/2019/12/math-expression@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/12/math-expression@730w.jpg 730w, /images/2019/12/math-expression@730w2x.jpg 1460w, /images/2019/12/math-expression@610w.jpg 610w, /images/2019/12/math-expression@610w2x.jpg 1220w, /images/2019/12/math-expression@450w.jpg 450w, /images/2019/12/math-expression@450w2x.jpg 900w, /images/2019/12/math-expression@330w.jpg 330w, /images/2019/12/math-expression@330w2x.jpg 660w, /images/2019/12/math-expression@545w.jpg 545w, /images/2019/12/math-expression@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/12/math-expression.jpg" alt="CloudWatch metric math" title="CloudWatch metric math"></picture></p><p>Combining CloudWatch metrics has several advantages:</p><ol><li><strong>Simplify</strong> your monitoring configuration by reducing the number of CloudWatch alarms.</li><li><strong>Reduce costs</strong> by reducing the number of CloudWatch alarms (each alarm costs around USD 0.10 per month).</li><li>Increase or decrease the desired capacity of an <strong>Auto Scaling Group</strong> according to <strong>multiple metrics</strong> (e.g., the typical bottlenecks CPU, memory, and network).</li></ol><p>All you need to do is to define a Metric Math Expression that combines multiple metrics. Doing so results in a calculated metric. Next, you can define a CloudWatch alarm or a visualization based on the calculated metric.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/12/cloudwatch-metric-math@730w.webp 730w, /images/2019/12/cloudwatch-metric-math@730w2x.webp 1460w, /images/2019/12/cloudwatch-metric-math@610w.webp 610w, /images/2019/12/cloudwatch-metric-math@610w2x.webp 1220w, /images/2019/12/cloudwatch-metric-math@450w.webp 450w, /images/2019/12/cloudwatch-metric-math@450w2x.webp 900w, /images/2019/12/cloudwatch-metric-math@330w.webp 330w, /images/2019/12/cloudwatch-metric-math@330w2x.webp 660w, /images/2019/12/cloudwatch-metric-math@545w.webp 545w, /images/2019/12/cloudwatch-metric-math@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/12/cloudwatch-metric-math@730w.png 730w, /images/2019/12/cloudwatch-metric-math@730w2x.png 1460w, /images/2019/12/cloudwatch-metric-math@610w.png 610w, /images/2019/12/cloudwatch-metric-math@610w2x.png 1220w, /images/2019/12/cloudwatch-metric-math@450w.png 450w, /images/2019/12/cloudwatch-metric-math@450w2x.png 900w, /images/2019/12/cloudwatch-metric-math@330w.png 330w, /images/2019/12/cloudwatch-metric-math@330w2x.png 660w, /images/2019/12/cloudwatch-metric-math@545w.png 545w, /images/2019/12/cloudwatch-metric-math@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/12/cloudwatch-metric-math.png" alt="Use metric math to combine multiple metrics" title="Use metric math to combine multiple metrics"></picture></p><p>Let’s imagine the following scenario: you are using an Auto Scaling Group to launch EC2 instances. Typical bottlenecks of your virtual machines are:</p><ul><li>CPU</li><li>Memory</li><li>Network (see <a href="/ec2-network-performance-cheat-sheet/">EC2 Network Benchmark</a>)</li></ul><p>How do you get notified or scale-out automatically when one of these resources gets scarce? And how do you get notified or scale in automatically when the resources are no longer being used?</p><p>The following screenshot shows four basic metrics:</p><ul><li>CPU Utilization</li><li>Memory Utilization</li><li>Network In</li><li>Network Out</li></ul><p>Please note that AWS does not provide a memory utilization metric by default. Therefore, I’m using the <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html" target="_blank" rel="noopener">CloudWatch Agent</a> to collect the data for a memory utilization metric.</p><p>Also, as explained in <a href="https://marbot.io/blog/monitoring-ec2-network-utilization.html" target="_blank" rel="noopener">Monitoring EC2 Network Utilization</a>, you need to combine the Network In and Network Out metric to calculate the total network throughput. The <code>Network Utilization</code> metric calculates the percentage utilization of the network.</p><p>However, I want to put your attention on the <code>Summary Utilization</code> metric:</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">IF</span>(cpu &gt; <span class="number">70</span>, <span class="number">1</span>, <span class="number">0</span>) OR IF(memory &gt; <span class="number">75</span>, <span class="number">1</span>, <span class="number">0</span>) OR IF(network &gt; <span class="number">80</span>, <span class="number">1</span>, <span class="number">0</span>)</span><br></pre></td></tr></table></figure><ul><li>If the CPU utilization is above 70%, the metric math expression will return <code>1</code>.</li><li>If the memory utilization is above 75%, the metric math expression will return <code>1</code>.</li><li>If the network utilization is above 80%, the metric math expression will return <code>1</code>.</li><li>Otherwise, the metric math expression will return <code>0</code>.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/12/cloudwatch-metric-math-screenshot-01@730w.webp 730w, /images/2019/12/cloudwatch-metric-math-screenshot-01@730w2x.webp 1460w, /images/2019/12/cloudwatch-metric-math-screenshot-01@610w.webp 610w, /images/2019/12/cloudwatch-metric-math-screenshot-01@610w2x.webp 1220w, /images/2019/12/cloudwatch-metric-math-screenshot-01@450w.webp 450w, /images/2019/12/cloudwatch-metric-math-screenshot-01@450w2x.webp 900w, /images/2019/12/cloudwatch-metric-math-screenshot-01@330w.webp 330w, /images/2019/12/cloudwatch-metric-math-screenshot-01@330w2x.webp 660w, /images/2019/12/cloudwatch-metric-math-screenshot-01@545w.webp 545w, /images/2019/12/cloudwatch-metric-math-screenshot-01@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/12/cloudwatch-metric-math-screenshot-01@730w.png 730w, /images/2019/12/cloudwatch-metric-math-screenshot-01@730w2x.png 1460w, /images/2019/12/cloudwatch-metric-math-screenshot-01@610w.png 610w, /images/2019/12/cloudwatch-metric-math-screenshot-01@610w2x.png 1220w, /images/2019/12/cloudwatch-metric-math-screenshot-01@450w.png 450w, /images/2019/12/cloudwatch-metric-math-screenshot-01@450w2x.png 900w, /images/2019/12/cloudwatch-metric-math-screenshot-01@330w.png 330w, /images/2019/12/cloudwatch-metric-math-screenshot-01@330w2x.png 660w, /images/2019/12/cloudwatch-metric-math-screenshot-01@545w.png 545w, /images/2019/12/cloudwatch-metric-math-screenshot-01@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/12/cloudwatch-metric-math-screenshot-01.png" alt="Metric Math Expression" title="Metric Math Expression"></picture></p><p>Next, define a CloudWatch alarm based on the <code>Summary Utilization</code> metric. Use <code>1</code> for the threshold.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/12/cloudwatch-metric-math-screenshot-02@730w.webp 730w, /images/2019/12/cloudwatch-metric-math-screenshot-02@730w2x.webp 1460w, /images/2019/12/cloudwatch-metric-math-screenshot-02@610w.webp 610w, /images/2019/12/cloudwatch-metric-math-screenshot-02@610w2x.webp 1220w, /images/2019/12/cloudwatch-metric-math-screenshot-02@450w.webp 450w, /images/2019/12/cloudwatch-metric-math-screenshot-02@450w2x.webp 900w, /images/2019/12/cloudwatch-metric-math-screenshot-02@330w.webp 330w, /images/2019/12/cloudwatch-metric-math-screenshot-02@330w2x.webp 660w, /images/2019/12/cloudwatch-metric-math-screenshot-02@545w.webp 545w, /images/2019/12/cloudwatch-metric-math-screenshot-02@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/12/cloudwatch-metric-math-screenshot-02@730w.png 730w, /images/2019/12/cloudwatch-metric-math-screenshot-02@730w2x.png 1460w, /images/2019/12/cloudwatch-metric-math-screenshot-02@610w.png 610w, /images/2019/12/cloudwatch-metric-math-screenshot-02@610w2x.png 1220w, /images/2019/12/cloudwatch-metric-math-screenshot-02@450w.png 450w, /images/2019/12/cloudwatch-metric-math-screenshot-02@450w2x.png 900w, /images/2019/12/cloudwatch-metric-math-screenshot-02@330w.png 330w, /images/2019/12/cloudwatch-metric-math-screenshot-02@330w2x.png 660w, /images/2019/12/cloudwatch-metric-math-screenshot-02@545w.png 545w, /images/2019/12/cloudwatch-metric-math-screenshot-02@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/12/cloudwatch-metric-math-screenshot-02.png" alt="Metric Math Expression" title="Metric Math Expression"></picture></p><p>The alarm will transition into the <code>ALARM</code> state when the CPU utilization is above 70%, or the memory utilization is above 75%, or the network utilization is above 80%. Configure the CloudWatch alarm to send a notification or increase the desired capacity of the Auto Scaling Group.</p><p>Do you prefer Infrastructure as Code? The following code snippet shows how to create the CloudWatch alarm with the help of CloudFormation.</p><blockquote><p>Note: The example assumes that you are running a <code>m5.large</code> instance with a maximal network throughout of about 0.75 Gbit&#x2F;s.</p></blockquote><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">AutoScalingGroupName:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">EC2HighUtilization:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AlarmDescription:</span> <span class="string">&#x27;EC2 High Utilization: CPU, memory, or network&#x27;</span></span><br><span class="line">      <span class="attr">Metrics:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Id:</span> <span class="string">summary</span></span><br><span class="line">        <span class="attr">Label:</span> <span class="string">EC2</span> <span class="string">Utilization</span></span><br><span class="line">        <span class="attr">Expression:</span> <span class="string">IF(cpu</span> <span class="string">&gt;</span> <span class="number">70</span><span class="string">,</span> <span class="number">1</span><span class="string">,</span> <span class="number">0</span><span class="string">)</span> <span class="string">OR</span> <span class="string">IF(memory</span> <span class="string">&gt;</span> <span class="number">75</span><span class="string">,</span> <span class="number">1</span><span class="string">,</span> <span class="number">0</span><span class="string">)</span> <span class="string">OR</span> <span class="string">IF(network</span> <span class="string">&gt;</span> <span class="number">80</span><span class="string">,</span> <span class="number">1</span><span class="string">,</span> <span class="number">0</span><span class="string">)</span></span><br><span class="line">        <span class="attr">ReturnData:</span> <span class="literal">true</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Id:</span> <span class="string">cpu</span></span><br><span class="line">        <span class="attr">MetricStat:</span></span><br><span class="line">          <span class="attr">Metric:</span></span><br><span class="line">            <span class="attr">Namespace:</span> <span class="string">AWS/EC2</span></span><br><span class="line">            <span class="attr">MetricName:</span> <span class="string">CPUUtilization</span></span><br><span class="line">            <span class="attr">Dimensions:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">AutoScalingGroupName</span></span><br><span class="line">              <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">AutoScalingGroupName</span></span><br><span class="line">          <span class="attr">Stat:</span> <span class="string">Maximum</span></span><br><span class="line">          <span class="attr">Period:</span> <span class="number">300</span></span><br><span class="line">        <span class="attr">ReturnData:</span> <span class="literal">false</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Id:</span> <span class="string">memory</span></span><br><span class="line">        <span class="attr">MetricStat:</span></span><br><span class="line">          <span class="attr">Metric:</span></span><br><span class="line">            <span class="attr">Namespace:</span> <span class="string">CWAgent</span></span><br><span class="line">            <span class="attr">MetricName:</span> <span class="string">mem_used_percent</span></span><br><span class="line">            <span class="attr">Dimensions:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">AutoScalingGroupName</span></span><br><span class="line">              <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">AutoScalingGroupName</span></span><br><span class="line">          <span class="attr">Stat:</span> <span class="string">Maximum</span></span><br><span class="line">          <span class="attr">Period:</span> <span class="number">300</span></span><br><span class="line">        <span class="attr">ReturnData:</span> <span class="literal">false</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Id:</span> <span class="string">network</span></span><br><span class="line">        <span class="attr">Label:</span> <span class="string">Network</span> <span class="string">Utilization</span></span><br><span class="line">        <span class="attr">Expression:</span> <span class="string">&quot;((network_in+network_out)/300/1000/1000/1000*8)/0.75*100&quot;</span></span><br><span class="line">        <span class="attr">ReturnData:</span> <span class="literal">false</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Id:</span> <span class="string">network_in</span></span><br><span class="line">        <span class="attr">MetricStat:</span></span><br><span class="line">          <span class="attr">Metric:</span></span><br><span class="line">            <span class="attr">Namespace:</span> <span class="string">AWS/EC2</span></span><br><span class="line">            <span class="attr">MetricName:</span> <span class="string">NetworkIn</span></span><br><span class="line">            <span class="attr">Dimensions:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">AutoScalingGroupName</span></span><br><span class="line">              <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">AutoScalingGroupName</span></span><br><span class="line">          <span class="attr">Stat:</span> <span class="string">Sum</span></span><br><span class="line">          <span class="attr">Period:</span> <span class="number">300</span></span><br><span class="line">        <span class="attr">ReturnData:</span> <span class="literal">false</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Id:</span> <span class="string">network_out</span></span><br><span class="line">        <span class="attr">MetricStat:</span></span><br><span class="line">          <span class="attr">Metric:</span></span><br><span class="line">            <span class="attr">Namespace:</span> <span class="string">AWS/EC2</span></span><br><span class="line">            <span class="attr">MetricName:</span> <span class="string">NetworkOut</span></span><br><span class="line">            <span class="attr">Dimensions:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">AutoScalingGroupName</span></span><br><span class="line">              <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">AutoScalingGroupName</span></span><br><span class="line">          <span class="attr">Stat:</span> <span class="string">Sum</span></span><br><span class="line">          <span class="attr">Period:</span> <span class="number">300</span></span><br><span class="line">        <span class="attr">ReturnData:</span> <span class="literal">false</span></span><br><span class="line">      <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanOrEqualToThreshold</span></span><br><span class="line">      <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">DatapointsToAlarm:</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">Threshold:</span> <span class="string">&#x27;1&#x27;</span></span><br></pre></td></tr></table></figure><p>That’s all. Happy monitoring!</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>As CloudWatch metric math supports <code>IF/AND/OR</code> statements, it is possible to aggregate multiple metrics into a single metric. Doing so allows you to scale an Auto Scaling Group based on multiple metrics as well as reduce the number of CloudWatch alarms, which reduces costs.</p><blockquote><p>This post was originally published on the <a href="https://marbot.io/blog/cloudwatch-combined-metrics.html" target="_blank" rel="noopener">marbot blog</a>.</p></blockquote>]]>
      </content:encoded>
    </item>
    <item>
      <title>All you need to know about AWS re:Invent in 2019</title>
      <link>https://cloudonaut.io/reinvent-recap-2019-aws-announcements/</link>
      <description>Our re:Invent recap discusses the 10 most important announcements that you should not miss.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/reinvent/">reinvent</category>
      <guid isPermaLink="true">https://cloudonaut.io/reinvent-recap-2019-aws-announcements/</guid>
      <pubDate>Tue, 10 Dec 2019 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>re:Invent was a blast: five days packed with announcements of new services and features. We have created a top 10 list for our re:Invent recap. Here is all you need to know about re:Invent 2019.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/12/reinvent_las_vegas@730w.webp 730w, /images/2019/12/reinvent_las_vegas@730w2x.webp 1460w, /images/2019/12/reinvent_las_vegas@610w.webp 610w, /images/2019/12/reinvent_las_vegas@610w2x.webp 1220w, /images/2019/12/reinvent_las_vegas@450w.webp 450w, /images/2019/12/reinvent_las_vegas@450w2x.webp 900w, /images/2019/12/reinvent_las_vegas@330w.webp 330w, /images/2019/12/reinvent_las_vegas@330w2x.webp 660w, /images/2019/12/reinvent_las_vegas@545w.webp 545w, /images/2019/12/reinvent_las_vegas@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/12/reinvent_las_vegas@730w.jpg 730w, /images/2019/12/reinvent_las_vegas@730w2x.jpg 1460w, /images/2019/12/reinvent_las_vegas@610w.jpg 610w, /images/2019/12/reinvent_las_vegas@610w2x.jpg 1220w, /images/2019/12/reinvent_las_vegas@450w.jpg 450w, /images/2019/12/reinvent_las_vegas@450w2x.jpg 900w, /images/2019/12/reinvent_las_vegas@330w.jpg 330w, /images/2019/12/reinvent_las_vegas@330w2x.jpg 660w, /images/2019/12/reinvent_las_vegas@545w.jpg 545w, /images/2019/12/reinvent_las_vegas@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/12/reinvent_las_vegas.jpg" alt="re:Invent 2019" title="re:Invent 2019"></picture></p><p>Michael has collected some voices in Las Vegas. Listen to what <a href="https://aws.amazon.com/developer/community/heroes/aleksandar-simovic/" target="_blank" rel="noopener">Aleksandar Simovic</a>, <a href="https://aws.amazon.com/developer/community/heroes/anders-bjornestad/" target="_blank" rel="noopener">Anders Bjørnestad</a>, and <a href="https://aws.amazon.com/developer/community/heroes/thorsten-hoger/" target="_blank" rel="noopener">Thorsten Höger</a> have to say in our podcast episode. On top of that, the podcast episode includes our top 10 of re:Invent announcements as well.</p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/10-reinvent-recap-2019/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="Provisioned-Concurrency-for-AWS-Lambda"><a href="#Provisioned-Concurrency-for-AWS-Lambda" class="headerlink" title="Provisioned Concurrency for AWS Lambda"></a>Provisioned Concurrency for AWS Lambda</h2><p>Provisioned concurrency clears an obstacle out of the way: cold starts. By default, AWS Lambda scales on-demand. However, initializing a new executor takes a while - something between 100 ms and 1 s. Typically, only a small portion of the requests is affected by cold starts. However, there are scenarios where latencies above 200 ms are not acceptable, not even for a small number of requests.</p><p>Provisioning memory for concurrent invocations of your Lambda function allows you to reduce or avoid cold starts. For example, provisioning 4 GB memory will pre-warm 16 executors for your Lambda function with 512 MB memory size. As long as the Lambda function does not have to process more than 16 requests in parallel, there will be no cold starts at all. Check out Danilo’s blog post <a href="https://aws.amazon.com/blogs/aws/new-provisioned-concurrency-for-lambda-functions/" target="_blank" rel="noopener">Provisioned Concurrency for Lambda Functions</a> for a latency benchmark between a Lambda function with and without provisioned concurrency.</p><p>But provisioned concurrency does not only come with technical implications, but it also affects your AWS bill as well. Provisioning capacity for your Lambda function comes with a discount of about 15%. But keep in mind that you pay for provisioned capacity no matter if your Lambda function processes requests or not.</p><p>The following figure illustrates the costs for every GB-second at different utilization in US East (N. Virginia). On top of that, you pay $0.20 per 1M requests.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/12/lambda_provisioned_concurrency@730w.webp 730w, /images/2019/12/lambda_provisioned_concurrency@730w2x.webp 1460w, /images/2019/12/lambda_provisioned_concurrency@610w.webp 610w, /images/2019/12/lambda_provisioned_concurrency@610w2x.webp 1220w, /images/2019/12/lambda_provisioned_concurrency@450w.webp 450w, /images/2019/12/lambda_provisioned_concurrency@450w2x.webp 900w, /images/2019/12/lambda_provisioned_concurrency@330w.webp 330w, /images/2019/12/lambda_provisioned_concurrency@330w2x.webp 660w, /images/2019/12/lambda_provisioned_concurrency@545w.webp 545w, /images/2019/12/lambda_provisioned_concurrency@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/12/lambda_provisioned_concurrency@730w.png 730w, /images/2019/12/lambda_provisioned_concurrency@730w2x.png 1460w, /images/2019/12/lambda_provisioned_concurrency@610w.png 610w, /images/2019/12/lambda_provisioned_concurrency@610w2x.png 1220w, /images/2019/12/lambda_provisioned_concurrency@450w.png 450w, /images/2019/12/lambda_provisioned_concurrency@450w2x.png 900w, /images/2019/12/lambda_provisioned_concurrency@330w.png 330w, /images/2019/12/lambda_provisioned_concurrency@330w2x.png 660w, /images/2019/12/lambda_provisioned_concurrency@545w.png 545w, /images/2019/12/lambda_provisioned_concurrency@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/12/lambda_provisioned_concurrency.png" alt="Provisioned vs. On-Demand" title="Provisioned vs. On-Demand"></picture></p><p>Good news, Provisioned Concurrency for AWS Lambda is ready to build something great!</p><table class="table table-striped table-responsive"><thead><tr><th>Ready to build?</th><th></th></tr></thead><tbody><tr><td>General Available?</td><td>✅</td></tr><tr><td>Available in all regions?</td><td>✅</td></tr><tr><td>Manage with CloudFormation?</td><td>✅</td></tr></tbody></table><h2 id="HTTP-API"><a href="#HTTP-API" class="headerlink" title="HTTP API"></a>HTTP API</h2><p>Building a REST API with AWS Lambda is getting easier and cheaper with HTTP APIs, a new service under the Amazon API Gateway umbrella.</p><p>HTTP APIs come with a small but powerful set of features.</p><ul><li>Forward incoming requests to AWS Lambda or any other HTTP endpoint.</li><li>Cross-origin resource sharing (CORS).</li><li>Authentication and authorization via JWT.</li><li>Custom domain names, as well as TLS&#x2F;SSL.</li></ul><p>I can’t wait to build a Serverless application with HTTP APIs. However, HTTP APIs are in beta and should not be used for production workloads yet. There is also an important feature missing: throttling. At the moment, you cannot rate-limit requests per client in any way.</p><table class="table table-striped table-responsive"><thead><tr><th>Ready to build?</th><th></th></tr></thead><tbody><tr><td>General Available?</td><td>❌</td></tr><tr><td>Available in all regions?</td><td>❌</td></tr><tr><td>Manage with CloudFormation?</td><td>✅</td></tr></tbody></table><h2 id="Fargate-for-EKS"><a href="#Fargate-for-EKS" class="headerlink" title="Fargate for EKS"></a>Fargate for EKS</h2><p>One of my most essential arguments, why I prefer ECS over EKS, is no longer valid: Fargate is available for EKS as well now. Why is that important? Because there is rarely a business case in which it makes economic sense to take responsibility for two layers of abstraction: virtual machines and containers. Fargate provides compute capacity at the abstraction layer of a container. No virtual machines to manage!</p><p>Fargate pricing is the same, no matter if you orchestrate the containers with EKS or ECS. On top of that, you pay $144 per EKS cluster per month. Whereas running an ECS cluster is free of charge.</p><p>To use Fargate with EKS, you need to upgrade your cluster to Kubernetes version <code>1.14</code> and platform version <code>eks.5</code>. Next, create a Fargate profile that tells the cluster master to launch pods on Fargate for a particular namespace. If needed, you can also use tags to define which pods should launch on Fargate.</p><p>Unfortunately, Fargate for EKS is only available in the following regions: US East (Ohio), US East (N. Virginia), Asia Pacific (Tokyo), and EU (Ireland).</p><table class="table table-striped table-responsive"><thead><tr><th>Ready to build?</th><th></th></tr></thead><tbody><tr><td>General Available?</td><td>✅</td></tr><tr><td>Available in all regions?</td><td>❌</td></tr><tr><td>Manage with CloudFormation?</td><td>❌</td></tr></tbody></table><h2 id="ALB-Least-Outstanding-Requests-Algorithm-and-Weighted-Target-Groups"><a href="#ALB-Least-Outstanding-Requests-Algorithm-and-Weighted-Target-Groups" class="headerlink" title="ALB: Least Outstanding Requests Algorithm and Weighted Target Groups"></a>ALB: Least Outstanding Requests Algorithm and Weighted Target Groups</h2><p>AWS improved the Application Load Balancer (ALB) significantly with the following new features:</p><ul><li><em>Least Outstanding Requests</em> algorithm for load balancing</li><li><em>Weighted Target Groups</em> to distribute traffic among multiple target groups</li></ul><p>Why are those new features important?</p><p>Until now, the ALB used the round-robin algorithm to distribute incoming requests among targets. As illustrated in the following figure, this algorithm leads to hot targets in case some requests consume more resources than others. With the least outstanding requests algorithm, the ALB keeps track of the backlog of each target and sends requests to the target with the least outstanding requests. Doing so distribute the workload more evenly among all targets and prevents from hot targets. Overall, that allows you to reduce over-provisioned capacity.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/12/alb_least_outstanding@730w.webp 730w, /images/2019/12/alb_least_outstanding@730w2x.webp 1460w, /images/2019/12/alb_least_outstanding@610w.webp 610w, /images/2019/12/alb_least_outstanding@610w2x.webp 1220w, /images/2019/12/alb_least_outstanding@450w.webp 450w, /images/2019/12/alb_least_outstanding@450w2x.webp 900w, /images/2019/12/alb_least_outstanding@330w.webp 330w, /images/2019/12/alb_least_outstanding@330w2x.webp 660w, /images/2019/12/alb_least_outstanding@545w.webp 545w, /images/2019/12/alb_least_outstanding@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/12/alb_least_outstanding@730w.png 730w, /images/2019/12/alb_least_outstanding@730w2x.png 1460w, /images/2019/12/alb_least_outstanding@610w.png 610w, /images/2019/12/alb_least_outstanding@610w2x.png 1220w, /images/2019/12/alb_least_outstanding@450w.png 450w, /images/2019/12/alb_least_outstanding@450w2x.png 900w, /images/2019/12/alb_least_outstanding@330w.png 330w, /images/2019/12/alb_least_outstanding@330w2x.png 660w, /images/2019/12/alb_least_outstanding@545w.png 545w, /images/2019/12/alb_least_outstanding@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/12/alb_least_outstanding.png" alt="ALB Least Outstand Requests" title="ALB Least Outstand Requests"></picture></p><p>Deploying without downtime is a core skill that organizations adopt when moving their workloads to the cloud. The following figure shows a popular deployment strategy called blue-green deployment. During a deployment, all traffic is switched from the current version (green) to the new version (blue). In case any unexpected problems occur, the deployment rolls back to the green version automatically. The deployment pattern requires full control over the amount of traffic that is sent to the green and blue versions of your application. That’s precisely what weighted target groups are all about!</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/12/alb_target_weight@730w.webp 730w, /images/2019/12/alb_target_weight@730w2x.webp 1460w, /images/2019/12/alb_target_weight@610w.webp 610w, /images/2019/12/alb_target_weight@610w2x.webp 1220w, /images/2019/12/alb_target_weight@450w.webp 450w, /images/2019/12/alb_target_weight@450w2x.webp 900w, /images/2019/12/alb_target_weight@330w.webp 330w, /images/2019/12/alb_target_weight@330w2x.webp 660w, /images/2019/12/alb_target_weight@545w.webp 545w, /images/2019/12/alb_target_weight@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/12/alb_target_weight@730w.png 730w, /images/2019/12/alb_target_weight@730w2x.png 1460w, /images/2019/12/alb_target_weight@610w.png 610w, /images/2019/12/alb_target_weight@610w2x.png 1220w, /images/2019/12/alb_target_weight@450w.png 450w, /images/2019/12/alb_target_weight@450w2x.png 900w, /images/2019/12/alb_target_weight@330w.png 330w, /images/2019/12/alb_target_weight@330w2x.png 660w, /images/2019/12/alb_target_weight@545w.png 545w, /images/2019/12/alb_target_weight@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/12/alb_target_weight.png" alt="ALB Weighted Target Groups" title="ALB Weighted Target Groups"></picture></p><table class="table table-striped table-responsive"><thead><tr><th>Ready to build?</th><th></th></tr></thead><tbody><tr><td>General Available?</td><td>✅</td></tr><tr><td>Available in all regions?</td><td>✅</td></tr><tr><td>Manage with CloudFormation?</td><td>❌</td></tr></tbody></table><h2 id="Inter-Region-Peering-with-Transit-Gateway"><a href="#Inter-Region-Peering-with-Transit-Gateway" class="headerlink" title="Inter-Region Peering with Transit Gateway"></a>Inter-Region Peering with Transit Gateway</h2><p>The AWS Transit Gateway was introduced last year at re:Invent. Peering networks have become much more convenient since then. A transit gateway allows you to share a VPN or Direct Connect connection with multiple VPCs. On top of that, it is possible to peer multiple VPCs with each other.</p><p>I’m thrilled that the AWS Transit Gateway allows me to peer networks across different regions from now on. A convenient feature for establishing links between your infrastructure distributed among different parts of the world.</p><p>Unfortunately, the feature is not available in all regions yet. Currently, the following regions are supported: US East (N. Virginia), US East (Ohio), US West (Oregon), EU (Ireland), and EU (Frankfurt).</p><table class="table table-striped table-responsive"><thead><tr><th>Ready to build?</th><th></th></tr></thead><tbody><tr><td>General Available?</td><td>✅</td></tr><tr><td>Available in all regions?</td><td>❌</td></tr><tr><td>Manage with CloudFormation?</td><td>❌</td></tr></tbody></table><h2 id="AWS-Fargate-Spot-Pricing"><a href="#AWS-Fargate-Spot-Pricing" class="headerlink" title="AWS Fargate Spot Pricing"></a>AWS Fargate Spot Pricing</h2><p>AWS Fargate received two new features to save money: </p><ol><li>20-52% saving with <a href="https://aws.amazon.com/savingsplans/pricing/" target="_blank" rel="noopener">Compute Savings Plans</a></li><li>69% saving with <a href="https://aws.amazon.com/fargate/pricing/" target="_blank" rel="noopener">Spot Pricing</a></li></ol><p>Use Spot pricing with interruption tolerant workloads (2-minute termination notice) and Savings Plans to cover your baseline usage of workloads that require. Unfortunately, we still wait for CloudFormation support.</p><table class="table table-striped table-responsive"><thead><tr><th>Ready to build?</th><th></th></tr></thead><tbody><tr><td>General Available?</td><td>✅</td></tr><tr><td>Available in all regions?</td><td>✅</td></tr><tr><td>Manage with CloudFormation?</td><td>❌</td></tr></tbody></table><p>If you are interested in the details of Savings Plans, read our (/reduce-your-aws-bill-with-savings-plans/)[Reduce your AWS bill with Savings Plans] post.</p><h2 id="ECS-Cluster-Auto-Scaling"><a href="#ECS-Cluster-Auto-Scaling" class="headerlink" title="ECS Cluster Auto Scaling"></a>ECS Cluster Auto Scaling</h2><p>If you run your ECS cluster on EC2 instances instead of Fargate, this is big news for you: Scaling <a href="/ecs-vs-fargate-whats-the-difference/#ECS-container-instance">EC2 container instances</a> was very hard, if not impossible. <a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-auto-scaling.html" target="_blank" rel="noopener">ECS Cluster Auto Scaling</a> solves the issue (as soon as CloudFormation support is added).</p><table class="table table-striped table-responsive"><thead><tr><th>Ready to build?</th><th></th></tr></thead><tbody><tr><td>General Available?</td><td>✅</td></tr><tr><td>Available in all regions?</td><td>✅</td></tr><tr><td>Manage with CloudFormation?</td><td>❌</td></tr></tbody></table><h2 id="Step-Functions-Express-Workflows"><a href="#Step-Functions-Express-Workflows" class="headerlink" title="Step Functions Express Workflows"></a>Step Functions Express Workflows</h2><p>You get cheaper Step Functions that can scale up to over 100,000 execution per second. The following limitations apply:</p><ul><li>Maximum execution duration is 5 minutes</li><li>Executions appear in CloudWatch Logs, not in the Step Functions API.</li><li>At-least-once execution guarantee (instead of exactly-once).</li><li>Does not support Job-run (.sync) or Callback (.waitForTaskToken) patterns.</li><li>Does not support Step Functions activities.</li></ul><table class="table table-striped table-responsive"><thead><tr><th>Ready to build?</th><th></th></tr></thead><tbody><tr><td>General Available?</td><td>✅</td></tr><tr><td>Available in all regions?</td><td>✅</td></tr><tr><td>Manage with CloudFormation?</td><td>✅</td></tr></tbody></table><h2 id="Amplify-DataStore"><a href="#Amplify-DataStore" class="headerlink" title="Amplify DataStore"></a>Amplify DataStore</h2><p>Amplify DataStore is a new Amplify capability to support offline-first applications. You can use the DataStore to write, read, and subscribe to changes no matter if the client is online or offline. The DataStore can optionally sync to the cloud as well as across devices. To sync with the cloud, the Amplify CLI will use the GraphQL schema to deploy an AWS AppSync backend with DynamoDB tables for each type and an additional table used for Delta Sync.</p><table class="table table-striped table-responsive"><thead><tr><th>Ready to build?</th><th></th></tr></thead><tbody><tr><td>General Available?</td><td>⚠️<sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup></td></tr><tr><td>Available in all regions?</td><td>✅</td></tr></tbody></table><h2 id="AWS-AppConfig"><a href="#AWS-AppConfig" class="headerlink" title="AWS AppConfig"></a>AWS AppConfig</h2><p><a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/appconfig.html" target="_blank" rel="noopener">AWS AppConfig</a> is a way to deploy configuration data to environments in a controlled and safe manner. Configuration data can be validated by a JSON schema or Lambda function. Changes are rolled out step-by-step and are rolled back if a CloudWatch Alarm fires within a “bake time”. Configuration data itself is fetched from SSM Parameter Store or an SSM Document (no, S3 is not supported).</p><p>The biggest surprise might be that your application has to poll AppConfig in regular intervals to get configuration updates. The following Node.js snippet shows how it works.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> appconfig = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">AppConfig</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2019-10-09&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> clientId = <span class="string">&#x27;i-123456&#x27;</span>; <span class="comment">// something that is stable troughout the lifetime of the process (see below for some ideas).</span></span><br><span class="line"><span class="keyword">let</span> configuration = &#123;&#125;;</span><br><span class="line"><span class="keyword">let</span> lastConfigurationVersion = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">fetchConfig</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> params = &#123;</span><br><span class="line">    <span class="title class_">Application</span>: <span class="string">&#x27;i6zrz6r&#x27;</span>, <span class="comment">// surprisingly, the name is NOT used. Instead, an ID that is not visible in the UI.</span></span><br><span class="line">    <span class="title class_">ClientId</span>: clientId,</span><br><span class="line">    <span class="title class_">Configuration</span>: <span class="string">&#x27;3gsav1a&#x27;</span>, <span class="comment">// surprisingly, the name is NOT used. Instead, an ID that is not visible in the UI.</span></span><br><span class="line">    <span class="title class_">Environment</span>: <span class="string">&#x27;c2h9vk6&#x27;</span> <span class="comment">// surprisingly, the name is NOT used. Instead, an ID that is not visible in the UI.</span></span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">if</span> (lastConfigurationVersion !== <span class="literal">null</span>) &#123;</span><br><span class="line">    params.<span class="property">ClientConfigurationVersion</span> = lastConfigurationVersion;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> data = <span class="keyword">await</span> appconfig.<span class="title function_">getConfiguration</span>(params).<span class="title function_">promise</span>();</span><br><span class="line">  <span class="keyword">if</span> (data.<span class="property">ConfigurationVersion</span> !== lastConfigurationVersion) &#123;</span><br><span class="line">    configuration = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(data.<span class="property">Content</span>.<span class="title function_">toString</span>(<span class="string">&#x27;utf8&#x27;</span>));</span><br><span class="line">    lastConfigurationVersion = data.<span class="property">ConfigurationVersion</span>;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(configuration);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">setInterval</span>(<span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">fetchConfig</span>();</span><br><span class="line">&#125;, <span class="number">15000</span>);</span><br><span class="line"></span><br><span class="line"><span class="title function_">fetchConfig</span>()</span><br><span class="line">  .<span class="title function_">then</span>(<span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;initilized&#x27;</span>));</span><br></pre></td></tr></table></figure><p>Some ideas for the <code>clientId</code> which can be up to 64 characters in length:</p><ul><li>EC2: Instance ID provided by the <a href="https://docs.aws.amazon.com/en_us/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html" target="_blank" rel="noopener">Metadata Service</a></li><li>ECS&#x2F;Fargate: hashed Task ARN provided by the <a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-metadata-endpoint-v3.html" target="_blank" rel="noopener">Metadata Endpoint</a></li><li>Lambda: hashed environment variable <code>AWS_LAMBDA_LOG_STREAM_NAME</code> to identify the function instance <sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup></li></ul><table class="table table-striped table-responsive"><thead><tr><th>Ready to build?</th><th></th></tr></thead><tbody><tr><td>General Available?</td><td>✅</td></tr><tr><td>Available in all regions?</td><td>✅</td></tr><tr><td>Manage with CloudFormation?</td><td>❌</td></tr></tbody></table><p>For reasons only AWS understands AWS AppConfig is part of AWS Systems Manager but still has it’s own service name in SDKs &amp; <a href="https://docs.aws.amazon.com/cli/latest/reference/appconfig/index.html" target="_blank" rel="noopener">CLI</a>.</p><p>That’s it! These were the 10 most important announcements from re:Invent. See you again in Las Vegas next year.</p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. I'm not sure about how we can use AppConfig with Lambda functions, which are only invoked from time to time. They will not be able to make the <code>GetConfiguration</code> API call in a regular interval. <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. Browser support is GA. Support for iOS and Android is in preview. <a href="#fnref:2" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to dockerize your Node.js Express application for AWS Fargate?</title>
      <link>https://cloudonaut.io/how-to-dockerize-your-nodejs-express-application-for-aws-fargate/</link>
      <description>Learn to dockerize your Node.js application based on Express and run it on AWS Fargate. With AWS Fargate, you launch Docker containers in the cloud without any need to manage virtual machines. Operating Docker containers could not be easier.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-dockerize-your-nodejs-express-application-for-aws-fargate/</guid>
      <pubDate>Thu, 05 Dec 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>My first project with <a href="https://nodejs.org/" target="_blank" rel="noopener">Node.js</a> - an asynchronous event-driven JavaScript runtime, designed to build scalable network applications - was building an online trading platform in 2013. Since then, Node.js is one of my favorite technologies. I will show you how to dockerize your Node.js application based on <a href="https://expressjs.com/" target="_blank" rel="noopener">Express</a> - a fast, unopinionated, minimalist web framework - and run it on AWS Fargate in this blog bost. I like AWS Fargate because running containers in the cloud were never easier.</p><p>Read on to learn how to build a Docker image for a Node.js application.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/12/docker-ship@730w.webp 730w, /images/2019/12/docker-ship@730w2x.webp 1460w, /images/2019/12/docker-ship@610w.webp 610w, /images/2019/12/docker-ship@610w2x.webp 1220w, /images/2019/12/docker-ship@450w.webp 450w, /images/2019/12/docker-ship@450w2x.webp 900w, /images/2019/12/docker-ship@330w.webp 330w, /images/2019/12/docker-ship@330w2x.webp 660w, /images/2019/12/docker-ship@545w.webp 545w, /images/2019/12/docker-ship@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/12/docker-ship@730w.jpg 730w, /images/2019/12/docker-ship@730w2x.jpg 1460w, /images/2019/12/docker-ship@610w.jpg 610w, /images/2019/12/docker-ship@610w2x.jpg 1220w, /images/2019/12/docker-ship@450w.jpg 450w, /images/2019/12/docker-ship@450w2x.jpg 900w, /images/2019/12/docker-ship@330w.jpg 330w, /images/2019/12/docker-ship@330w2x.jpg 660w, /images/2019/12/docker-ship@545w.jpg 545w, /images/2019/12/docker-ship@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/12/docker-ship.jpg" alt="Dockerize your Node.js application for AWS Fargate" title="Dockerize your Node.js application for AWS Fargate"></picture></p><h2 id="Building-the-Docker-image"><a href="#Building-the-Docker-image" class="headerlink" title="Building the Docker image"></a>Building the Docker image</h2><p>The <code>Dockerfile</code> is based on the <a href="https://hub.docker.com/_/node/" target="_blank" rel="noopener">official Node.js Docker Image</a>: <code>node:10.16.2-stretch</code>. Static files (folders <code>img</code> and <code>css</code>) are served by Express as well as the dynamic parts. The following details are required to understand the <code>Dockerfile</code>:</p><ul><li><code>envsubst</code> is used to generate the config file from environment variables</li><li><code>npm ci --only=production</code> installs the dependencies declared in <code>package.json</code> (<code>package-lock.json</code>, to be more precise)</li><li>The Express application listens on port 8080</li><li>The Express application’s entry point is <code>server.js</code> and can be started with <code>node server.js</code></li></ul><p>A simple <code>server.js</code> file follows. Yours likely is more complicated.</p><figure class="highlight javascript"><figcaption><span>server.js</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = <span class="title function_">express</span>();</span><br><span class="line">app.<span class="title function_">use</span>(<span class="string">&#x27;/css&#x27;</span>, express.<span class="title function_">static</span>(<span class="string">&#x27;css&#x27;</span>));</span><br><span class="line">app.<span class="title function_">use</span>(<span class="string">&#x27;/img&#x27;</span>, express.<span class="title function_">static</span>(<span class="string">&#x27;img&#x27;</span>));</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">&#x27;/health-check&#x27;</span>, <span class="function">(<span class="params">req, res, next</span>) =&gt;</span> &#123;</span><br><span class="line">  res.<span class="title function_">sendStatus</span>(<span class="number">200</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">8080</span>, <span class="string">&#x27;0.0.0.0&#x27;</span>);</span><br></pre></td></tr></table></figure><blockquote><p><strong>Customization</strong> Most likely, your folder structure is different. Therefore, adapt the <em>Copy config files</em> and <em>Copy Node.js files</em> section in the following Dockerfile to your needs.</p></blockquote><figure class="highlight dockerfile"><figcaption><span>docker&#x2F;Dockerfile</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">FROM</span> node:<span class="number">10.16</span>.<span class="number">2</span>-stretch</span><br><span class="line"></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /usr/src/app</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">ENV</span> NODE_ENV production</span><br><span class="line"></span><br><span class="line"><span class="comment"># Install envsubst</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apt-get update &amp;&amp; apt-get install -y gettext</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> docker/custom-entrypoint /usr/local/bin/</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">chmod</span> u+x /usr/local/bin/custom-entrypoint</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;custom-entrypoint&quot;</span>]</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">mkdir</span> /usr/src/app/config/</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Copy config files</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> config/*.tmp /tmp/config/</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Install Node.js dependencies</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> package*.json /usr/src/app/</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> npm ci --only=production</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Copy Node.js files</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> css /usr/src/app/css</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> img /usr/src/app/img</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> views /usr/src/app/views</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> server.js /usr/src/app/</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Expose port 8080 and start Node.js server</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">8080</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;node&quot;</span>, <span class="string">&quot;server.js&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>The custom entrypoint is used to generate the config file from environment variables with <code>envsubst</code>.</p><figure class="highlight bash"><figcaption><span>docker&#x2F;custom-entrypoint</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;generating configuration files&quot;</span></span><br><span class="line">FILES=/tmp/config/*</span><br><span class="line"><span class="keyword">for</span> f <span class="keyword">in</span> <span class="variable">$FILES</span></span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">  c=$(<span class="built_in">basename</span> <span class="variable">$f</span> .tmp)</span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;... <span class="variable">$c</span>&quot;</span></span><br><span class="line">  envsubst &lt; <span class="variable">$f</span> &gt; /usr/src/app/config/<span class="variable">$&#123;c&#125;</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;starting <span class="variable">$@</span>&quot;</span></span><br><span class="line"><span class="built_in">exec</span> <span class="string">&quot;<span class="variable">$@</span>&quot;</span></span><br></pre></td></tr></table></figure><p>Next, you will learn how to test your containers and application locally.</p><h3 id="Testing-locally"><a href="#Testing-locally" class="headerlink" title="Testing locally"></a>Testing locally</h3><p>Use <a href="https://docs.docker.com/compose/" target="_blank" rel="noopener">Docker Compose</a> to run your application locally. The following <code>docker-compose.yml</code> file configures Docker Compose and starts two containers: Node.js and a MySQL database.</p><figure class="highlight yaml"><figcaption><span>docker&#x2F;docker-compose.yml</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&#x27;3&#x27;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">nodejs:</span></span><br><span class="line">    <span class="attr">build:</span></span><br><span class="line">      <span class="attr">context:</span> <span class="string">&#x27;..&#x27;</span></span><br><span class="line">      <span class="attr">dockerfile:</span> <span class="string">&#x27;docker/Dockerfile&#x27;</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;8080:8080&#x27;</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">mysql</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">DATABASE_HOST:</span> <span class="string">mysql</span></span><br><span class="line">      <span class="attr">DATABASE_NAME:</span> <span class="string">app</span></span><br><span class="line">      <span class="attr">DATABASE_USER:</span> <span class="string">app</span></span><br><span class="line">      <span class="attr">DATABASE_PASSWORD:</span> <span class="string">secret</span></span><br><span class="line">  <span class="attr">mysql:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">&#x27;mysql:5.6&#x27;</span></span><br><span class="line">    <span class="attr">command:</span> <span class="string">&#x27;--default-authentication-plugin=mysql_native_password&#x27;</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;3306:3306&#x27;</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">MYSQL_ROOT_PASSWORD:</span> <span class="string">secret</span></span><br><span class="line">      <span class="attr">MYSQL_DATABASE:</span> <span class="string">app</span></span><br><span class="line">      <span class="attr">MYSQL_USER:</span> <span class="string">app</span></span><br><span class="line">      <span class="attr">MYSQL_PASSWORD:</span> <span class="string">secret</span></span><br></pre></td></tr></table></figure><p>The following command starts the application:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker-compose -f docker/docker-compose.yml up --build</span><br></pre></td></tr></table></figure><p>Magically, Docker Compose will spin up two containers: Node.js and MySQL. Point your browser to <a href="http://localhost:8080/" target="_blank" rel="noopener">http://localhost:8080</a> to check that your web application is up and running. The log files of all containers will show up in your terminal, which simplifies debugging a lot.</p><p>After you have verified that your application is working correctly, cancel the running <code>docker-compose</code> process by pressing <code>CTRL + C</code>, and tear down the containers:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker-compose -f docker/docker-compose.yml down</span><br></pre></td></tr></table></figure><h2 id="Deploying-on-AWS"><a href="#Deploying-on-AWS" class="headerlink" title="Deploying on AWS"></a>Deploying on AWS</h2><p>You are now ready to deploy your application on AWS.</p><p>(1) Build Docker image:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker build -t nodejs-express:latest -f docker/Dockerfile .</span><br></pre></td></tr></table></figure><p>(2) Create ECR repository:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws ecr create-repository --repository-name nodejs-express \</span><br><span class="line">  --query <span class="string">&#x27;repository.repositoryUri&#x27;</span> --output text</span><br></pre></td></tr></table></figure><p>(3) Login to Docker registry (ECR):</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$(aws ecr get-login --no-include-email)</span><br></pre></td></tr></table></figure><p>(4) Tag Docker image:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker tag nodejs-express:latest \</span><br><span class="line">111111111111.dkr.ecr.eu-west-1.amazonaws.com/\</span><br><span class="line">nodejs-express:latest</span><br></pre></td></tr></table></figure><p>(5) Push Docker image:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker push \</span><br><span class="line">111111111111.dkr.ecr.eu-west-1.amazonaws.com/\</span><br><span class="line">nodejs-express:latest</span><br></pre></td></tr></table></figure><p>There is only one step missing: you need to spin up the cloud infrastructure.</p><ol><li>Use our <a href="https://templates.cloudonaut.io/en/stable/fargate/#using-the-clusters-load-balancer-and-path-andor-host-based-routing" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a>.</li><li>Use our <a href="https://github.com/cfn-modules/docs/tree/master/examples/fargate-alb-proxy-pattern" target="_blank" rel="noopener">cfn-modules</a>.</li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>Show your Tool: Jenkins Pipeline Step Plugin for AWS</title>
      <link>https://cloudonaut.io/show-your-tool-jekins-pipeline-aws-plugin/</link>
      <description>
        <![CDATA[<p>In this series, we present AWS tooling from the community for the community. We talk directly with the tool makers. Who are they? What pr]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/show-your-tool/">Show your Tool</category>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <guid isPermaLink="true">https://cloudonaut.io/show-your-tool-jekins-pipeline-aws-plugin/</guid>
      <pubDate>Mon, 02 Dec 2019 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>In this series, we present AWS tooling from the community for the community. We talk directly with the tool makers. Who are they? What problem does the tool solve? And what motivates them to contribute to open-source AWS tooling.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@730w.webp 730w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@730w2x.webp 1460w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@610w.webp 610w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@610w2x.webp 1220w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@450w.webp 450w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@450w2x.webp 900w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@330w.webp 330w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@330w2x.webp 660w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@545w.webp 545w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@730w.jpg 730w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@730w2x.jpg 1460w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@610w.jpg 610w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@610w2x.jpg 1220w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@450w.jpg 450w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@450w2x.jpg 900w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@330w.jpg 330w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@330w2x.jpg 660w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@545w.jpg 545w, /images/2019/12/show-your-tool-jekins-pipeline-aws-plugin@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/12/show-your-tool-jekins-pipeline-aws-plugin.jpg" alt="Show your Tool" title="Show your Tool"></picture></p><p>This time, we talk with Thorsten Höger about the <a href="https://github.com/jenkinsci/pipeline-aws-plugin" target="_blank" rel="noopener">Jenkins Pipeline Step Plugin for AWS</a>. You can connect with Thorsten on <a href="https://x.com/hoegertn" target="_blank" rel="noopener">Twitter</a> or <a href="https://www.linkedin.com/in/hoegertn/" target="_blank" rel="noopener">LinkedIn</a>.</p><p><em>cloudonaut: Tell us a little about yourself, your history with AWS, and your motivation to develop AWS tooling.</em><br>Thorsten Höger: I am CEO and cloud consultant at Taimos, where I am advising customers on how to use AWS. Being a developer, I focus on improving development processes and automating everything to build efficient deployment pipelines for customers of all sizes.</p><p>I organize the AWS User Group in Stuttgart (Germany), and I am frequently speaking at Meetups, BarCamps, and other community events. In 2017 I was appointed AWS Community Hero.</p><p>As a supporter of open-source software, I am maintaining or contributing to several projects, like test frameworks for AWS Lambda, Amazon Alexa, or developer tools for AWS CloudFormation and AWS CDK.</p><p><em>cloudonaut: What problem does your tool solve?</em><br>Thorsten Höger: I maintain a Jenkins plugin that provides pipeline steps to interact with the AWS API conveniently.</p><p><em>cloudonaut: Who should use your tool? Who should not?</em><br>Thorsten Höger: Obviously, it only works for Jenkins users that use Jenkins pipelines.</p><p><em>cloudonaut: Show us a short demo of your tool</em><br>Thorsten Höger: The following example deploys a static website.</p><ol><li>An IAM role is assumed by Jenkins to grant least privilege access to an AWS account</li><li>A CloudFormation stack is created or updated to deploy the necessary AWS resources (S3 Bucket, CloudFront distribution, …)</li><li>Files are uploaded to S3.</li><li>The CloudFront distribution is invalidated.</li></ol><figure class="highlight php"><table><tr><td class="code"><pre><span class="line"><span class="title function_ invoke__">withAWS</span>(<span class="attr">role</span>:<span class="string">&#x27;admin&#x27;</span>, <span class="attr">roleAccount</span>:<span class="string">&#x27;123456789012&#x27;</span>) &#123;</span><br><span class="line">    def outputs = <span class="title function_ invoke__">cfnUpdate</span>(<span class="attr">stack</span>:<span class="string">&#x27;my-stack&#x27;</span>, <span class="attr">file</span>:<span class="string">&#x27;template.yaml&#x27;</span>)</span><br><span class="line">    <span class="title function_ invoke__">s3Upload</span>(<span class="attr">file</span>:’index.html<span class="string">&#x27;, bucket:outputs.BucketName, path:&#x27;</span>index.html<span class="string">&#x27;)</span></span><br><span class="line"><span class="string">    cfInvalidate(distribution:outputs.DistributionId, paths:[&#x27;</span><span class="comment">/*&#x27;], waitForCompletion: true)</span></span><br><span class="line"><span class="comment">&#125;</span></span><br></pre></td></tr></table></figure><p><em>cloudonaut: How can we use your tool?</em><br>Thorsten Höger: You can install the “Pipeline: AWS Steps” plugin via the Jenkins Update Center.</p><ol><li>In Jenkins, click on <strong>Manage Jenkins</strong></li><li>Click on <strong>Manage Plugins</strong></li><li>Select the <strong>Available</strong> tab</li><li>Search for <em>aws steps</em></li><li>Select and install the plugin</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/12/jenkins-pipeline-aws-plugin@730w.webp 730w, /images/2019/12/jenkins-pipeline-aws-plugin@730w2x.webp 1460w, /images/2019/12/jenkins-pipeline-aws-plugin@610w.webp 610w, /images/2019/12/jenkins-pipeline-aws-plugin@610w2x.webp 1220w, /images/2019/12/jenkins-pipeline-aws-plugin@450w.webp 450w, /images/2019/12/jenkins-pipeline-aws-plugin@450w2x.webp 900w, /images/2019/12/jenkins-pipeline-aws-plugin@330w.webp 330w, /images/2019/12/jenkins-pipeline-aws-plugin@330w2x.webp 660w, /images/2019/12/jenkins-pipeline-aws-plugin@545w.webp 545w, /images/2019/12/jenkins-pipeline-aws-plugin@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/12/jenkins-pipeline-aws-plugin@730w.png 730w, /images/2019/12/jenkins-pipeline-aws-plugin@730w2x.png 1460w, /images/2019/12/jenkins-pipeline-aws-plugin@610w.png 610w, /images/2019/12/jenkins-pipeline-aws-plugin@610w2x.png 1220w, /images/2019/12/jenkins-pipeline-aws-plugin@450w.png 450w, /images/2019/12/jenkins-pipeline-aws-plugin@450w2x.png 900w, /images/2019/12/jenkins-pipeline-aws-plugin@330w.png 330w, /images/2019/12/jenkins-pipeline-aws-plugin@330w2x.png 660w, /images/2019/12/jenkins-pipeline-aws-plugin@545w.png 545w, /images/2019/12/jenkins-pipeline-aws-plugin@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/12/jenkins-pipeline-aws-plugin.png" alt="Installing the Jenkins Pipeline Step Plugin for AWS" title="Installing the Jenkins Pipeline Step Plugin for AWS"></picture></p><p><em>cloudonaut: Where can we find more information about your tool?</em><br>Thorsten Höger: You can find all the available steps in the <a href="https://github.com/jenkinsci/pipeline-aws-plugin" target="_blank" rel="noopener">README on GitHub</a>.</p><p><em>cloudonaut: Are you aware of tools that solve a similar problem than yours? What’s the difference?</em><br>Thorsten Höger: You could use the AWS CLI in a <a href="https://jenkins.io/doc/pipeline/steps/workflow-durable-task-step/#sh-shell-script" target="_blank" rel="noopener">shell step</a>, or you could use alternatives to Jenkins like GitLab CI, GitHub Action, CodePipeline, …</p><p><em>cloudonaut: What’s the roadmap for your tool? Are you planning any significant releases?</em><br>Thorsten Höger: No major changes are in the pipeline. But I add steps from time to time, and I’m always looking for <a href="https://github.com/jenkinsci/pipeline-aws-plugin/pulls?q=is:pr+is:closed" target="_blank" rel="noopener">contributions from the community</a>.</p><p>To accommodate non-Jenkins users, I started <a href="https://github.com/taimos/aws-authenticate" target="_blank" rel="noopener">AWS Authenticate</a>. The new project helps you to configure your AWS access in CI&#x2F;CD environments. You can set the region, the IAM profile to use, or assume IAM roles with one CLI call. For example:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash -e</span></span><br><span class="line">(</span><br><span class="line">    <span class="built_in">eval</span> <span class="string">&quot;<span class="subst">$(aws-authenticate auth --role admin --roleAccount 123456789012)</span>&quot;</span></span><br><span class="line">    aws s3 <span class="built_in">cp</span> index.html s3://my-bucket/index.html</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p><em>cloudonaut: How do you stay motivated to maintain your open source project?</em><br>Thorsten Höger: I’m working as a <a href="https://www.taimos.com/" target="_blank" rel="noopener">freelancer</a>, and I use the plugin in many customer projects to speed up pipeline development times. I’m also amazed about how many plugin users talk to me during events and say thank you.</p><p><em>cloudonaut: Are you attending any conferences within the next few months where the community can get in touch?</em><br>Thorsten Höger: Yes, I’m in Las Vegas this week at AWS re:Invent. I will give a talk on Tuesday morning about Using the AWS CDK construct libraries to meet enterprise compliance. Feel free to talk to me after the talk or meet me during Taco Tuesday with AWS Heroes.</p><p>–</p><p>What tools do you use to make your AWS work easier? <a href="mailto:&#x68;&#x65;&#108;&#x6c;&#x6f;&#64;&#x63;&#x6c;&#x6f;&#117;&#x64;&#x6f;&#x6e;&#97;&#x75;&#116;&#x2e;&#x69;&#111;">Share your favorite tool with us</a>!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Checklist: Is your application ready for a container cluster?</title>
      <link>https://cloudonaut.io/checklist-container-cluster-ecs-fargate/</link>
      <description>Is your application ready to run on a container cluster? Use this checklist to find out whether you are good to deploy your application on Amazon Elastic Container Service (ECS) and AWS Fargate or any other container cluster solution.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/checklist-container-cluster-ecs-fargate/</guid>
      <pubDate>Thu, 28 Nov 2019 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Is your application ready to run on a container cluster? Use this checklist to find out whether you are good to deploy your application on Amazon Elastic Container Service (ECS) and AWS Fargate or any other container cluster solution.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/11/checklist@730w.webp 730w, /images/2019/11/checklist@730w2x.webp 1460w, /images/2019/11/checklist@610w.webp 610w, /images/2019/11/checklist@610w2x.webp 1220w, /images/2019/11/checklist@450w.webp 450w, /images/2019/11/checklist@450w2x.webp 900w, /images/2019/11/checklist@330w.webp 330w, /images/2019/11/checklist@330w2x.webp 660w, /images/2019/11/checklist@545w.webp 545w, /images/2019/11/checklist@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/11/checklist@730w.jpg 730w, /images/2019/11/checklist@730w2x.jpg 1460w, /images/2019/11/checklist@610w.jpg 610w, /images/2019/11/checklist@610w2x.jpg 1220w, /images/2019/11/checklist@450w.jpg 450w, /images/2019/11/checklist@450w2x.jpg 900w, /images/2019/11/checklist@330w.jpg 330w, /images/2019/11/checklist@330w2x.jpg 660w, /images/2019/11/checklist@545w.jpg 545w, /images/2019/11/checklist@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/11/checklist.jpg" alt="Checklist" title="Checklist"></picture></p><p>Does your application fulfill the following five requirements?</p><h2 id="✅-Stateless-avoid-persisting-data"><a href="#✅-Stateless-avoid-persisting-data" class="headerlink" title="✅ Stateless: avoid persisting data"></a>✅ Stateless: avoid persisting data</h2><p>Your application (call it a microservice if you want to) is stateless. Answering a request or processing a job does not rely on reading data stored by previous requests or jobs. This applies to data from memory as well as a local disk.</p><p>Instead of that, your application stores data in a SQL&#x2F;NoSQL database (e.g., RDS or DynamoDB), an in-memory database (Elasticache), or any other fully-managed storage service (e.g., S3).</p><h2 id="✅-Logging-write-to-stdout-and-stderr"><a href="#✅-Logging-write-to-stdout-and-stderr" class="headerlink" title="✅ Logging: write to stdout and stderr"></a>✅ Logging: write to stdout and stderr</h2><p>Your application writes log messages to standard output (<code>stdout</code>) and standard error (<code>stderr</code>). Do not write log messages to files. (see <em>Stateless</em>). Docker has built-in support to ship log messages from <code>stdout</code> and <code>stderr</code> to various centralized logging solutions (e.g., CloudWatch Logs). Check out <a href="/a-simple-way-to-manage-log-messages-from-containers-cloudwatch-logs/">A simple way to manage log messages from containers: CloudWatch Logs</a> to learn more.</p><h2 id="✅-Configuration-use-environment-variables"><a href="#✅-Configuration-use-environment-variables" class="headerlink" title="✅ Configuration: use environment variables"></a>✅ Configuration: use environment variables</h2><p>Your application reads configuration parameters from environment variables (e.g., the database endpoint or any other service endpoint). Do not use files to store the configuration for your application. </p><p>Use a templating engine for configuration files if you are containerizing a legacy application. I prefer <code>envsubst</code> to do so. Alternatively, you could have a look at <a href="/dockerizing-legacy-applications-with-confd/">Dockerizing legacy applications with confd</a>.</p><h2 id="✅-Process-restrict-to-one-process"><a href="#✅-Process-restrict-to-one-process" class="headerlink" title="✅ Process: restrict to one process"></a>✅ Process: restrict to one process</h2><p>Your container starts exactly one main process. If your application consists of more than one process, split them up into multiple containers. For example, if you run NGINX and PHP-FPM, create two containers.</p><h2 id="✅-Remote-access-disable-SSH"><a href="#✅-Remote-access-disable-SSH" class="headerlink" title="✅ Remote access: disable SSH"></a>✅ Remote access: disable SSH</h2><p>Your container does not start an SSH daemon. Do not install or enable SSH within a container (see <em>Process</em>s). Use <code>docker attach</code> to log into a container if needed for debugging. On top of that, optimize your logging.</p><h2 id="✅-Shutdown-avoid-canceling-requests-and-jobs"><a href="#✅-Shutdown-avoid-canceling-requests-and-jobs" class="headerlink" title="✅ Shutdown: avoid canceling requests and jobs"></a>✅ Shutdown: avoid canceling requests and jobs</h2><p>Your application receives <code>KILL</code> signals and shuts down gracefully. Test whether the KILL signal triggered by <code>docker kill</code> leads to your application stopping to answer new requests or start new jobs and terminate after the last request or job has been completed.</p><ul><li>Does your Dockerfile contain <code>ENTRYPOINT</code> or <code>CMD</code> in shell form? Your main process will not receive any <code>KILL</code> signals.</li><li>Are you starting your main process from a shell script? Make sure you are using <code>exec</code> to do so.</li></ul><p>When using Fargate it is necessary, that your application is able to shutdown gracefully within 2 minutes.</p><h2 id="🎉-Summary"><a href="#🎉-Summary" class="headerlink" title="🎉 Summary"></a>🎉 Summary</h2><p>Checked all five requirements from the checklist? Happy you! Your application is ready for ECS and Fargate or any other container cluster solution.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Reduce your AWS bill with Savings Plans</title>
      <link>https://cloudonaut.io/reduce-your-aws-bill-with-savings-plans/</link>
      <description>AWS made a prominent announcement on November 6th, 2019: AWS Savings Plans. It was never easier to get a discount on compute capacity by committing to a monthly consumption and paying upfront. This blog post introduced AWS Savings Plans and compares them to other options to reduce your AWS bill as well.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/costs/">costs</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/reduce-your-aws-bill-with-savings-plans/</guid>
      <pubDate>Tue, 26 Nov 2019 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>We are getting used to consuming compute capacity on-demand. The pay-per-use model is an essential benefit of the cloud. However, the cloud provider has to build data centers and buy hardware in advance. Doing so requires capacity planning and upfront investments. Therefore, the cloud provider must add the resulting costs to the on-demand price.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/11/reducing-costs@730w.webp 730w, /images/2019/11/reducing-costs@730w2x.webp 1460w, /images/2019/11/reducing-costs@610w.webp 610w, /images/2019/11/reducing-costs@610w2x.webp 1220w, /images/2019/11/reducing-costs@450w.webp 450w, /images/2019/11/reducing-costs@450w2x.webp 900w, /images/2019/11/reducing-costs@330w.webp 330w, /images/2019/11/reducing-costs@330w2x.webp 660w, /images/2019/11/reducing-costs@545w.webp 545w, /images/2019/11/reducing-costs@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/11/reducing-costs@730w.jpg 730w, /images/2019/11/reducing-costs@730w2x.jpg 1460w, /images/2019/11/reducing-costs@610w.jpg 610w, /images/2019/11/reducing-costs@610w2x.jpg 1220w, /images/2019/11/reducing-costs@450w.jpg 450w, /images/2019/11/reducing-costs@450w2x.jpg 900w, /images/2019/11/reducing-costs@330w.jpg 330w, /images/2019/11/reducing-costs@330w2x.jpg 660w, /images/2019/11/reducing-costs@545w.jpg 545w, /images/2019/11/reducing-costs@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/11/reducing-costs.jpg" alt="Reducing costs" title="Reducing costs"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/9-reduce-your-aws-bill-with-savings-plans/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>However, you might not need the flexibility of on-demand capacity. Therefore, AWS offers the following deals since 2009:</p><ul><li>You commit to a monthly usage of compute capacity, AWS grants a discount on the on-demand price.</li><li>You pay for compute capacity upfront, AWS grants a discount on the on-demand price.</li></ul><p>Overall, AWS offers a discount of up to 75% on the on-demand price for EC2 instances. That’s a huge opportunity to reduce your AWS bill.</p><h2 id="What-are-Savings-Plans"><a href="#What-are-Savings-Plans" class="headerlink" title="What are Savings Plans?"></a>What are Savings Plans?</h2><p>AWS made a prominent announcement on November 6th, 2019: AWS Savings Plans. It was never easier to get a discount on compute capacity by committing to a monthly consumption and paying upfront.</p><p>Starting from <a href="https://console.aws.amazon.com/billing/" target="_blank" rel="noopener">Billing and Cost Management Dashboard</a>, you can purchase a savings plan.</p><blockquote><p>AWS calculates recommendations for purchasing savings plans based on historical usage. Unfortunately, that feature does not work with AWS accounts that are linked to AWS Organizations right now.</p></blockquote><p>Let me explain the example illustrated in the following screenshot:</p><ul><li>Commitment to pay for at least USD 10 per hour for compute usage. Not matter if you are using that much compute capacity at all.</li><li>Pay USD 87,600.00 upfront for a year of compute usage.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/11/purchase-savings-plans@730w.webp 730w, /images/2019/11/purchase-savings-plans@730w2x.webp 1460w, /images/2019/11/purchase-savings-plans@610w.webp 610w, /images/2019/11/purchase-savings-plans@610w2x.webp 1220w, /images/2019/11/purchase-savings-plans@450w.webp 450w, /images/2019/11/purchase-savings-plans@450w2x.webp 900w, /images/2019/11/purchase-savings-plans@330w.webp 330w, /images/2019/11/purchase-savings-plans@330w2x.webp 660w, /images/2019/11/purchase-savings-plans@545w.webp 545w, /images/2019/11/purchase-savings-plans@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/11/purchase-savings-plans@730w.png 730w, /images/2019/11/purchase-savings-plans@730w2x.png 1460w, /images/2019/11/purchase-savings-plans@610w.png 610w, /images/2019/11/purchase-savings-plans@610w2x.png 1220w, /images/2019/11/purchase-savings-plans@450w.png 450w, /images/2019/11/purchase-savings-plans@450w2x.png 900w, /images/2019/11/purchase-savings-plans@330w.png 330w, /images/2019/11/purchase-savings-plans@330w2x.png 660w, /images/2019/11/purchase-savings-plans@545w.png 545w, /images/2019/11/purchase-savings-plans@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/11/purchase-savings-plans.png" alt="Purchase Savings Plans" title="Purchase Savings Plans"></picture></p><p>Fine, but what about the discount? In our example, the discount is applied to USD 10 per hour of compute usage. The official <a href="https://aws.amazon.com/savingsplans/pricing/" target="_blank" rel="noopener">Pricing with Savings Plans</a> page lists the price for Amazon EC2 instances and AWS Fargate. The following table contains some pricing examples for Linux on shared tenancy in US West (N. California).</p><table class="table table-striped table-responsive"><thead><tr><th>Resource Type</th><th style="text-align:right">On-Demand Rate</th><th style="text-align:right">Savings Plans Rate</th><th style="text-align:right">Discount</th></tr></thead><tbody><tr><td>m5a.xlarge</td><td style="text-align:right">$0.202</td><td style="text-align:right">$0.163</td><td style="text-align:right">19%</td></tr><tr><td>m5.large</td><td style="text-align:right">$0.112</td><td style="text-align:right">$0.09</td><td style="text-align:right">20%</td></tr><tr><td>m5a.large</td><td style="text-align:right">$0.101</td><td style="text-align:right">$0.081</td><td style="text-align:right">20%</td></tr><tr><td>c5.large</td><td style="text-align:right">$0.106</td><td style="text-align:right">$0.078</td><td style="text-align:right">26%</td></tr><tr><td>r5.metal</td><td style="text-align:right">$6.72</td><td style="text-align:right">$4.544</td><td style="text-align:right">32%</td></tr><tr><td>m2.2xlarge</td><td style="text-align:right">$0.55</td><td style="text-align:right">$0.259</td><td style="text-align:right">53%</td></tr></tbody></table><p>Interestingly, the savings plans discount in US West (N. California) varies from 19% to 53% depending on the instance type. Discounts vary between regions, as well. Keep in mind that the granted discount depends on the term of your commitment as well as on how much you are paying up front.</p><blockquote><p>Overall, the savings plans discounts vary between 10% and 72%.</p></blockquote><p>It is essential to mention that there are two options when purchasing a savings plan:</p><ul><li><strong>Compute Savings Plans</strong> apply to your compute usage within all regions, among all EC2 instances, as well as containers running on Fargate.</li><li><strong>EC2 Instance Savings Plans</strong> apply to EC2 instances of a specific instance family (e.g., <code>m5</code>) in a particular region only.</li></ul><p>I will discuss the differences in more detail later.</p><h2 id="Why-are-Savings-Plans-a-big-deal-for-containers-on-AWS"><a href="#Why-are-Savings-Plans-a-big-deal-for-containers-on-AWS" class="headerlink" title="Why are Savings Plans a big deal for containers on AWS?"></a>Why are Savings Plans a big deal for containers on AWS?</h2><p>Up until now, AWS Fargate offered on-demand pricing only. The following table shows the surcharge you had to pay for running a workload with 2 vCPU and 8 GiB memory on Fargate compared to running on EC2 in US West (N. California).</p><table class="table table-striped table-responsive"><thead><tr><th style="text-align:center">EC2 (On-Demand)</th><th style="text-align:center">EC2 (Reserved)*</th></tr></thead><tbody><tr><td style="text-align:center">20%</td><td style="text-align:center">70%</td></tr></tbody></table><p><code>*</code>: Standard reserved instance with a 1-year term and all upfront payment.</p><p>Therefore, running steady workloads on AWS Fargate was not competitive.</p><p>Fortunately, that changed with the announcement of AWS Savings Plans. AWS Savings Plans apply not only to EC2 but to Fargate as well.</p><blockquote><p>AWS Savings Plans grant a discount of 15% to 52% for AWS Fargate, depending on the region, term length, and upfront payment.</p></blockquote><p>With a savings plan on a 1-year term and all upfront payment, the surcharge for Fargate compared to AWS is now competitive especially, if you keep in mind that you typically need to overprovision a cluster of EC2 instances running your container workload.</p><table class="table table-striped table-responsive"><thead><tr><th style="text-align:center">EC2 (Reserved) <code>*</code></th></tr></thead><tbody><tr><td style="text-align:center">34%</td></tr></tbody></table><p><code>*</code>: Standard reserved instance with a 1-year term and all upfront payment.</p><p>In short, when you are running containers on AWS Fargate, you should buy a savings plan covering your baseline capacity.</p><h2 id="Reserved-Instances-vs-Savings-Plans"><a href="#Reserved-Instances-vs-Savings-Plans" class="headerlink" title="Reserved Instances vs. Savings Plans"></a>Reserved Instances vs. Savings Plans</h2><p>As I mentioned at the beginning, it was possible to commit to a monthly usage of compute capacity since the early days of AWS. The pricing option was and is called Reserved Instances.</p><p>Reserved Instances (RI) are available in different versions:</p><p>⚠️ &#x3D; requires modifying the reservation</p><table class="table table-striped table-responsive"><thead><tr><th>Type + Scope</th><th style="text-align:center">Standard + AZ</th><th style="text-align:center">Convertible + AZ</th><th style="text-align:center">Standard + Region</th><th style="text-align:center">Convertible + Region</th></tr></thead><tbody><tr><td>Discount</td><td style="text-align:center">💰💰</td><td style="text-align:center">💰</td><td style="text-align:center">💰💰</td><td style="text-align:center">💰</td></tr><tr><td>Region flexibility</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td></tr><tr><td>AZ flexibility</td><td style="text-align:center">⚠️</td><td style="text-align:center">⚠️</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td></tr><tr><td>Instance type flexibility</td><td style="text-align:center">⚠️</td><td style="text-align:center">⚠️</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td></tr><tr><td>Instance family flexibility</td><td style="text-align:center">❌</td><td style="text-align:center">⚠️</td><td style="text-align:center">❌</td><td style="text-align:center">⚠️</td></tr><tr><td>Capacity reservation</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td></tr></tbody></table><p>Next, have a look at the two different versions of Savings Plans.</p><table class="table table-striped table-responsive"><thead><tr><th>Type</th><th style="text-align:center">Compute</th><th style="text-align:center">EC2 Instance</th></tr></thead><tbody><tr><td>Discount</td><td style="text-align:center">💰</td><td style="text-align:center">💰💰</td></tr><tr><td>Region flexibility</td><td style="text-align:center">✅</td><td style="text-align:center">❌</td></tr><tr><td>AZ flexibility</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td></tr><tr><td>Instance type flexibility</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td></tr><tr><td>Instance family flexibility</td><td style="text-align:center">✅</td><td style="text-align:center">❌</td></tr><tr><td>Capacity reservation</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td></tr></tbody></table><p>So, Savings Plans offer more flexibility compared to Reserved Instances. However, the discount on the on-demand price is the same. The following table compares the discounts for an EC2 instance of type <code>m5.large</code> with Linux operating system on shared tenancy in US West (N. California) with a 1-year term and all upfront payment.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:center">Discount</th></tr></thead><tbody><tr><td>Compute Savings Plan</td><td style="text-align:center">20%</td></tr><tr><td>Reserved Instance Convertible</td><td style="text-align:center">20%</td></tr><tr><td>EC2 Instance Savings Plan</td><td style="text-align:center">30%</td></tr><tr><td>Reserved Instance Standard</td><td style="text-align:center">30%</td></tr></tbody></table><p>A diagram says more than a thousand words.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/11/savings-plans-vs-reserved-instances@730w.webp 730w, /images/2019/11/savings-plans-vs-reserved-instances@730w2x.webp 1460w, /images/2019/11/savings-plans-vs-reserved-instances@610w.webp 610w, /images/2019/11/savings-plans-vs-reserved-instances@610w2x.webp 1220w, /images/2019/11/savings-plans-vs-reserved-instances@450w.webp 450w, /images/2019/11/savings-plans-vs-reserved-instances@450w2x.webp 900w, /images/2019/11/savings-plans-vs-reserved-instances@330w.webp 330w, /images/2019/11/savings-plans-vs-reserved-instances@330w2x.webp 660w, /images/2019/11/savings-plans-vs-reserved-instances@545w.webp 545w, /images/2019/11/savings-plans-vs-reserved-instances@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/11/savings-plans-vs-reserved-instances@730w.png 730w, /images/2019/11/savings-plans-vs-reserved-instances@730w2x.png 1460w, /images/2019/11/savings-plans-vs-reserved-instances@610w.png 610w, /images/2019/11/savings-plans-vs-reserved-instances@610w2x.png 1220w, /images/2019/11/savings-plans-vs-reserved-instances@450w.png 450w, /images/2019/11/savings-plans-vs-reserved-instances@450w2x.png 900w, /images/2019/11/savings-plans-vs-reserved-instances@330w.png 330w, /images/2019/11/savings-plans-vs-reserved-instances@330w2x.png 660w, /images/2019/11/savings-plans-vs-reserved-instances@545w.png 545w, /images/2019/11/savings-plans-vs-reserved-instances@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/11/savings-plans-vs-reserved-instances.png" alt="Comparing Savings Plans and Reserved Instance options" title="Comparing Savings Plans and Reserved Instance options"></picture></p><p>It does not make sense to buy Reserved Instances any more as Savings Plans offer more flexibility for the same discount on the on-demand price.</p><h2 id="Do-I-need-Capacity-Reservations"><a href="#Do-I-need-Capacity-Reservations" class="headerlink" title="Do I need Capacity Reservations?"></a>Do I need Capacity Reservations?</h2><p>As mentioned in the previous section, Reserved Instances with scope AZ come with a capacity reservation. However, there is no such option with Savings Plans.</p><p>Do you need a capacity reservation? What happens in the rare case of an outage affecting an availability zone (AZ)? The pressure on the remaining AZs increases as customers replace their affected resources. Most likely, there will not be enough capacity in the remaining AZs to do so. Sooner or later, you will see <code>Not enough capacity</code> errors and are not able to launch any new EC2 instances anymore. Unless you have a capacity reservation.</p><p>So depending on your workload and RTO (Recovery Time Objective), you need a capacity reservation. Luckily, <a href="https://aws.amazon.com/about-aws/whats-new/2018/10/Amazon-EC2-now-offers-On-Demand-Capacity-Reservations/" target="_blank" rel="noopener">AWS announced standalone Capacity Reservations in October 2018</a>. Pricing and capacity reservation are two different things since then.</p><h2 id="How-to-monitor-Savings-Plans"><a href="#How-to-monitor-Savings-Plans" class="headerlink" title="How to monitor Savings Plans?"></a>How to monitor Savings Plans?</h2><p>Immediately, after you have purchased a Saving Plan, you should configure monitoring as well.</p><ul><li><strong>Saving Plans Utilization is low</strong>: you are using less compute resources than you are paying for.</li><li><strong>Saving Plans Coverage is low</strong>: your compute usage exceeds your Saving Plans.</li></ul><p>Doing so is simple by creating a budget, as illustrated in the following screenshot.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/11/budget-savings-plans@730w.webp 730w, /images/2019/11/budget-savings-plans@730w2x.webp 1460w, /images/2019/11/budget-savings-plans@610w.webp 610w, /images/2019/11/budget-savings-plans@610w2x.webp 1220w, /images/2019/11/budget-savings-plans@450w.webp 450w, /images/2019/11/budget-savings-plans@450w2x.webp 900w, /images/2019/11/budget-savings-plans@330w.webp 330w, /images/2019/11/budget-savings-plans@330w2x.webp 660w, /images/2019/11/budget-savings-plans@545w.webp 545w, /images/2019/11/budget-savings-plans@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/11/budget-savings-plans@730w.png 730w, /images/2019/11/budget-savings-plans@730w2x.png 1460w, /images/2019/11/budget-savings-plans@610w.png 610w, /images/2019/11/budget-savings-plans@610w2x.png 1220w, /images/2019/11/budget-savings-plans@450w.png 450w, /images/2019/11/budget-savings-plans@450w2x.png 900w, /images/2019/11/budget-savings-plans@330w.png 330w, /images/2019/11/budget-savings-plans@330w2x.png 660w, /images/2019/11/budget-savings-plans@545w.png 545w, /images/2019/11/budget-savings-plans@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/11/budget-savings-plans.png" alt="Budget to monitor Savings Plans Utilization" title="Budget to monitor Savings Plans Utilization"></picture></p><p>As described next, you should also check the IAM policies in your AWS accounts.</p><h2 id="Restrict-access-to-Savings-Plans"><a href="#Restrict-access-to-Savings-Plans" class="headerlink" title="Restrict access to Savings Plans"></a>Restrict access to Savings Plans</h2><p>Purchasing Reserved Instances and Savings Plans result in bigger financial commitments. Therefore, you might want to restrict who can make such purchases in your AWS accounts.</p><p>Are you using blacklists to avoid that certain users can buy EC2 Reserved Instances? If so, your IAM policy might look something like this.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2012-10-17&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Statement&quot;</span>: [&#123;</span><br><span class="line">    <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Deny&quot;</span>,</span><br><span class="line">    <span class="string">&quot;Action&quot;</span>: <span class="string">&quot;ec2:PurchaseReservedInstancesOffering&quot;</span>,</span><br><span class="line">    <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;*&quot;</span></span><br><span class="line">  &#125;]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The following policy demonstrates how you can deny access to Savings Plans as well.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2012-10-17&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Statement&quot;</span>: [&#123;</span><br><span class="line">    <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Deny&quot;</span>,</span><br><span class="line">    <span class="string">&quot;Action&quot;</span>: <span class="string">&quot;ec2:PurchaseReservedInstancesOffering&quot;</span>,</span><br><span class="line">    <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;*&quot;</span></span><br><span class="line">  &#125;, &#123;</span><br><span class="line">    <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Deny&quot;</span>,</span><br><span class="line">    <span class="string">&quot;Action&quot;</span>: <span class="string">&quot;savingsplans:CreateSavingsPlan&quot;</span>,</span><br><span class="line">    <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;*&quot;</span></span><br><span class="line">  &#125;]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>If you use AWS Organizations, you better deny the above operations in a Service Control Policy.</p><h2 id="What-about-RDS-Elasticsearch-DynamoDB-…"><a href="#What-about-RDS-Elasticsearch-DynamoDB-…" class="headerlink" title="What about RDS, Elasticsearch, DynamoDB, …?"></a>What about RDS, Elasticsearch, DynamoDB, …?</h2><p>Savings Plans for EC2 and Fargate are not the only options to reduce your AWS bill. Similar deals - you commit to an hourly consumption or even pay upfront - exists for other AWS services as well.</p><ul><li><a href="https://aws.amazon.com/rds/reserved-instances/" target="_blank" rel="noopener">Relational Database Service (RDS) Reserved Instances</a></li><li><a href="https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/aes-ri.html" target="_blank" rel="noopener">Amazon Elasticsearch Service Reserved Instances</a></li><li><a href="https://aws.amazon.com/dynamodb/pricing/provisioned/" target="_blank" rel="noopener">DynamoDB Reserved Capacity</a></li></ul><p>Unfortunately, managing most of these options at scale is complicated compared to Savings Plans.</p><h2 id="Are-there-other-options-to-reduce-costs-for-EC2"><a href="#Are-there-other-options-to-reduce-costs-for-EC2" class="headerlink" title="Are there other options to reduce costs for EC2?"></a>Are there other options to reduce costs for EC2?</h2><p>Using Savings Plans affects your billing but not your infrastructure. However, there is another pricing model for EC2: Spot Instances. A Spot Instance makes use of unused capacity in Amazon’s data centers. The spot price reflects the current demand for a particular instance type in a specific availability zone.</p><p>The following screenshot shows the spot price for an <code>m5.large</code> instance in US West (N. California). Roughly estimated, the spot price in recent months was 60% percent below the on-demand price.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/11/spot-instance-price@730w.webp 730w, /images/2019/11/spot-instance-price@730w2x.webp 1460w, /images/2019/11/spot-instance-price@610w.webp 610w, /images/2019/11/spot-instance-price@610w2x.webp 1220w, /images/2019/11/spot-instance-price@450w.webp 450w, /images/2019/11/spot-instance-price@450w2x.webp 900w, /images/2019/11/spot-instance-price@330w.webp 330w, /images/2019/11/spot-instance-price@330w2x.webp 660w, /images/2019/11/spot-instance-price@545w.webp 545w, /images/2019/11/spot-instance-price@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/11/spot-instance-price@730w.png 730w, /images/2019/11/spot-instance-price@730w2x.png 1460w, /images/2019/11/spot-instance-price@610w.png 610w, /images/2019/11/spot-instance-price@610w2x.png 1220w, /images/2019/11/spot-instance-price@450w.png 450w, /images/2019/11/spot-instance-price@450w2x.png 900w, /images/2019/11/spot-instance-price@330w.png 330w, /images/2019/11/spot-instance-price@330w2x.png 660w, /images/2019/11/spot-instance-price@545w.png 545w, /images/2019/11/spot-instance-price@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/11/spot-instance-price.png" alt="Spot Instance Price" title="Spot Instance Price"></picture></p><ol><li>You create a spot request and define the maximum price you want to pay.</li><li>AWS launches an instance when there is unused capacity available, and the spot price is below your maximum price.</li><li>AWS will terminate the instance with short notice in case the spot price exceeds your maximum price during runtime.</li></ol><p>Spot Instances are an excellent option for stateless workloads, where terminating instances is not a big deal. It is not uncommon to reduce the EC2 costs by 60% to 80% by switching from on-demand to Spot Instances. However, you need to make sure your architecture and application are ready for Spot Instances. Also, you need to plan for service availability in times of low spare capacity in AWS data centers.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>I purchased a Savings Plan for our compute consumption today. I’m quite happy to be able to reduce our AWS bill without trading in too much flexibility. Even though we plan to transition a few EC2 workloads to Fargate next year, it strikes me that Savings Plans offer more flexibility for the same discount on the on-demand price than Reserved Instances. No need to deal with Reserved Instances any more.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>ETL with a Glue Python Shell Job: Load data from S3 to Redshift</title>
      <link>https://cloudonaut.io/etl-glue-python-shell-job-load-data-from-s3-to-redshift/</link>
      <description>AWS Glue offers tools for solving ETL challenges. A Glue Python Shell job is a perfect fit for ETL tasks with low to medium complexity and data volume. For example, loading data from S3 to Redshift can be accomplished with a Glue Python Shell job immediately after someone uploads data to S3.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/redshift/">redshift</category>
      <category domain="https://cloudonaut.io/tag/glue/">glue</category>
      <guid isPermaLink="true">https://cloudonaut.io/etl-glue-python-shell-job-load-data-from-s3-to-redshift/</guid>
      <pubDate>Thu, 21 Nov 2019 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Gaining valuable insights from data is a challenge. After collecting data, the next step is to extract, transform, and load (ETL) the data into an analytics platform like Amazon Redshift. Luckily, there is a platform to build ETL pipelines: AWS Glue.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/11/pipe@730w.webp 730w, /images/2019/11/pipe@730w2x.webp 1460w, /images/2019/11/pipe@610w.webp 610w, /images/2019/11/pipe@610w2x.webp 1220w, /images/2019/11/pipe@450w.webp 450w, /images/2019/11/pipe@450w2x.webp 900w, /images/2019/11/pipe@330w.webp 330w, /images/2019/11/pipe@330w2x.webp 660w, /images/2019/11/pipe@545w.webp 545w, /images/2019/11/pipe@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/11/pipe@730w.jpg 730w, /images/2019/11/pipe@730w2x.jpg 1460w, /images/2019/11/pipe@610w.jpg 610w, /images/2019/11/pipe@610w2x.jpg 1220w, /images/2019/11/pipe@450w.jpg 450w, /images/2019/11/pipe@450w2x.jpg 900w, /images/2019/11/pipe@330w.jpg 330w, /images/2019/11/pipe@330w2x.jpg 660w, /images/2019/11/pipe@545w.jpg 545w, /images/2019/11/pipe@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/11/pipe.jpg" alt="ETL Pipeline" title="ETL Pipeline"></picture></p><p>In short, <a href="https://docs.aws.amazon.com/glue/latest/dg/what-is-glue.html" target="_blank" rel="noopener">AWS Glue</a> solves the following problems: a managed-infrastructure to run ETL jobs, a data catalog to organize data stored in data lakes, and crawlers to discover and categorize data.</p><p>In the following, I would like to present a simple but exemplary ETL pipeline to load data from S3 to Redshift.</p><ol><li>Someone uploads data to S3.</li><li>An S3 event triggers a Lambda function.</li><li>The Lambda function starts a Glue job.</li><li>The Glue job executes an SQL query to load the data from S3 to Redshift.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/11/etl-pipeline@730w.webp 730w, /images/2019/11/etl-pipeline@730w2x.webp 1460w, /images/2019/11/etl-pipeline@610w.webp 610w, /images/2019/11/etl-pipeline@610w2x.webp 1220w, /images/2019/11/etl-pipeline@450w.webp 450w, /images/2019/11/etl-pipeline@450w2x.webp 900w, /images/2019/11/etl-pipeline@330w.webp 330w, /images/2019/11/etl-pipeline@330w2x.webp 660w, /images/2019/11/etl-pipeline@545w.webp 545w, /images/2019/11/etl-pipeline@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/11/etl-pipeline@730w.png 730w, /images/2019/11/etl-pipeline@730w2x.png 1460w, /images/2019/11/etl-pipeline@610w.png 610w, /images/2019/11/etl-pipeline@610w2x.png 1220w, /images/2019/11/etl-pipeline@450w.png 450w, /images/2019/11/etl-pipeline@450w2x.png 900w, /images/2019/11/etl-pipeline@330w.png 330w, /images/2019/11/etl-pipeline@330w2x.png 660w, /images/2019/11/etl-pipeline@545w.png 545w, /images/2019/11/etl-pipeline@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/11/etl-pipeline.png" alt="ETL Pipeline to load data from S3 to Redshift" title="ETL Pipeline to load data from S3 to Redshift"></picture></p><p>AWS Glue offers two different job types:</p><ul><li>Apache Spark</li><li>Python Shell</li></ul><p>An Apache Spark job allows you to do complex ETL tasks on vast amounts of data. However, the learning curve is quite steep. Luckily, there is an alternative: Python Shell. A Python Shell job is a perfect fit for ETL tasks with low to medium complexity and data volume.</p><p>Therefore, I recommend a Glue job of type Python Shell to load data from S3 to Redshift without or with minimal transformation.</p><p>All you need to configure a Glue job is a Python script. The code example executes the following steps:</p><ol><li><code>import</code> modules that are bundled by AWS Glue by default.</li><li>Define some configuration parameters (e.g., the Redshift hostname <code>RS_HOST</code>).</li><li>Read the S3 bucket and object from the arguments (see <code>getResolvedOptions</code>) handed over when starting the job.</li><li>Establish a connection to Redshift: <code>connect(...)</code>.</li><li>Increase the statement timeout (see <code>statement_timeout</code>) to one hour.</li><li>Execute the <code>COPY</code> query to tell Redshift to the object from S3.</li></ol><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> pgdb <span class="keyword">import</span> <span class="keyword">connect</span></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">from</span> awsglue.utils <span class="keyword">import</span> getResolvedOptions</span><br><span class="line"></span><br><span class="line"># <span class="keyword">CONFIGURATION</span></span><br><span class="line">RS_HOST = &quot;xyz.redshift.amazonaws.com&quot;</span><br><span class="line">RS_PORT = &quot;5439&quot;</span><br><span class="line">RS_DATABASE = &quot;mydatabase&quot;</span><br><span class="line">RS_USER = &quot;myadmin&quot;</span><br><span class="line">RS_PASSWORD = &quot;XYZ&quot;</span><br><span class="line">RS_SCHEMA = &quot;myschema&quot;</span><br><span class="line">RS_TABLE = &quot;mytable&quot;</span><br><span class="line">RS_COLUMNS = &quot;timestamp,value_a,value_b,value_c&quot;</span><br><span class="line">DELIMITER = &quot;\t&quot;</span><br><span class="line">DATEFORMAT = &quot;YYYY-MM-DD&quot;</span><br><span class="line"></span><br><span class="line"># ARGUMENTS</span><br><span class="line">args = getResolvedOptions(sys.argv, [&quot;s3-bucket&quot;, &quot;s3-object&quot;])</span><br><span class="line">S3_BUCKET = args[&quot;s3_bucket&quot;]</span><br><span class="line">S3_OBJECT = args[&quot;s3_object&quot;]</span><br><span class="line"></span><br><span class="line">con = <span class="keyword">connect</span>(host=RS_HOST + <span class="string">&#x27;:&#x27;</span> + RS_PORT, <span class="keyword">database</span>=RS_DATABASE, <span class="keyword">user</span>=RS_USER, <span class="keyword">password</span>=RS_PASSWORD)</span><br><span class="line"><span class="keyword">cursor</span> = con.<span class="keyword">cursor</span>()</span><br><span class="line"></span><br><span class="line"><span class="keyword">cursor</span>.<span class="keyword">execute</span>(&quot;set statement_timeout = 360000&quot;)</span><br><span class="line"></span><br><span class="line">copy_query = &quot;COPY %s.%s(%s) from &#x27;s3://%s/%s&#x27; iam_role &#x27;arn:aws:iam::111111111111:role/LoadFromS3ToRedshiftJob&#x27; delimiter &#x27;%s&#x27; DATEFORMAT AS &#x27;%s&#x27; ROUNDEC TRUNCATECOLUMNS ESCAPE MAXERROR AS 500;&quot; % (</span><br><span class="line">  RS_SCHEMA, RS_TABLE, RS_COLUMNS, S3_BUCKET, S3_OBJECT, <span class="keyword">DELIMITER</span>, DATEFORMAT)</span><br><span class="line"></span><br><span class="line"><span class="keyword">cursor</span>.<span class="keyword">execute</span>(copy_query)</span><br><span class="line">con.<span class="keyword">commit</span>()</span><br><span class="line"><span class="keyword">cursor</span>.<span class="keyword">close</span>()</span><br><span class="line">con.<span class="keyword">close</span>();</span><br></pre></td></tr></table></figure><p>To trigger the ETL pipeline each time someone uploads a new object to an S3 bucket, you need to configure the following resources:</p><ol><li>Create a Lambda function (Node.js) and use the code example from below to start the Glue job <code>LoadFromS3ToRedshift</code>.</li><li>Attach an IAM role to the Lambda function, which grants access to <code>glue:StartJobRun</code>.</li><li>Create a <a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html" target="_blank" rel="noopener">S3 Event Notification</a> that invokes the Lambda function each time someone uploads an object to your S3 bucket.</li></ol><p>The following example shows how to start a Glue job and pass the S3 bucket and object as arguments.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> glue = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">Glue</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2017-03-31&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">handler</span> = <span class="title function_">async</span> (event, context) =&gt; &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Handling S3 event: <span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(event)&#125;</span>`</span>);</span><br><span class="line">  <span class="keyword">await</span> glue.<span class="title function_">startJobRun</span>(&#123;</span><br><span class="line">    <span class="title class_">JobName</span>: <span class="string">&#x27;LoadFromS3ToRedshift&#x27;</span>,</span><br><span class="line">    <span class="title class_">Arguments</span>: &#123;</span><br><span class="line">      <span class="string">&#x27;--s3-object&#x27;</span>: <span class="built_in">decodeURIComponent</span>(event.<span class="property">Records</span>[<span class="number">0</span>].<span class="property">s3</span>.<span class="property">object</span>.<span class="property">key</span>),</span><br><span class="line">      <span class="string">&#x27;--s3-bucket&#x27;</span>: event.<span class="property">Records</span>[<span class="number">0</span>].<span class="property">s3</span>.<span class="property">bucket</span>.<span class="property">name</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;).<span class="title function_">promise</span>();</span><br><span class="line">&#125;; </span><br></pre></td></tr></table></figure><p>There is only one thing left. You might want to set up monitoring for your simple ETL pipeline.</p><ol><li>Create an SNS topic and add your e-mail address as a subscriber.</li><li>Create a CloudWatch Rule with the following event pattern and configure the SNS topic as a target.</li></ol><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;detail-type&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;Glue Job State Change&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;source&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;aws.glue&quot;</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;detail&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;state&quot;</span>: [</span><br><span class="line">      <span class="string">&quot;FAILED&quot;</span>,</span><br><span class="line">      <span class="string">&quot;TIMEOUT&quot;</span></span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>By doing so, you will receive an e-mail whenever your Glue job fails.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>AWS Glue offers tools for solving ETL challenges. A Glue Python Shell job is a perfect fit for ETL tasks with low to medium complexity and data volume. For example, loading data from S3 to Redshift can be accomplished with a Glue Python Shell job immediately after someone uploads data to S3.</p><p>And by the way: the whole solution is Serverless! No need to manage any EC2 instances.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to dockerize your Python Django application for AWS Fargate?</title>
      <link>https://cloudonaut.io/how-to-dockerize-your-python-django-application-for-aws-fargate/</link>
      <description>The biggest game-changer for Docker on AWS was the announcement of AWS Fargate. Operating Docker containers could not be easier. With AWS Fargate, you launch Docker containers in the cloud without any need to manage virtual machines. Django is a popular Python web framework that encourages rapid development and clean, pragmatic design. The following post describes how you can dockerize your Python Django application and run it on AWS Fargate.</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-dockerize-your-python-django-application-for-aws-fargate/</guid>
      <pubDate>Tue, 19 Nov 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The biggest game-changer for Docker on AWS was the announcement of AWS Fargate. Operating Docker containers could not be easier. With AWS Fargate, you launch Docker containers in the cloud without any need to manage virtual machines. <a href="https://www.djangoproject.com/" target="_blank" rel="noopener">Django</a> is a popular <a href="https://www.python.org/" target="_blank" rel="noopener">Python</a> web framework that encourages rapid development and clean, pragmatic design.</p><p>The following post describes how you can dockerize your Python Django application and run it on AWS Fargate.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/11/docker-ship@730w.webp 730w, /images/2019/11/docker-ship@730w2x.webp 1460w, /images/2019/11/docker-ship@610w.webp 610w, /images/2019/11/docker-ship@610w2x.webp 1220w, /images/2019/11/docker-ship@450w.webp 450w, /images/2019/11/docker-ship@450w2x.webp 900w, /images/2019/11/docker-ship@330w.webp 330w, /images/2019/11/docker-ship@330w2x.webp 660w, /images/2019/11/docker-ship@545w.webp 545w, /images/2019/11/docker-ship@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/11/docker-ship@730w.jpg 730w, /images/2019/11/docker-ship@730w2x.jpg 1460w, /images/2019/11/docker-ship@610w.jpg 610w, /images/2019/11/docker-ship@610w2x.jpg 1220w, /images/2019/11/docker-ship@450w.jpg 450w, /images/2019/11/docker-ship@450w2x.jpg 900w, /images/2019/11/docker-ship@330w.jpg 330w, /images/2019/11/docker-ship@330w2x.jpg 660w, /images/2019/11/docker-ship@545w.jpg 545w, /images/2019/11/docker-ship@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/11/docker-ship.jpg" alt="Dockerize your Python Django application for AWS Fargate" title="Dockerize your Python Django application for AWS Fargate"></picture></p><h3 id="Building-the-Docker-images"><a href="#Building-the-Docker-images" class="headerlink" title="Building the Docker images"></a>Building the Docker images</h3><p>Two Docker images are needed:</p><ul><li>NGINX to serve static files and proxy to Django</li><li>Python Django application</li></ul><p>First, you will learn how to build NGINX image. The <code>Dockerfile</code> makes use of <a href="https://docs.docker.com/develop/develop-images/multistage-build/" target="_blank" rel="noopener">multi-stage builds</a>. You can use more than one <code>FROM</code> statement in your Dockerfile, as shown in the following example.</p><ol><li>Static assets are generated in a Python stage<ol><li>Based on the official <a href="https://hub.docker.com/_/python" target="_blank" rel="noopener">python</a> image</li><li>Using <code>pip3</code> to install the Python dependencies</li><li>Copying the app</li><li>Generating the static assets with <code>python3 manage.py collectstatic</code> (output goes to the <code>assets</code> folder)</li></ol></li><li>The static assets are copied (using <code>COPY --from=build</code>) into the NGINX stage which produces the final Docker image<ol><li>Based on the official <a href="https://hub.docker.com/_/nginx" target="_blank" rel="noopener">nginx</a> image</li><li>Copying the <code>static/</code> folder from the previous stage</li></ol></li></ol><blockquote><p><strong>Customization</strong> Most likely, your folder structure is different. Therefore, adapt the <em>Copy Python files</em> section in the following Dockerfile to your needs.</p></blockquote><figure class="highlight gradle"><figcaption><span>docker&#x2F;nginx&#x2F;Dockerfile</span></figcaption><table><tr><td class="code"><pre><span class="line"># <span class="keyword">Static</span> Assets</span><br><span class="line"><span class="keyword">FROM</span> python:<span class="number">3.7</span>.<span class="number">4</span> AS build</span><br><span class="line"></span><br><span class="line">WORKDIR <span class="regexp">/usr/</span>src/app</span><br><span class="line"></span><br><span class="line"># Install Python <span class="keyword">dependencies</span></span><br><span class="line"><span class="keyword">COPY</span> requirements.txt <span class="regexp">/usr/</span>src<span class="regexp">/app/</span></span><br><span class="line">RUN pip3 install -r requirements.txt</span><br><span class="line"></span><br><span class="line"># <span class="keyword">Copy</span> Python files</span><br><span class="line"><span class="keyword">COPY</span> example <span class="regexp">/usr/</span>src<span class="regexp">/app/</span>example # MODIFY <span class="keyword">THIS</span> LINE: YOUR FOLDERS ARE DIFFERENT</span><br><span class="line"><span class="keyword">COPY</span> rapid <span class="regexp">/usr/</span>src<span class="regexp">/app/</span>rapid # MODIFY OR REMOVE <span class="keyword">THIS</span> LINE: YOUR FOLDERS ARE DIFFERENT</span><br><span class="line"><span class="keyword">COPY</span> manage.py <span class="regexp">/usr/</span>src<span class="regexp">/app/</span></span><br><span class="line"></span><br><span class="line"># Build <span class="keyword">static</span> assets</span><br><span class="line">RUN SECRET_KEY=secret python3 manage.py collectstatic</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># NGINX</span><br><span class="line"><span class="keyword">FROM</span> nginx:<span class="number">1.14</span></span><br><span class="line"></span><br><span class="line"># Configure NGINX</span><br><span class="line"><span class="keyword">COPY</span> docker<span class="regexp">/nginx/</span><span class="keyword">default</span>.conf <span class="regexp">/etc/</span>nginx<span class="regexp">/conf.d/</span><span class="keyword">default</span>.conf</span><br><span class="line"></span><br><span class="line"># <span class="keyword">Copy</span> <span class="keyword">static</span> files</span><br><span class="line"><span class="keyword">COPY</span> --<span class="keyword">from</span>=build <span class="regexp">/usr/</span>src<span class="regexp">/app/</span>build<span class="regexp">/ /</span>var<span class="regexp">/www/</span>html/<span class="keyword">static</span></span><br><span class="line">RUN chown -R nginx:nginx <span class="regexp">/var/</span>www/html</span><br></pre></td></tr></table></figure><p>The NGINX configuration file forwards requests to the Python container if the path does not start with <code>/static/</code>.</p><figure class="highlight nginx"><figcaption><span>docker&#x2F;nginx&#x2F;default.conf</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="section">server</span> &#123;</span><br><span class="line">    <span class="attribute">listen</span>       <span class="number">80</span>;</span><br><span class="line">    <span class="attribute">server_name</span>  localhost;</span><br><span class="line">    <span class="attribute">root</span>         /var/www/html;</span><br><span class="line"></span><br><span class="line">    <span class="section">location</span> <span class="regexp">~ ^/static/</span> &#123;</span><br><span class="line">      <span class="comment"># serve from NGINX</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="section">location</span> / &#123;</span><br><span class="line">      <span class="comment"># pass to Python gunicorn based on</span></span><br><span class="line">      <span class="comment"># http://docs.gunicorn.org/en/stable/deploy.html</span></span><br><span class="line">      <span class="attribute">proxy_set_header</span> X-Forwarded-For <span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line">      <span class="attribute">proxy_set_header</span> X-Forwarded-Proto <span class="variable">$scheme</span>;</span><br><span class="line">      <span class="attribute">proxy_set_header</span> Host <span class="variable">$http_host</span>;</span><br><span class="line">      <span class="comment"># we don&#x27;t want nginx trying to do something clever with</span></span><br><span class="line">      <span class="comment"># redirects, we set the Host: header above already.</span></span><br><span class="line">      <span class="attribute">proxy_redirect</span> <span class="literal">off</span>;</span><br><span class="line">      <span class="attribute">proxy_pass</span> http://127.0.0.1:8000;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Next, you will learn how to create the Django image. The <code>Dockerfile</code> is based on the official <a href="https://hub.docker.com/_/python" target="_blank" rel="noopener">python</a> image with the following additions:</p><ul><li><a href="https://github.com/vishnubob/wait-for-it" target="_blank" rel="noopener">wait-for-it</a> is installed to wait for the MySQL database container if you test locally.</li><li>Python dependencies are installed with <code>pip3 install</code>.</li><li>A <a href="https://docs.docker.com/engine/reference/run/#entrypoint-default-command-to-execute-at-runtime" target="_blank" rel="noopener">custom etrypoint</a> is defined to run commands before the Django app starts (read on to learn more).</li><li><a href="https://gunicorn.org/" target="_blank" rel="noopener">gunicorn</a> runs the app.</li></ul><blockquote><p><strong>Customization</strong> Most likely, your folder structure is different. Therefore, adapt the <em>Copy Python files</em> section in the following Dockerfile to your needs.</p></blockquote><figure class="highlight dockerfile"><figcaption><span>docker&#x2F;python&#x2F;Dockerfile</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">FROM</span> python:<span class="number">3.7</span>.<span class="number">4</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /usr/src/app</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Install wait-for-it</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> docker/wait-for-it.sh /usr/local/bin/</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">chmod</span> u+x /usr/local/bin/wait-for-it.sh</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Install Python dependencies</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> requirements.txt /usr/src/app/</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> pip3 install -r requirements.txt</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Copy Python files</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> example /usr/src/app/example <span class="comment"># MODIFY THIS LINE: YOUR FOLDERS ARE DIFFERENT</span></span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> rapid /usr/src/app/rapid <span class="comment"># MODIFY THIS LINE: YOUR FOLDERS ARE DIFFERENT</span></span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> manage.py /usr/src/app/</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Configure custom entrypoint to run migrations</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> docker/python/custom-entrypoint /usr/local/bin/</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">chmod</span> u+x /usr/local/bin/custom-entrypoint</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;custom-entrypoint&quot;</span>]</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Expose port 8000 and start Python server</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">8000</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;gunicorn&quot;</span>, <span class="string">&quot;-b&quot;</span>, <span class="string">&quot;0.0.0.0&quot;</span>, <span class="string">&quot;-w&quot;</span>, <span class="string">&quot;2&quot;</span>, <span class="string">&quot;rapid.wsgi&quot;</span>]</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>Customization</strong> The <code>-w</code> parameter of gunicorn defines the number of <a href="https://gunicorn.org/reference/settings/#worker-processes" target="_blank" rel="noopener">workers</a> and should be in the range of <code>2-4 x $(NUM_CORES)</code>.</p></blockquote><p>The custom entrypoint is used to:</p><ul><li>Wait for the MySQL container if the <code>WAIT_FOR_IT</code> environment variable is set (used for testing locally only).</li><li>Run the database migrations before the Django app is started.</li></ul><figure class="highlight bash"><figcaption><span>docker&#x2F;python&#x2F;custom-entrypoint</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ -n <span class="string">&quot;<span class="variable">$&#123;WAIT_FOR_IT&#125;</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">  wait-for-it.sh mysql:3306</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;running migrations&quot;</span></span><br><span class="line">python3 manage.py migrate</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;starting <span class="variable">$@</span>&quot;</span></span><br><span class="line"><span class="built_in">exec</span> <span class="string">&quot;<span class="variable">$@</span>&quot;</span></span><br></pre></td></tr></table></figure><p>That’s it. You have everything you need to build both, the NGINX as well as the Django image. Next, you will learn how to test your containers and application locally.</p><h3 id="Testing-locally"><a href="#Testing-locally" class="headerlink" title="Testing locally"></a>Testing locally</h3><p>Use <a href="https://docs.docker.com/compose/" target="_blank" rel="noopener">Docker Compose</a> to run your application locally. The following <code>docker-compose.yml</code> file configures Docker Compose and starts three containers: NGINX, Django as well as a MySQL database.</p><figure class="highlight yaml"><figcaption><span>docker&#x2F;docker-compose.yml</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&#x27;3&#x27;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">nginx:</span></span><br><span class="line">    <span class="attr">build:</span></span><br><span class="line">      <span class="attr">context:</span> <span class="string">&#x27;..&#x27;</span></span><br><span class="line">      <span class="attr">dockerfile:</span> <span class="string">&#x27;docker/nginx/Dockerfile&#x27;</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">python</span></span><br><span class="line">    <span class="attr">network_mode:</span> <span class="string">&#x27;service:python&#x27;</span> <span class="comment"># use network interface of python container to simulate awsvpc network mode</span></span><br><span class="line">  <span class="attr">python:</span></span><br><span class="line">    <span class="attr">build:</span></span><br><span class="line">      <span class="attr">context:</span> <span class="string">&#x27;..&#x27;</span></span><br><span class="line">      <span class="attr">dockerfile:</span> <span class="string">&#x27;docker/python/Dockerfile&#x27;</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;8080:80&#x27;</span> <span class="comment"># forwards port of nginx container</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">mysql</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">DATABASE_HOST:</span> <span class="string">mysql</span></span><br><span class="line">      <span class="attr">DATABASE_NAME:</span> <span class="string">app</span></span><br><span class="line">      <span class="attr">DATABASE_USER:</span> <span class="string">app</span></span><br><span class="line">      <span class="attr">DATABASE_PASSWORD:</span> <span class="string">secret</span></span><br><span class="line">      <span class="attr">SECRET_KEY:</span> <span class="string">secret</span></span><br><span class="line">      <span class="attr">WAIT_FOR_IT:</span> <span class="string">&#x27;true&#x27;</span></span><br><span class="line">  <span class="attr">mysql:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">&#x27;mysql:5.6&#x27;</span></span><br><span class="line">    <span class="attr">command:</span> <span class="string">&#x27;--default-authentication-plugin=mysql_native_password&#x27;</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;3306:3306&#x27;</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">MYSQL_ROOT_PASSWORD:</span> <span class="string">secret</span></span><br><span class="line">      <span class="attr">MYSQL_DATABASE:</span> <span class="string">app</span></span><br><span class="line">      <span class="attr">MYSQL_USER:</span> <span class="string">app</span></span><br><span class="line">      <span class="attr">MYSQL_PASSWORD:</span> <span class="string">secret</span></span><br></pre></td></tr></table></figure><p>The following command starts the application:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker-compose -f docker/docker-compose.yml up --build</span><br></pre></td></tr></table></figure><p>Magically, Docker Compose will spin up three containers: NGINX, Django, and MySQL. Point your browser to <a href="http://localhost:8080/" target="_blank" rel="noopener">http://localhost:8080</a> to check that your web application is up and running. The log files of all containers will show up in your terminal, which simplifies debugging a lot.</p><p>After you have verified that your application is working correctly, cancel the running <code>docker-compose</code> process by pressing <code>CTRL + C</code>, and tear down the containers:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker-compose -f docker/docker-compose.yml down</span><br></pre></td></tr></table></figure><h3 id="Deploying-on-AWS"><a href="#Deploying-on-AWS" class="headerlink" title="Deploying on AWS"></a>Deploying on AWS</h3><p>You are now ready to deploy your application on AWS.</p><p>(1) Build Docker images:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker build -t python-django-nginx:latest \</span><br><span class="line">  -f docker/nginx/Dockerfile .</span><br><span class="line">docker build -t python-django-python:latest \</span><br><span class="line">  -f docker/python/Dockerfile .</span><br></pre></td></tr></table></figure><p>(2) Create ECR repositories:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws ecr create-repository --repository-name python-django-nginx \</span><br><span class="line">  --query <span class="string">&#x27;repository.repositoryUri&#x27;</span> --output text</span><br><span class="line">aws ecr create-repository --repository-name python-django-python \</span><br><span class="line">  --query <span class="string">&#x27;repository.repositoryUri&#x27;</span> --output text</span><br></pre></td></tr></table></figure><p>(3) Login to Docker registry (ECR):</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$(aws ecr get-login --no-include-email)</span><br></pre></td></tr></table></figure><p>(4) Tag Docker images:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker tag python-django-nginx:latest \</span><br><span class="line">111111111111.dkr.ecr.eu-west-1.amazonaws.com/\</span><br><span class="line">python-django-nginx:latest</span><br><span class="line">docker tag python-django-python:latest \</span><br><span class="line">111111111111.dkr.ecr.eu-west-1.amazonaws.com/\</span><br><span class="line">python-django-python:latest</span><br></pre></td></tr></table></figure><p>(5) Push Docker images:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker push \</span><br><span class="line">111111111111.dkr.ecr.eu-west-1.amazonaws.com/\</span><br><span class="line">python-django-nginx:latest</span><br><span class="line">docker push \</span><br><span class="line">111111111111.dkr.ecr.eu-west-1.amazonaws.com/\</span><br><span class="line">python-django-python:latest</span><br></pre></td></tr></table></figure><p>There is only one step missing: you need to spin up the cloud infrastructure.</p><ol><li>Use our <a href="https://templates.cloudonaut.io/en/stable/fargate/#using-the-clusters-load-balancer-and-path-andor-host-based-routing" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a>.</li><li>Use our <a href="https://github.com/cfn-modules/docs/tree/master/examples/fargate-alb-proxy-pattern" target="_blank" rel="noopener">cfn-modules</a>.</li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>re:Invent Roulette</title>
      <link>https://cloudonaut.io/aws-reinvent-roulette-2019/</link>
      <description>
        <![CDATA[<p>Going to AWS re:Invent is an excellent opportunity to discover the latest services, to deepen your knowledge, and to meet interesting peo]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/reinvent/">reinvent</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-reinvent-roulette-2019/</guid>
      <pubDate>Thu, 14 Nov 2019 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Going to AWS re:Invent is an excellent opportunity to discover the latest services, to deepen your knowledge, and to meet interesting people from all over the world.</p><p>Our goal is to connect you with an open-minded professional. With re:Invent Roulette, we are connecting you with another attendee based on your favorite topic (e.g., DevOps, Serverless, Big Data, AI, …).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/11/roulette@730w.webp 730w, /images/2019/11/roulette@730w2x.webp 1460w, /images/2019/11/roulette@610w.webp 610w, /images/2019/11/roulette@610w2x.webp 1220w, /images/2019/11/roulette@450w.webp 450w, /images/2019/11/roulette@450w2x.webp 900w, /images/2019/11/roulette@330w.webp 330w, /images/2019/11/roulette@330w2x.webp 660w, /images/2019/11/roulette@545w.webp 545w, /images/2019/11/roulette@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/11/roulette@730w.jpg 730w, /images/2019/11/roulette@730w2x.jpg 1460w, /images/2019/11/roulette@610w.jpg 610w, /images/2019/11/roulette@610w2x.jpg 1220w, /images/2019/11/roulette@450w.jpg 450w, /images/2019/11/roulette@450w2x.jpg 900w, /images/2019/11/roulette@330w.jpg 330w, /images/2019/11/roulette@330w2x.jpg 660w, /images/2019/11/roulette@545w.jpg 545w, /images/2019/11/roulette@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/11/roulette.jpg" alt="re:Invent Roulette" title="re:Invent Roulette"></picture></p><p>We have invented the re:Invent Roulette for outgoing as well as introvert people.</p><h2 id="How-does-it-work"><a href="#How-does-it-work" class="headerlink" title="How does it work?"></a>How does it work?</h2><p>It’s easy to get involved.</p><ol><li>You fill out the <a href="https://forms.gle/v77rrRChLJt3exJRA" target="_blank" rel="noopener">re:Invent Roulette</a> application form.</li><li>Based on your information, we will match you with another professional who applied for re:Invent Roulette as well.</li><li>We will introduce both of you via e-mail.</li><li>You meet with an open-minded professional at re:Invent.</li></ol><h2 id="What-to-talk-about"><a href="#What-to-talk-about" class="headerlink" title="What to talk about?"></a>What to talk about?</h2><p>A few ideas to talk about.</p><ul><li>Discuss the announcements from the keynotes.</li><li>Ask about the projects your opposite is working on.</li><li>Share a success story or even better a post mortem of an incident.</li><li>Ask for the other one’s favorite tools.</li></ul><p>That’s it. Remember to fill out the <a href="https://forms.gle/v77rrRChLJt3exJRA" target="_blank" rel="noopener">re:Invent Roulette</a> application form. And, please share!</p><hr><p>By the way, do you want to meet me at re:Invent? Just send me an e-mail: <a href="mailto:&#109;&#105;&#x63;&#104;&#x61;&#x65;&#x6c;&#x40;&#119;&#105;&#x64;&#100;&#105;&#120;&#x2e;&#x64;&#101;">michael@widdix.de</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Review: AWS Global Accelerator - Improving Latency and Design for Failure</title>
      <link>https://cloudonaut.io/review-aws-global-accelerator-latency-multi-region-disaster-recovery/</link>
      <description>
        <![CDATA[<p>The Cloud is all about networking. AWS introduced Global Accelerator at re:Invent in 2018. A year after that, it is about time to review]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/review/">Review</category>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/review/">review</category>
      <guid isPermaLink="true">https://cloudonaut.io/review-aws-global-accelerator-latency-multi-region-disaster-recovery/</guid>
      <pubDate>Tue, 12 Nov 2019 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The Cloud is all about networking. AWS introduced Global Accelerator at re:Invent in 2018. A year after that, it is about time to review the service. AWS Global Accelerator makes use of Amazon’s worldwide infrastructure and is designed to improve the performance and reliability of your applications.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/11/network@730w.webp 730w, /images/2019/11/network@730w2x.webp 1460w, /images/2019/11/network@610w.webp 610w, /images/2019/11/network@610w2x.webp 1220w, /images/2019/11/network@450w.webp 450w, /images/2019/11/network@450w2x.webp 900w, /images/2019/11/network@330w.webp 330w, /images/2019/11/network@330w2x.webp 660w, /images/2019/11/network@545w.webp 545w, /images/2019/11/network@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/11/network@730w.jpg 730w, /images/2019/11/network@730w2x.jpg 1460w, /images/2019/11/network@610w.jpg 610w, /images/2019/11/network@610w2x.jpg 1220w, /images/2019/11/network@450w.jpg 450w, /images/2019/11/network@450w2x.jpg 900w, /images/2019/11/network@330w.jpg 330w, /images/2019/11/network@330w2x.jpg 660w, /images/2019/11/network@545w.jpg 545w, /images/2019/11/network@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/11/network.jpg" alt="Review: AWS Global Accelerator" title="Review: AWS Global Accelerator"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/8-review-aws-global-accelerator/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>Let the review begin.</p><h2 id="How-It-Works"><a href="#How-It-Works" class="headerlink" title="How It Works"></a>How It Works</h2><p>The Global Accelerator provides two static <a href="https://en.wikipedia.org/wiki/Anycast" target="_blank" rel="noopener">Anycast</a> IPv4 addresses. All you need to do is to define endpoints in one or multiple regions. The following endpoints are supported:</p><ul><li>Internet-facing Application Load Balancer (ALB)</li><li>Internal Application Load Balancer (ALB)</li><li>Internet-facing Network Load Balancer (NLB)</li><li>Elastic IP</li><li>EC2 Instance (with or without Public IP)</li></ul><p>The Global Accelerator optimizes the route for each client, which means it minimizes the number of hops until a TPC or UDP package enters Amazon’s network and therefore reduces latencies. Also, Global Accelerator route requests to healthy endpoints only.</p><blockquote><p>Global Accelerator creates a peering connection between your accelerator and a VPC that you created with Amazon Virtual Private Cloud (Amazon VPC). The traffic between Global Accelerator and your VPC uses private IP addresses.<sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup></p></blockquote><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/11/global-accelerator@730w.webp 730w, /images/2019/11/global-accelerator@730w2x.webp 1460w, /images/2019/11/global-accelerator@610w.webp 610w, /images/2019/11/global-accelerator@610w2x.webp 1220w, /images/2019/11/global-accelerator@450w.webp 450w, /images/2019/11/global-accelerator@450w2x.webp 900w, /images/2019/11/global-accelerator@330w.webp 330w, /images/2019/11/global-accelerator@330w2x.webp 660w, /images/2019/11/global-accelerator@545w.webp 545w, /images/2019/11/global-accelerator@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/11/global-accelerator@730w.png 730w, /images/2019/11/global-accelerator@730w2x.png 1460w, /images/2019/11/global-accelerator@610w.png 610w, /images/2019/11/global-accelerator@610w2x.png 1220w, /images/2019/11/global-accelerator@450w.png 450w, /images/2019/11/global-accelerator@450w2x.png 900w, /images/2019/11/global-accelerator@330w.png 330w, /images/2019/11/global-accelerator@330w2x.png 660w, /images/2019/11/global-accelerator@545w.png 545w, /images/2019/11/global-accelerator@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/11/global-accelerator.png" alt="Global Accelerator" title="Global Accelerator"></picture></p><p>The Global Accelerator uses two public IP addresses for enhanced fault tolerance. There is also a DNS name available pointing to both public IP addresses (e.g., <code>af56c15e2cd32aa08.awsglobalaccelerator.com </code>).</p><p>To make use of Global Accelerator, you need to:</p><ol><li>Create an accelerator which provisions two static Anycast IP addresses.</li><li>Create a listener for the protocol and port or port range.</li><li>Create an endpoint group for every region you want to route traffic to.</li><li>Add an endpoint (e.g., an ALB) to each endpoint group.</li></ol><p>On top of that, Global Accelerator supports shifting traffic between regions and endpoints gradually. A convenient feature to move your workload because of deployments or outages.</p><p>Want to reduce the traffic of a specific region? Use traffic dials to decrease traffic. For example, by reducing the traffic dial from 100% to 50%, half of the traffic will be routed to another region instead. When using multiple endpoints within a region, use the endpoint weight to adjust the proportion of traffic for each endpoint.</p><p>In case of a failure, re-routing traffic to other endpoints, availability zones, or regions happens within 30 seconds after the health check detects a failed endpoint.</p><h2 id="Use-Cases"><a href="#Use-Cases" class="headerlink" title="Use Cases"></a>Use Cases</h2><p>Common scenarios for using the AWS Global Accelerator:</p><ul><li>Multi-Region: deploy your infrastructure to multiple regions and route traffic to the region that is closest to the client.</li><li>Disaster Recovery: shift traffic from a region or endpoint affected by an outage to another endpoint. The Anycast IP addresses make sure all clients send requests to healthy endpoints only immediately.<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup> Doing so is a challenge when using routing mechanisms based on Route 53 (DNS) due to caching issues.</li><li>Static IP Address: some clients (e.g., IoT devices) might not be able to resolve names via DNS, or static IP addresses might be needed for a firewall rule (NLB is an alternative in that scenario).</li><li>Low Latency: all scenarios, where latency is critical (e.g., trading, gaming, …).</li><li>Blue-Green or Canary Deployment: shift traffic between infrastructures during deployments or for testing purposes.</li><li>Origin Cloaking: use the Global Accelerator as public and protected (see <a href="https://aws.amazon.com/shield/" target="_blank" rel="noopener">AWS Shield</a>) endpoint for an ALB or EC2 running in a private subnet.</li></ul><p>Read on for a comparison of Global Accelerator and CloudFront.</p><h2 id="Latency-Benchmark"><a href="#Latency-Benchmark" class="headerlink" title="Latency Benchmark"></a>Latency Benchmark</h2><p>Reducing the latency for clients connecting to your endpoints worldwide is the unique selling point of Global Accelerator. But what kind of latency improvements can you expect? I run a latency benchmark on <a href="https://perfops.net/" target="_blank" rel="noopener">PerfOps</a>. Special thanks to Dmitriy, CEO of PerfOps, for providing free credits to run the latency benchmark.</p><blockquote><p>Please note, AWS announced <a href="https://aws.amazon.com/about-aws/whats-new/2020/03/aws-global-accelerator-launches-tcp-termination-at-the-edge/" target="_blank" rel="noopener">TCP Termination at the Edge</a> on March 23, 2020. The following latency benchmark is therefore outdated. That’s especially important for the comparison between Global Accelerator and CloudFront. Hopefully I will find time to update the latency benchmark soon.</p></blockquote><ul><li>The benchmark was executed by 247 nodes distributed worldwide. </li><li>The nodes are simulating traffic from different parts of the world. Results from real-world clients with consumer standard Internet connectivity might differ.</li><li>The benchmark consists of 10,000 measurements per endpoint.</li><li>An Application Load Balancer (ALB) in <code>eu-west-1</code> was used as the only endpoint.</li><li>The ALB responds with a static response without forwarding the request to a target.</li><li>The benchmark measures the time-to-first-byte (TTFB).</li></ul><p>In short, Global Accelerator delivers what it promises. The network latency is specified in the 95th percentile.</p><table class="table table-striped table-responsive"><thead><tr><th>Endpoint</th><th style="text-align:right">Network Latency</th></tr></thead><tbody><tr><td>Global Accelerator</td><td style="text-align:right">361 ms</td></tr><tr><td>Application Load Balancer</td><td style="text-align:right">543 ms</td></tr></tbody></table><p>That’s a latency reduction by 33%.</p><p>The following table illustrates the latency reduction that you can achieve from different continents when fronting your ALB with a Global Accelerator. Again, we are having a look at the 95th percentile.</p><table class="table table-striped table-responsive"><thead><tr><th>Continent</th><th style="text-align:right">Global Accelerator</th><th style="text-align:right">ALB</th><th style="text-align:right">Latency Reduction</th></tr></thead><tbody><tr><td>Africa</td><td style="text-align:right">369 ms</td><td style="text-align:right">497 ms</td><td style="text-align:right">26%</td></tr><tr><td>Asia</td><td style="text-align:right">401 ms</td><td style="text-align:right">580 ms</td><td style="text-align:right">31%</td></tr><tr><td>Europe</td><td style="text-align:right">146 ms</td><td style="text-align:right">154 ms</td><td style="text-align:right">5%<code>*</code></td></tr><tr><td>North America</td><td style="text-align:right">188  ms</td><td style="text-align:right">312 ms</td><td style="text-align:right">40%</td></tr><tr><td>Oceania</td><td style="text-align:right">377 ms</td><td style="text-align:right">702 ms</td><td style="text-align:right">46%</td></tr><tr><td>South America</td><td style="text-align:right">376 ms</td><td style="text-align:right">498 ms</td><td style="text-align:right">24%</td></tr></tbody></table><p><code>*</code>: Note that the ALB is running in <code>eu-west-1</code>. Therefore, it is not surprising that there is no significant difference between the latency of Global Accelerator and the ALB.</p><p>Note that latencies can be further improved by adding more regional endpoints (e.g., additional load balancers in North America and Asia). Of course, that also requires you to duplicate your infrastructure and data, which is an entirely different story.</p><h2 id="Pricing"><a href="#Pricing" class="headerlink" title="Pricing"></a>Pricing</h2><p>It is complicated to estimate the costs for adding Global Accelerator to your infrastructure in advance.</p><p>First of all, each public IP address pair (aka. accelerator) provided by Global Accelerator costs USD 18 per month.</p><p>On top of that, you pay an additional fee for the traffic. Roughly estimated, the premium is 20% on your traffic. It might surprise you that you not only pay for outgoing traffic but also for incoming traffic. Or to be more precise, you are paying for the dominant traffic direction (either incoming or outgoing traffic).</p><p>So let us assume, your infrastructure leverages a Global Accelerator as well as an Application Load Balancer in <code>eu-west-1</code>. Your workload results in 1,000 GB of traffic per month. 700 GB of that traffic is outbound. So outbound is your dominant direction, and you pay for those 700 GB, but not for the other 300 GB inbound. 50% of the traffic is transferred to the United States, 25% to Europe, and 25% to the Middle East.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th style="text-align:right">USD per GB</th><th style="text-align:right">Traffic</th><th style="text-align:right">Total Price</th></tr></thead><tbody><tr><td>EC2 Data Transfer Out</td><td style="text-align:right">0.09</td><td style="text-align:right">700 GB</td><td style="text-align:right">USD 63.00</td></tr><tr><td>Global Accelerator (from EU to US)</td><td style="text-align:right">0.015</td><td style="text-align:right">350 GB</td><td style="text-align:right">USD 5.25</td></tr><tr><td>Global Accelerator (from EU to EU)</td><td style="text-align:right">0.015</td><td style="text-align:right">175 GB</td><td style="text-align:right">USD 2.63</td></tr><tr><td>Global Accelerator (from EU to ME)</td><td style="text-align:right">0.035</td><td style="text-align:right">175 GB</td><td style="text-align:right">USD 6.13</td></tr></tbody></table><p>For more details, visit the <a href="https://aws.amazon.com/global-accelerator/pricing/" target="_blank" rel="noopener">official pricing page</a>.</p><h2 id="CloudFront-vs-Global-Accelerator"><a href="#CloudFront-vs-Global-Accelerator" class="headerlink" title="CloudFront vs. Global Accelerator"></a>CloudFront vs. Global Accelerator</h2><p>When optimizing for low latency and response times, CloudFront - the Content Delivery Network (CDN) - is an obvious choice. Both services optimize the route of a request from clients all over the world to your endpoints. However, CloudFront can process a request and cache a response from 200 locations distributed worldwide. The Global Accelerator routes the packages to one of your endpoints (one or multiple endpoints optionally distributed among regions).</p><p>There is another fundamental difference between CloudFront and Global Accelerator: CloudFront caches responses from your endpoints. At best, CloudFront can answer an incoming request from an edge location near to the client without forwarding a request to your endpoint. Depending on your workload, the majority of requests are cacheable, which reduces the response times and latencies enormously.</p><p>In summary, the result of the latency benchmark with the same setup, as described above, is not a surprise. The Global Accelerator reduces the latency to the ALB. But still, the package is routed from each continent to the ALB in <code>eu-west-1</code>. CloudFront, on the other hand, was able to cache the responses at the edge locations.</p><table class="table table-striped table-responsive"><thead><tr><th>Continent</th><th style="text-align:right">Global Accelerator</th><th style="text-align:right">CloudFront (✅ cache)</th></tr></thead><tbody><tr><td>Africa</td><td style="text-align:right">369 ms</td><td style="text-align:right">51 ms</td></tr><tr><td>Asia</td><td style="text-align:right">401 ms</td><td style="text-align:right">179 ms</td></tr><tr><td>Europe</td><td style="text-align:right">146 ms</td><td style="text-align:right">107 ms</td></tr><tr><td>North America</td><td style="text-align:right">187  ms</td><td style="text-align:right">78 ms</td></tr><tr><td>Oceania</td><td style="text-align:right">377 ms</td><td style="text-align:right">71 ms</td></tr><tr><td>South America</td><td style="text-align:right">376 ms</td><td style="text-align:right">29 ms<code>*</code></td></tr></tbody></table><p><code>*</code>: Please note that for some continents, the benchmark is running on less than ten different nodes. Some of the nodes might even be located in or near an AWS data center.</p><p>When caching a response is not an option, CloudFront has to forward each request to the ALB. CloudFront needs to process each request that results in a higher latency compared to using Global Accelerator, which routes the packages directly to the ALB immediately.</p><table class="table table-striped table-responsive"><thead><tr><th>Continent</th><th style="text-align:right">Global Accelerator</th><th style="text-align:right">CloudFront (✅ cache)</th><th style="text-align:right">CloudFront (❌ cache)</th></tr></thead><tbody><tr><td>Africa</td><td style="text-align:right">369 ms</td><td style="text-align:right">51 ms</td><td style="text-align:right">387 ms</td></tr><tr><td>Asia</td><td style="text-align:right">401 ms</td><td style="text-align:right">179 ms</td><td style="text-align:right">661 ms</td></tr><tr><td>Europe</td><td style="text-align:right">146 ms</td><td style="text-align:right">107 ms</td><td style="text-align:right">169 ms</td></tr><tr><td>North America</td><td style="text-align:right">187  ms</td><td style="text-align:right">78 ms</td><td style="text-align:right">374 ms</td></tr><tr><td>Oceania</td><td style="text-align:right">377 ms</td><td style="text-align:right">71 ms</td><td style="text-align:right">1008 ms<code>*</code></td></tr><tr><td>South America</td><td style="text-align:right">376 ms</td><td style="text-align:right">29 ms</td><td style="text-align:right">530 ms</td></tr></tbody></table><p><code>*</code>: Please note that for some continents, the benchmark is running on less than ten different nodes. Some of the nodes might even be located in or near an AWS data center.</p><p>What do these benchmark results mean in practice?</p><p><strong>When to use CloudFront?</strong></p><ul><li>The protocol is HTTP&#x2F;HTTPS.</li><li>The majority of responses can be cached.</li><li>Processing decrypted data outside the EU (GDPR)&#x2F;another territory is not an issue.</li></ul><p><strong>When to use Global Accelerator?</strong></p><ul><li>When using CloudFront is not an option (see above).</li><li>For all non-HTTP workloads based on TCP or UDP. For example, Voice-over-IP, DNS, MQTT, FTP, …</li><li>Fast and reliable multi-region disaster recovery is required (does not rely on DNS).</li><li>When you don’t want to make your endpoint publicly available (with the exception of S3).</li></ul><h2 id="Private-Subnet"><a href="#Private-Subnet" class="headerlink" title="Private Subnet"></a>Private Subnet</h2><p>There is no way to reach an instance placed into a private subnet from the Internet. Agree?</p><p>That is no longer the case. With Global Accelerator, you can add a public endpoint to any instance running in a private subnet. The only requirement is an Internet Gateway attached to the VPC.</p><p>Keep that in mind, when writing IAM policies to deny creating publicly available resources or when configuring compliance checks.</p><h2 id="Missing-Features"><a href="#Missing-Features" class="headerlink" title="Missing Features"></a>Missing Features</h2><p>I can’t understand why CloudFormation still doesn’t have support for Global Accelerator. However, the <a href="https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/255" target="_blank" rel="noopener">missing resources</a> have been added to the public road map recently. As usual, Terraform already supports the newish service.</p><p>As discussed at the beginning, deploying applications and infrastructures seamlessly is a use case for Global Accelerator. However, there are no integrations with developer tools and management tools, yet. For example, CodePipeline or CodeDeploy should offer ways to do a blue-green or canary deployment with the help of Global Accelerator.</p><h2 id="Limits"><a href="#Limits" class="headerlink" title="Limits"></a>Limits</h2><p>The following service limits are documented:<sup><a href="#fn:3" id="fnref:3" class="footnote-ref">3</a></sup></p><ul><li>20 accelerators (2x 20 static Anycast IP addresses) per AWS account </li><li>10 listeners per accelerator</li><li>10 port ranges per listener</li><li>10 endpoints per endpoint group</li></ul><p>Also, I stumbled upon the following limitation in the documentation: the idle timeout is 90 seconds for TCP connections and 30 seconds for UDP connections.<sup><a href="#fn:4" id="fnref:4" class="footnote-ref">4</a></sup></p><p>It is worth noting that the AWS Global Accelerator does not support IPv6 yet.</p><h2 id="Service-Maturity-Table"><a href="#Service-Maturity-Table" class="headerlink" title="Service Maturity Table"></a>Service Maturity Table</h2><p>The following table summarizes the maturity of the service:</p><table class="table table-striped table-responsive"><thead><tr><th>Criteria</th><th>Support</th><th style="text-align:right">Score</th></tr></thead><tbody><tr><td>Feature Completeness</td><td>⚠️</td><td style="text-align:right">6</td></tr><tr><td>Documentation Detailedness</td><td>⚠️</td><td style="text-align:right">6</td></tr><tr><td>Tags (Grouping + Billing)</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>CloudFormation + Terraform support</td><td>❌</td><td style="text-align:right">5</td></tr><tr><td>Emits CloudWatch Events</td><td>❌</td><td style="text-align:right">0</td></tr><tr><td>IAM granularity</td><td>✅<sup><a href="#fn:5" id="fnref:5" class="footnote-ref">5</a></sup></td><td style="text-align:right">10</td></tr><tr><td>Integrated with AWS Config</td><td>❌<sup><a href="#fn:6" id="fnref:6" class="footnote-ref">6</a></sup></td><td style="text-align:right">0</td></tr><tr><td>Auditing via AWS CloudTrail</td><td>✅</td><td style="text-align:right">10</td></tr><tr><td>Available in all commercial regions</td><td>⚠️<sup><a href="#fn:7" id="fnref:7" class="footnote-ref">7</a></sup></td><td style="text-align:right">7</td></tr><tr><td>SLA</td><td>✅<sup><a href="#fn:8" id="fnref:8" class="footnote-ref">8</a></sup></td><td style="text-align:right">10</td></tr><tr><td><strong>Total Maturity Score (0-10)</strong></td><td>⚠️</td><td style="text-align:right"><strong>6.4</strong></td></tr></tbody></table><p>Our maturity score for AWS Global Accelerator is 6.4 on a scale from 0 to 10.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>By optimizing the route from the clients distributed worldwide to your endpoints, the AWS Global Accelerator optimizes the network latency significantly. On top of that, a multi-region infrastructure benefits from the routing possibilities provided by AWS Global Accelerator. The service maturity is good for a 1-year-old service. As described above, some features are missing. I haven’t used Global Accelerator in any project yet, because CloudFront was doing the trick so far.</p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. https://medium.com/@adhorn/multi-region-serverless-backend-reloaded-1b887bc615c0 <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. https://docs.aws.amazon.com/global-accelerator/latest/dg/introduction-benefits-of-migrating.html <a href="#fnref:2" class="footnote-backref">↩</a></p></li><li id="fn:3"><p>3. https://docs.aws.amazon.com/global-accelerator/latest/dg/limits-global-accelerator.html <a href="#fnref:3" class="footnote-backref">↩</a></p></li><li id="fn:4"><p>4. https://docs.aws.amazon.com/global-accelerator/latest/dg/introduction-how-it-works.html <a href="#fnref:4" class="footnote-backref">↩</a></p></li><li id="fn:5"><p>5. https://docs.aws.amazon.com/IAM/latest/UserGuide/list_awsglobalaccelerator.html <a href="#fnref:5" class="footnote-backref">↩</a></p></li><li id="fn:6"><p>6. https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html <a href="#fnref:6" class="footnote-backref">↩</a></p></li><li id="fn:7"><p>7. https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/ <a href="#fnref:7" class="footnote-backref">↩</a></p></li><li id="fn:8"><p>8. https://aws.amazon.com/global-accelerator/sla/ <a href="#fnref:8" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Resilient task scheduling with ECS Fargate</title>
      <link>https://cloudonaut.io/resilient-task-scheduling-with-ecs-fargate-cron-scheduled-task/</link>
      <description>
        <![CDATA[<p>Many applications use scheduled jobs to automate recurring tasks, such as:</p>
<ul>
<li>Generating and sending a monthly report.</li>
<li]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <category domain="https://cloudonaut.io/tag/highlight/">highlight</category>
      <guid isPermaLink="true">https://cloudonaut.io/resilient-task-scheduling-with-ecs-fargate-cron-scheduled-task/</guid>
      <pubDate>Tue, 05 Nov 2019 10:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Many applications use scheduled jobs to automate recurring tasks, such as:</p><ul><li>Generating and sending a monthly report.</li><li>Disabling users who haven’t logged in for more than 365 days.</li><li>Deleting stale data from the database.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/11/schedule@730w.webp 730w, /images/2019/11/schedule@730w2x.webp 1460w, /images/2019/11/schedule@610w.webp 610w, /images/2019/11/schedule@610w2x.webp 1220w, /images/2019/11/schedule@450w.webp 450w, /images/2019/11/schedule@450w2x.webp 900w, /images/2019/11/schedule@330w.webp 330w, /images/2019/11/schedule@330w2x.webp 660w, /images/2019/11/schedule@545w.webp 545w, /images/2019/11/schedule@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/11/schedule@730w.jpg 730w, /images/2019/11/schedule@730w2x.jpg 1460w, /images/2019/11/schedule@610w.jpg 610w, /images/2019/11/schedule@610w2x.jpg 1220w, /images/2019/11/schedule@450w.jpg 450w, /images/2019/11/schedule@450w2x.jpg 900w, /images/2019/11/schedule@330w.jpg 330w, /images/2019/11/schedule@330w2x.jpg 660w, /images/2019/11/schedule@545w.jpg 545w, /images/2019/11/schedule@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/11/schedule.jpg" alt="Scheduled Jobs" title="Scheduled Jobs"></picture></p><p>Doing so is simple, as long as an application runs on a single machine. In that case, <code>cron</code>, a time-based job scheduler does the trick. But what to do when your workload is running on ECS and Fargate?</p><p>Read on to learn why using <code>cron</code> or ECS scheduled tasks is not an option. On top of that, you will get to know an advanced solution for scheduling jobs on ECS and Fargate.</p><h2 id="Don’t-Use-cron-inside-a-container"><a href="#Don’t-Use-cron-inside-a-container" class="headerlink" title="Don’t: Use cron inside a container"></a>Don’t: Use cron inside a container</h2><p>It is an obvious but wrong choice to use <code>cron</code> to trigger scheduled jobs.</p><ol><li>It is an anti-pattern to run more than one process per container. Therefore, running your application and <code>cron</code> in one container is a no go.</li><li>There is no guarantee that ECS is running your container 24&#x2F;7 exactly once. Therefore, using <code>cron</code> inside a container may cause jobs to run multiple times or not at all.</li><li>It is not very cost-effective to run a container on Fargate 24&#x2F;7 to execute a job a few times per day&#x2F;week&#x2F;month.</li></ol><h2 id="Don’t-Use-ECS-scheduled-tasks"><a href="#Don’t-Use-ECS-scheduled-tasks" class="headerlink" title="Don’t: Use ECS scheduled tasks"></a>Don’t: Use ECS scheduled tasks</h2><p>AWS proposes the following solution in their <a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/scheduled_tasks.html" target="_blank" rel="noopener">documentation</a>:</p><ol><li>Open the AWS Management Console.</li><li>Select your ECS cluster.</li><li>Create a <strong>Scheduled Task</strong> based on a fixed interval or cron-like expression.</li></ol><p>Behind the scenes, AWS is creating a CloudWatch event rule which starts an ECS task based on the defined schedule, as shown in the following figure.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/11/cloudwatch-event-ecs-task@730w.webp 730w, /images/2019/11/cloudwatch-event-ecs-task@730w2x.webp 1460w, /images/2019/11/cloudwatch-event-ecs-task@610w.webp 610w, /images/2019/11/cloudwatch-event-ecs-task@610w2x.webp 1220w, /images/2019/11/cloudwatch-event-ecs-task@450w.webp 450w, /images/2019/11/cloudwatch-event-ecs-task@450w2x.webp 900w, /images/2019/11/cloudwatch-event-ecs-task@330w.webp 330w, /images/2019/11/cloudwatch-event-ecs-task@330w2x.webp 660w, /images/2019/11/cloudwatch-event-ecs-task@545w.webp 545w, /images/2019/11/cloudwatch-event-ecs-task@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/11/cloudwatch-event-ecs-task@730w.png 730w, /images/2019/11/cloudwatch-event-ecs-task@730w2x.png 1460w, /images/2019/11/cloudwatch-event-ecs-task@610w.png 610w, /images/2019/11/cloudwatch-event-ecs-task@610w2x.png 1220w, /images/2019/11/cloudwatch-event-ecs-task@450w.png 450w, /images/2019/11/cloudwatch-event-ecs-task@450w2x.png 900w, /images/2019/11/cloudwatch-event-ecs-task@330w.png 330w, /images/2019/11/cloudwatch-event-ecs-task@330w2x.png 660w, /images/2019/11/cloudwatch-event-ecs-task@545w.png 545w, /images/2019/11/cloudwatch-event-ecs-task@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/11/cloudwatch-event-ecs-task.png" alt="A CloudWatch Events Rule starts an ECS task" title="A CloudWatch Events Rule starts an ECS task"></picture></p><p>I highly recommend not to use this approach because it is missing an important aspect: the <em>monitoring of the scheduled job</em>. Neither does this approach provide a way to define a timeout for the scheduled job, nor does it retry a failed job. Instead, ECS scheduled tasks are operating in fire-and-forget mode. In summary, this approach is not resilient.</p><h2 id="Do-Use-Step-Functions-to-start-and-monitor-an-ECS-task"><a href="#Do-Use-Step-Functions-to-start-and-monitor-an-ECS-task" class="headerlink" title="Do: Use Step Functions to start and monitor an ECS task"></a>Do: Use Step Functions to start and monitor an ECS task</h2><p>Nevertheless, there is a more resilient solution to schedule jobs with ECS and Fargate. As shown in the figure below, three components work together to schedule jobs:</p><ul><li><strong>CloudWatch Events Rule</strong>: triggers the state machine based on a schedule.</li><li><strong>Step Functions</strong>: a state machine orchestrating simple or complex workflows. In this scenario, the state machine starts a container and waits until the container exits.</li><li><strong>Fargate</strong>: the computing engine for the container executing the scheduled job.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/11/cloudwatch-event-step-functions-ecs-task@730w.webp 730w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@730w2x.webp 1460w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@610w.webp 610w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@610w2x.webp 1220w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@450w.webp 450w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@450w2x.webp 900w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@330w.webp 330w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@330w2x.webp 660w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@545w.webp 545w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/11/cloudwatch-event-step-functions-ecs-task@730w.png 730w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@730w2x.png 1460w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@610w.png 610w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@610w2x.png 1220w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@450w.png 450w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@450w2x.png 900w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@330w.png 330w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@330w2x.png 660w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@545w.png 545w, /images/2019/11/cloudwatch-event-step-functions-ecs-task@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/11/cloudwatch-event-step-functions-ecs-task.png" alt="A CloudWatch Events Rule triggers a Step Function which starts an ECS task" title="A CloudWatch Events Rule triggers a Step Function which starts an ECS task"></picture></p><p>The state machine is monitoring the health of the scheduled job. If necessary, the state machine will retry a failed job. Additionally, you should define a timeout for a scheduled job. When doing so, the state machine will stop a scheduled job after reaching the timeout to avoid running jobs endlessly, for example, in case of a misconfiguration.</p><p>Next, you will learn how to create a CloudWatch Events Rule, Step Functions state machine, and Fargate task definition with the help of CloudFormation.</p><p>First, you need to create the basic ECS and Fargate infrastructure consisting of an ECS Cluster, task definition, and security group. I’m skipping the details of how to do so here.</p><figure class="highlight nestedtext"><table><tr><td class="code"><pre><span class="line"><span class="attribute">Cluster</span><span class="punctuation">:</span></span><br><span class="line">  <span class="attribute">Type</span><span class="punctuation">:</span> <span class="string">&#x27;AWS::ECS::Cluster&#x27;</span></span><br><span class="line">  <span class="attribute">Properties</span><span class="punctuation">:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line"><span class="attribute">TaskDefinition</span><span class="punctuation">:</span></span><br><span class="line">  <span class="attribute">Type</span><span class="punctuation">:</span> <span class="string">&#x27;AWS::ECS::TaskDefinition&#x27;</span></span><br><span class="line">  <span class="attribute">Properties</span><span class="punctuation">:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line"><span class="attribute">SecurityGroup</span><span class="punctuation">:</span></span><br><span class="line">  <span class="attribute">Type</span><span class="punctuation">:</span> <span class="string">&#x27;AWS::EC2::SecurityGroup&#x27;</span></span><br><span class="line">  <span class="attribute">Properties</span><span class="punctuation">:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br></pre></td></tr></table></figure><p>Next, create the state machine. What does the state machine do?</p><ul><li>Create a Fargate task based on the task definition defined before.</li><li>Restart the Fargate task up to 3 times in case of a failure.</li><li>Stop the Fargate task if it has not exited after 600 seconds.</li></ul><figure class="highlight nix"><table><tr><td class="code"><pre><span class="line"><span class="params">StateMachine:</span></span><br><span class="line">  <span class="params">Type:</span> &#x27;AWS::StepFunctions::StateMachine&#x27;</span><br><span class="line">  <span class="params">Properties:</span></span><br><span class="line">    <span class="params">DefinitionString:</span> <span class="operator">!</span>Sub</span><br><span class="line">    <span class="operator">-</span> |</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;1.0&quot;</span>,</span><br><span class="line">        <span class="string">&quot;Comment&quot;</span>: <span class="string">&quot;Run ECS/Fargate tasks&quot;</span>,</span><br><span class="line">        <span class="string">&quot;TimeoutSeconds&quot;</span>: $&#123;Timeout&#125;,</span><br><span class="line">        <span class="string">&quot;StartAt&quot;</span>: <span class="string">&quot;RunTask&quot;</span>,</span><br><span class="line">        <span class="string">&quot;States&quot;</span>: &#123;</span><br><span class="line">          <span class="string">&quot;RunTask&quot;</span>: &#123;</span><br><span class="line">            <span class="string">&quot;Type&quot;</span>: <span class="string">&quot;Task&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;arn:aws:states:::ecs:runTask.sync&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Parameters&quot;</span>: &#123;</span><br><span class="line">              <span class="string">&quot;LaunchType&quot;</span>: <span class="string">&quot;FARGATE&quot;</span>,</span><br><span class="line">              <span class="string">&quot;Cluster&quot;</span>: <span class="string">&quot;<span class="subst">$&#123;Cluster&#125;</span>&quot;</span>,</span><br><span class="line">              <span class="string">&quot;TaskDefinition&quot;</span>: <span class="string">&quot;<span class="subst">$&#123;TaskDefinition&#125;</span>&quot;</span>,</span><br><span class="line">              <span class="string">&quot;NetworkConfiguration&quot;</span>: &#123;</span><br><span class="line">                <span class="string">&quot;AwsvpcConfiguration&quot;</span>: &#123;</span><br><span class="line">                  <span class="string">&quot;Subnets&quot;</span>: [<span class="string">&quot;<span class="subst">$&#123;SubnetA&#125;</span>&quot;</span>, <span class="string">&quot;<span class="subst">$&#123;SubnetB&#125;</span>&quot;</span>],</span><br><span class="line">                  <span class="string">&quot;AssignPublicIp&quot;</span>: <span class="string">&quot;<span class="subst">$&#123;AssignPublicIp&#125;</span>&quot;</span>,</span><br><span class="line">                  <span class="string">&quot;SecurityGroups&quot;</span>: $&#123;SecurityGroups&#125;</span><br><span class="line">                &#125;</span><br><span class="line">              &#125;</span><br><span class="line">            &#125;,</span><br><span class="line">            <span class="string">&quot;Retry&quot;</span>: [</span><br><span class="line">              &#123;</span><br><span class="line">                <span class="string">&quot;ErrorEquals&quot;</span>: [</span><br><span class="line">                  <span class="string">&quot;States.TaskFailed&quot;</span></span><br><span class="line">                ],</span><br><span class="line">                <span class="string">&quot;IntervalSeconds&quot;</span>: <span class="number">10</span>,</span><br><span class="line">                <span class="string">&quot;MaxAttempts&quot;</span>: <span class="number">3</span>,</span><br><span class="line">                <span class="string">&quot;BackoffRate&quot;</span>: <span class="number">2</span></span><br><span class="line">              &#125;</span><br><span class="line">            ],</span><br><span class="line">            <span class="string">&quot;End&quot;</span>: <span class="literal">true</span></span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    <span class="operator">-</span> <span class="params">Cluster:</span> <span class="operator">!</span>GetAtt Cluster.Arn</span><br><span class="line">      <span class="params">TaskDefinition:</span> <span class="operator">!</span>Ref TaskDefinition</span><br><span class="line">      <span class="params">SubnetA:</span> &#x27;subnet-<span class="number">00680</span>a8cf7dff9021&#x27; <span class="comment"># Replace with your subnet</span></span><br><span class="line">      <span class="params">SubnetB:</span> &#x27;subnet-<span class="number">023</span>d074f51ea412d0&#x27; <span class="comment"># Replace with your subnet</span></span><br><span class="line">      <span class="params">AssignPublicIp:</span> &#x27;ENABLED&#x27;</span><br><span class="line">      <span class="params">SecurityGroups:</span> <span class="operator">!</span>Sub &#x27;[<span class="string">&quot;<span class="subst">$&#123;SecurityGroup.GroupId&#125;</span>&quot;</span>]&#x27;</span><br><span class="line">      <span class="params">Timeout:</span> <span class="number">600</span> <span class="comment"># 10 minutes</span></span><br><span class="line">    <span class="params">RoleArn:</span> <span class="operator">!</span>GetAtt &#x27;StateMachineRole.Arn&#x27;</span><br></pre></td></tr></table></figure><p>Everything needs an IAM role. The following code snippet shows the IAM role for the state machine.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">StateMachineRole:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">      <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">      <span class="attr">Statement:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">        <span class="attr">Principal:</span></span><br><span class="line">          <span class="attr">Service:</span> <span class="string">&#x27;states.amazonaws.com&#x27;</span></span><br><span class="line">        <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    <span class="attr">Policies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">StateMachine</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;iam:PassRole&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="type">!GetAtt</span> <span class="string">TaskExecutionRole.Arn</span></span><br><span class="line">          <span class="bullet">-</span> <span class="type">!GetAtt</span> <span class="string">TaskRole.Arn</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;ecs:RunTask&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Ref</span> <span class="string">TaskDefinition</span></span><br><span class="line">          <span class="attr">Condition:</span></span><br><span class="line">            <span class="attr">ArnEquals:</span></span><br><span class="line">              <span class="attr">&#x27;ecs:cluster&#x27;:</span> <span class="type">!GetAtt</span> <span class="string">Cluster.Arn</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ecs:StopTask&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ecs:DescribeTasks&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">          <span class="attr">Condition:</span></span><br><span class="line">            <span class="attr">ArnEquals:</span></span><br><span class="line">              <span class="attr">&#x27;ecs:cluster&#x27;:</span> <span class="type">!GetAtt</span> <span class="string">Cluster.Arn</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;events:PutTargets&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;events:PutRule&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;events:DescribeRule&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:$&#123;AWS::Partition&#125;:events:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:rule/StepFunctionsGetEventsForECSTaskRule&#x27;</span></span><br></pre></td></tr></table></figure><p>There is one crucial part missing: the CloudWatch Event Rule, which triggers the state machine based on a schedule. The following snippet shows the rule which will trigger the state machine every hour (see <code>ScheduleExpression</code>.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Rule:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::Events::Rule&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">ScheduleExpression:</span> <span class="string">&#x27;rate(1 hour)&#x27;</span></span><br><span class="line">    <span class="attr">State:</span> <span class="string">ENABLED</span></span><br><span class="line">    <span class="attr">Targets:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Arn:</span> <span class="type">!Ref</span> <span class="string">StateMachine</span></span><br><span class="line">      <span class="attr">Id:</span> <span class="string">statemachine</span></span><br><span class="line">      <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;RuleRole.Arn&#x27;</span></span><br><span class="line"><span class="attr">RuleRole:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">      <span class="attr">Statement:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">        <span class="attr">Principal:</span></span><br><span class="line">          <span class="attr">Service:</span> <span class="string">&#x27;events.amazonaws.com&#x27;</span></span><br><span class="line">        <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    <span class="attr">Policies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">EventRulePolicy</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;states:StartExecution&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Ref</span> <span class="string">StateMachine</span></span><br></pre></td></tr></table></figure><p>Define a scheduled expression in cron or rate style.</p><p>A few examples for schedule expressions in cron style:</p><table><thead><tr><th>Schedule Expression</th><th>Explanation</th></tr></thead><tbody><tr><td><code>cron(0 6 * * ? *)</code></td><td>06:00 am (UTC) every day</td></tr><tr><td><code>cron(0 12 * * MON-FRI *)</code></td><td>12:00 am (UTC) from Monday to Friday</td></tr><tr><td><code>cron(0 8 1 * ? *)</code></td><td>08:00 am (UTC) every first day of the month</td></tr></tbody></table><p>And more examples for schedule expressions in rate style:</p><table><thead><tr><th>Schedule Expression</th><th>Explanation</th></tr></thead><tbody><tr><td><code>rate(15 minutes)</code></td><td>Every 15 minutes</td></tr><tr><td><code>rate(1 hour)</code></td><td>Every hour</td></tr><tr><td><code>rate(14 days)</code></td><td>Every 14 days</td></tr></tbody></table><p>See <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html" target="_blank" rel="noopener">Schedule Expressions for Rules</a> for more detailed explanations.</p><p>One more thing, before we are done. I highly recommend creating two CloudWatch alarms to monitor failed executions and timeouts.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">ExecutionsFailedAlarm:</span></span><br><span class="line">  <span class="attr">Condition:</span> <span class="string">HasAlertingModule</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AlarmDescription:</span> <span class="string">&#x27;Failure while executing scheduled task.&#x27;</span></span><br><span class="line">    <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/States&#x27;</span></span><br><span class="line">    <span class="attr">MetricName:</span> <span class="string">ExecutionsFailed</span></span><br><span class="line">    <span class="attr">Dimensions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">StateMachineArn</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">StateMachine</span></span><br><span class="line">    <span class="attr">Statistic:</span> <span class="string">Sum</span></span><br><span class="line">    <span class="attr">Period:</span> <span class="number">300</span></span><br><span class="line">    <span class="attr">DatapointsToAlarm:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">0</span></span><br><span class="line">    <span class="attr">TreatMissingData:</span> <span class="string">notBreaching</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;arn:aws:sns:eu-west-1:111111111111:alerting&#x27;</span> <span class="comment"># Replace with SNS topic ARN</span></span><br><span class="line"><span class="attr">ExecutionsTimeoutAlarm:</span></span><br><span class="line">  <span class="attr">Condition:</span> <span class="string">HasAlertingModule</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AlarmDescription:</span> <span class="string">&#x27;Executing scheduled task timed out.&#x27;</span></span><br><span class="line">    <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/States&#x27;</span></span><br><span class="line">    <span class="attr">MetricName:</span> <span class="string">ExecutionsTimedOut</span></span><br><span class="line">    <span class="attr">Dimensions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">StateMachineArn</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">StateMachine</span></span><br><span class="line">    <span class="attr">Statistic:</span> <span class="string">Sum</span></span><br><span class="line">    <span class="attr">Period:</span> <span class="number">300</span></span><br><span class="line">    <span class="attr">DatapointsToAlarm:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">0</span></span><br><span class="line">    <span class="attr">TreatMissingData:</span> <span class="string">notBreaching</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;arn:aws:sns:eu-west-1:111111111111:alerting&#x27;</span> <span class="comment"># Replace with SNS topic ARN</span></span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>That’s all! Run scheduled tasks resiliently with the help of a CloudWatch Events Rule, a Step Functions state machine, and Fargate. Using ECS Scheduled Tasks or even worse, <code>cron</code> is not an option.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How we run our blog cloudonaut.io</title>
      <link>https://cloudonaut.io/how-we-run-our-blog-cloudfront-s3-hexo/</link>
      <description>
        <![CDATA[<p>Now and then you ask us: How do you run cloudonaut.io? Today, I want to share some insights with you about the work and technology behind]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/cloudfront/">cloudfront</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-we-run-our-blog-cloudfront-s3-hexo/</guid>
      <pubDate>Tue, 29 Oct 2019 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Now and then you ask us: How do you run cloudonaut.io? Today, I want to share some insights with you about the work and technology behind the scenes of our blog.</p><ul><li>How do we come up with new topics?</li><li>How do we host this website?</li><li>How do we survive to be listed on the front page of <a href="https://news.ycombinator.com/" target="_blank" rel="noopener">Hacker News</a>?</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/10/backstage@730w.webp 730w, /images/2019/10/backstage@730w2x.webp 1460w, /images/2019/10/backstage@610w.webp 610w, /images/2019/10/backstage@610w2x.webp 1220w, /images/2019/10/backstage@450w.webp 450w, /images/2019/10/backstage@450w2x.webp 900w, /images/2019/10/backstage@330w.webp 330w, /images/2019/10/backstage@330w2x.webp 660w, /images/2019/10/backstage@545w.webp 545w, /images/2019/10/backstage@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/10/backstage@730w.jpg 730w, /images/2019/10/backstage@730w2x.jpg 1460w, /images/2019/10/backstage@610w.jpg 610w, /images/2019/10/backstage@610w2x.jpg 1220w, /images/2019/10/backstage@450w.jpg 450w, /images/2019/10/backstage@450w2x.jpg 900w, /images/2019/10/backstage@330w.jpg 330w, /images/2019/10/backstage@330w2x.jpg 660w, /images/2019/10/backstage@545w.jpg 545w, /images/2019/10/backstage@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/10/backstage.jpg" alt="How we run our blog cloudonaut.io" title="How we run our blog cloudonaut.io"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/7-how-we-run-our-blog-cloudfront-s3-hexo/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="Finding-new-topics-and-planning"><a href="#Finding-new-topics-and-planning" class="headerlink" title="Finding new topics and planning"></a>Finding new topics and planning</h2><p>We work with AWS daily in customer projects. Because of that, we receive a lot of questions, see a lot of architectures, and we can identify common issues. All of this is the input for new blog posts. Andreas and I aim to publish one post per week. Depending on our workload, we have a queue of posts ready for publication, or we write them the day before you can read them.</p><h2 id="Writing-hexo"><a href="#Writing-hexo" class="headerlink" title="Writing: hexo"></a>Writing: hexo</h2><p>Andreas and I write all our posts with <a href="https://www.sublimetext.com/" target="_blank" rel="noopener">Sublime Text</a> in <a href="https://en.wikipedia.org/wiki/Markdown" target="_blank" rel="noopener">Markdown</a>. The last sentence in Markdown looks like this:</p><figure class="highlight markdown"><table><tr><td class="code"><pre><span class="line"><span class="section">## Writing: hexo</span></span><br><span class="line">Andreas and I write all our posts with [<span class="string">Sublime Text</span>](<span class="link">https://www.sublimetext.com/</span>) in [<span class="string">Markdown</span>](<span class="link">https://en.wikipedia.org/wiki/Markdown</span>).</span><br></pre></td></tr></table></figure><p>We use the blog framework <a href="https://hexo.io/" target="_blank" rel="noopener">hexo</a> to convert all the Markdown files into static HTML pages. hexo also supports themes and plugins that we use to customize the generated HTML.</p><p>We use plugins to:</p><ul><li>Extract the preview image from the content of an article to support <a href="/page/1/">list views</a>.</li><li>Make use of the HTML <code>&lt;picture&gt;</code> element to optimize the page size.</li><li>Support <a href="https://github.com/widdix/hexo-footnotes" target="_blank" rel="noopener">footnotes</a>.</li><li>Offer an <a href="https://github.com/widdix/hexo-generator-feed" target="_blank" rel="noopener">RSS feed</a>.</li><li>Display <a href="https://git@github.com/widdix/hexo-tag-table-bootstrap" target="_blank" rel="noopener">shiny tables</a>.</li></ul><p>A <code>git</code> branch is created for every new post and pushed to a private repository on <a href="http://github.com/" target="_blank" rel="noopener">GitHub</a>. GitHub supports excellent review capabilities via pull requests, as the following figure shows.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/10/github-pr@730w.webp 730w, /images/2019/10/github-pr@730w2x.webp 1460w, /images/2019/10/github-pr@610w.webp 610w, /images/2019/10/github-pr@610w2x.webp 1220w, /images/2019/10/github-pr@450w.webp 450w, /images/2019/10/github-pr@450w2x.webp 900w, /images/2019/10/github-pr@330w.webp 330w, /images/2019/10/github-pr@330w2x.webp 660w, /images/2019/10/github-pr@545w.webp 545w, /images/2019/10/github-pr@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/10/github-pr@730w.png 730w, /images/2019/10/github-pr@730w2x.png 1460w, /images/2019/10/github-pr@610w.png 610w, /images/2019/10/github-pr@610w2x.png 1220w, /images/2019/10/github-pr@450w.png 450w, /images/2019/10/github-pr@450w2x.png 900w, /images/2019/10/github-pr@330w.png 330w, /images/2019/10/github-pr@330w2x.png 660w, /images/2019/10/github-pr@545w.png 545w, /images/2019/10/github-pr@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/10/github-pr.png" alt="We use GitHub pull requests to review blog posts" title="We use GitHub pull requests to review blog posts"></picture></p><p>Depending on the length of the blog post, we invest 2-8 hours into a blog post.</p><h2 id="Deployment-Jenkins"><a href="#Deployment-Jenkins" class="headerlink" title="Deployment: Jenkins"></a>Deployment: Jenkins</h2><p>Once we merge a pull request, the continuous deployment pipeline starts. The most important steps are:</p><ol><li>Install Node.js dependencies: <code>npm ci --production</code></li><li>Generate website files: <code>node ./node_modules/.bin/hexo generate</code></li><li>Copy the generated files to S3: <code>aws s3 sync [...]</code></li><li>Invalidate the CloudFront cache: <code>INVALIDATION_ID=$(aws cloudfront create-invalidation --distribution-id [...] --paths &#39;/*&#39; --query &#39;Invalidation.Id&#39; --output text)</code></li><li>Wait for the invalidation to finish: <code>aws cloudfront wait invalidation-completed --distribution-id [...] --id $INVALIDATION_ID</code></li><li>Probe the most important pages to check that they return expected content (e.g., check if the RSS feed works)</li></ol><p>The cloudonaut.io deployment pipeline is hosted on our Jenkins infrastructure. The task to migrate to CodePipeline waits in our backlog for quite some time. 🤷‍♂️ </p><h2 id="Static-Hosting-S3-CloudFront"><a href="#Static-Hosting-S3-CloudFront" class="headerlink" title="Static Hosting: S3 &amp; CloudFront"></a>Static Hosting: S3 &amp; CloudFront</h2><p>We are using CloudFront as our CDN and S3 as the origin is a proven serverless architecture. We use our open-source <a href="https://templates.cloudonaut.io/en/stable/static-website/" target="_blank" rel="noopener">CloudFormation template</a> to deploy the setup. CloudFront and S3 ensure that our website does not crash even under heavy load.</p><h2 id="Edge-intelligence-Lambda-Edge"><a href="#Edge-intelligence-Lambda-Edge" class="headerlink" title="Edge intelligence: Lambda@Edge"></a>Edge intelligence: Lambda@Edge</h2><p><a href="https://aws.amazon.com/lambda/edge/" target="_blank" rel="noopener">Lambda@Edge</a> is a way to execute your code in CloudFront. You can execute your code when:</p><ol><li>The request comes in from the client.</li><li>Before the request is forwarded to the origin (cache miss).</li><li>After the response from the origin is received.</li><li>Before the response is sent to the client.</li></ol><p>We use Lambda@Edge for redirects and <a href="https://github.com/widdix/static-website-img-optimize" target="_blank" rel="noopener">on-the-fly image optimization</a>.</p><h2 id="Alerting-Incident-Management-CloudWatch-marbot"><a href="#Alerting-Incident-Management-CloudWatch-marbot" class="headerlink" title="Alerting + Incident Management: CloudWatch &amp; marbot"></a>Alerting + Incident Management: CloudWatch &amp; marbot</h2><p>We use <a href="https://uptimerobot.com/" target="_blank" rel="noopener">Uptime Robot</a> to check from outside of AWS if our website is reachable. If not, we send an alert to marbot: <a href="https://marbot.io/" target="_blank" rel="noopener">our Slack based incident management solution</a> plus an email (hosted by Google).</p><p>We also observe a bunch of CloudWatch Metrics from CloudFront to detect if our Lambda@Edge functions are having issues or we respond with 5xx codes. Whatever goes wrong, <a href="https://marbot.io/" target="_blank" rel="noopener">marbot</a> will alert us as the following figure shows.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/10/marbot-cloudfront-alert@730w.webp 730w, /images/2019/10/marbot-cloudfront-alert@730w2x.webp 1460w, /images/2019/10/marbot-cloudfront-alert@610w.webp 610w, /images/2019/10/marbot-cloudfront-alert@610w2x.webp 1220w, /images/2019/10/marbot-cloudfront-alert@450w.webp 450w, /images/2019/10/marbot-cloudfront-alert@450w2x.webp 900w, /images/2019/10/marbot-cloudfront-alert@330w.webp 330w, /images/2019/10/marbot-cloudfront-alert@330w2x.webp 660w, /images/2019/10/marbot-cloudfront-alert@545w.webp 545w, /images/2019/10/marbot-cloudfront-alert@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/10/marbot-cloudfront-alert@730w.png 730w, /images/2019/10/marbot-cloudfront-alert@730w2x.png 1460w, /images/2019/10/marbot-cloudfront-alert@610w.png 610w, /images/2019/10/marbot-cloudfront-alert@610w2x.png 1220w, /images/2019/10/marbot-cloudfront-alert@450w.png 450w, /images/2019/10/marbot-cloudfront-alert@450w2x.png 900w, /images/2019/10/marbot-cloudfront-alert@330w.png 330w, /images/2019/10/marbot-cloudfront-alert@330w2x.png 660w, /images/2019/10/marbot-cloudfront-alert@545w.png 545w, /images/2019/10/marbot-cloudfront-alert@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/10/marbot-cloudfront-alert.png" alt="CloudFront Lambda@Edge alert in marbot" title="CloudFront Lambda@Edge alert in marbot"></picture></p><p>We will not be able to respond 24&#x2F;7, but we do our best to keep the site up and running.</p><p>The CloudWatch monitoring is part of the next version of our open source CloudFormation templates <a href="https://github.com/widdix/aws-cf-templates/pull/360" target="_blank" rel="noopener">watch this GitHub pull request</a>.</p><h2 id="Analytics-Athena-and-QuickSight"><a href="#Analytics-Athena-and-QuickSight" class="headerlink" title="Analytics: Athena and QuickSight"></a>Analytics: Athena and QuickSight</h2><p>Last but not least, we want to know how many people visit our blog. Earlier this year, we replaced Google Analytics with a solution that we own and control. <strong>We also removed all cookies</strong>. Andreas <a href="/own-your-analytics-data-replacing-google-analytics-with-amazon-quicksight/">wrote a detailed article about the solution</a>. In a nutshell, we use:</p><ul><li>Amazon CloudFront for collecting access logs.</li><li>Amazon Simple Storage Service (S3) as the data lake to store access logs.</li><li>The ad-hoc analytics service Amazon Athena to query data.</li><li>The business intelligence (BI) solution Amazon QuickSight to visualize the tracking data.</li></ul><p>The following figure demonstrates how the solution works.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/04/athena-quicksight@730w.webp 730w, /images/2019/04/athena-quicksight@730w2x.webp 1460w, /images/2019/04/athena-quicksight@610w.webp 610w, /images/2019/04/athena-quicksight@610w2x.webp 1220w, /images/2019/04/athena-quicksight@450w.webp 450w, /images/2019/04/athena-quicksight@450w2x.webp 900w, /images/2019/04/athena-quicksight@330w.webp 330w, /images/2019/04/athena-quicksight@330w2x.webp 660w, /images/2019/04/athena-quicksight@545w.webp 545w, /images/2019/04/athena-quicksight@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/04/athena-quicksight@730w.png 730w, /images/2019/04/athena-quicksight@730w2x.png 1460w, /images/2019/04/athena-quicksight@610w.png 610w, /images/2019/04/athena-quicksight@610w2x.png 1220w, /images/2019/04/athena-quicksight@450w.png 450w, /images/2019/04/athena-quicksight@450w2x.png 900w, /images/2019/04/athena-quicksight@330w.png 330w, /images/2019/04/athena-quicksight@330w2x.png 660w, /images/2019/04/athena-quicksight@545w.png 545w, /images/2019/04/athena-quicksight@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/04/athena-quicksight.png" alt="Overview: CloudFront, S3, Athena, QuickSight" title="Overview: CloudFront, S3, Athena, QuickSight"></picture></p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>We love simplicity! Our blog runs on CloudFront and S3, which is maintenance-free and does handle traffic spikes easily. We use the static website generator <a href="https://hexo.io/" target="_blank" rel="noopener">hexo</a> to publish our content. Lambda@Edge handles redirects and generates optimized images on the fly. Instead of Google Analytics, we are using Athena and QuickSight to get statistics about our blog and posts.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Show your Tool: awsume</title>
      <link>https://cloudonaut.io/show-your-tool-awsume/</link>
      <description>
        <![CDATA[<p>In this series, we present AWS tooling from the community for the community. We talk directly with the tool makers. Who are they? What pr]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/show-your-tool/">Show your Tool</category>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <guid isPermaLink="true">https://cloudonaut.io/show-your-tool-awsume/</guid>
      <pubDate>Tue, 22 Oct 2019 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>In this series, we present AWS tooling from the community for the community. We talk directly with the tool makers. Who are they? What problem does the tool solve? And what motivates them to contribute to open-source AWS tooling.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/10/show-your-tool-awsume@730w.webp 730w, /images/2019/10/show-your-tool-awsume@730w2x.webp 1460w, /images/2019/10/show-your-tool-awsume@610w.webp 610w, /images/2019/10/show-your-tool-awsume@610w2x.webp 1220w, /images/2019/10/show-your-tool-awsume@450w.webp 450w, /images/2019/10/show-your-tool-awsume@450w2x.webp 900w, /images/2019/10/show-your-tool-awsume@330w.webp 330w, /images/2019/10/show-your-tool-awsume@330w2x.webp 660w, /images/2019/10/show-your-tool-awsume@545w.webp 545w, /images/2019/10/show-your-tool-awsume@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/10/show-your-tool-awsume@730w.jpg 730w, /images/2019/10/show-your-tool-awsume@730w2x.jpg 1460w, /images/2019/10/show-your-tool-awsume@610w.jpg 610w, /images/2019/10/show-your-tool-awsume@610w2x.jpg 1220w, /images/2019/10/show-your-tool-awsume@450w.jpg 450w, /images/2019/10/show-your-tool-awsume@450w2x.jpg 900w, /images/2019/10/show-your-tool-awsume@330w.jpg 330w, /images/2019/10/show-your-tool-awsume@330w2x.jpg 660w, /images/2019/10/show-your-tool-awsume@545w.jpg 545w, /images/2019/10/show-your-tool-awsume@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/10/show-your-tool-awsume.jpg" alt="Show your Tool" title="Show your Tool"></picture></p><p>This time, we talk with Michael Barney about <a href="https://awsu.me/" target="_blank" rel="noopener">awsume</a>. You can find Michael on <a href="https://github.com/mbarneyjr" target="_blank" rel="noopener">GitHub</a> and <a href="https://x.com/mbarneyjr" target="_blank" rel="noopener">Twitter</a>. Michael also has <a href="https://mbarney.me/" target="_blank" rel="noopener">a website</a>.</p><p><em>cloudonaut: Tell us a little about yourself, your history with AWS, and your motivation to develop AWS tooling.</em><br>Michael Barney: After college, I started at Trek10, an AWS consulting partner specializing in serverless. It was there that I discovered a passion for Serverless. As of writing, I’ve been working with AWS for about two years. I believe that the more AWS tooling evolves, the more enabled developers will be. The tool I’ve worked on, <a href="https://awsu.me/" target="_blank" rel="noopener">awsume</a>, has helped the team at Trek10 a ton.</p><p><em>cloudonaut: What problem does your tool solve?</em><br>Michael Barney: <em>awsume</em>‚ is a command-line tool that grabs AWS credentials (access keys, session token, etc.) and exports them to environment variables. It makes MFA much easier to use, it’s extensible, and it comes with a few cool built-in utilities such as <em>autoawsume</em>, which automatically refreshes your credentials for you in the background.</p><p><em>cloudonaut: Who should use your tool? Who should not?</em><br>Michael Barney: The motivation behind the tool was that since the AWS CLI would use the MFA token on the assume-role call, you would have to re-enter your MFA token every hour. What you can do instead is make the <code>get-session-token</code> call on the source profile, which returns MFA credentials that are valid for 12 hours, so you can use those credentials every hour to renew your role credentials, without needing to re-enter your MFA token.</p><p>Trek10 does work in multiple accounts for each of our clients, so another motivation behind <em>awsume</em> is that maintaining your config and credentials files became cumbersome. <em>awsume</em> helped in that aspect by being extensible, so we developed a plugin to inject those profiles from our internal database at runtime.</p><p>Long story short, <em>awsume</em> is meant to be used by anyone who needs to access an aws account. Whether you’re part of a large complex organization or a hobbyist with a personal account, <em>awsume</em> could definitely help you out.</p><p><em>cloudonaut: Show us a short demo of your tool</em><br>Michael Barney: Please check out this short demo.</p><p><img class="img-fluid" src="https://awsu.me/assets/img/demo.2f37110c.gif" alt="awsume Demo" title="awsume Demo"></p><p>Basically, you run <code>awsume &lt;profile_name&gt;</code>, but there’s plenty of other ways to use <em>awsume</em>. Check out the <a href="https://awsu.me/general/usage.html" target="_blank" rel="noopener">documentation</a> to learn about all features.</p><p><em>cloudonaut: How can we use your tool?</em><br>Michael Barney: Follow the quickstart guide at <a href="https://awsu.me/general/quickstart.html" target="_blank" rel="noopener">https://awsu.me/general/quickstart.html</a>.</p><p>On most machines, you should be able to <code>pip install awsume</code>, restart your terminal, and it’ll work.</p><p><em>cloudonaut: Where can we find more information about your tool?</em><br>Michael Barney: I would check out the <a href="https://awsu.me/" target="_blank" rel="noopener">awsume</a> website.</p><p><em>cloudonaut: Are you aware of tools that solve a similar problem than yours? What’s the difference?</em><br>Michael Barney: There are a few, namely <em>aws-vault</em>. As someone that hasn’t used it, it’s hard to talk about the differences, but I do know that <em>aws-vault</em> stores your credentials in the macOS Keychain, Windows Credential Manager, etc. as opposed to the default config and credentials files.</p><p><em>cloudonaut: What’s the roadmap for your tool? Are you planning any significant releases?</em><br>Michael Barney: There isn’t a public roadmap. You could check the <a href="https://github.com/trek10inc/awsume" target="_blank" rel="noopener">GitHub repo</a> for open issues and feature requests, so if you want it to do something that it doesn’t already, feel free to open an issue. </p><p><em>cloudonaut: How do you stay motivated to maintain your open source project?</em><br>Michael Barney: Working at a company that encourages best practices and thought leadership like Trek10, it’s pretty easy to stay motivated. I love good software.</p><p><em>cloudonaut: Are you attending any conferences within the next few months where the community can get in touch?</em><br>Michael Barney: I should be attending ServerlessConf this year.</p><p>–</p><p>What tools do you use to make your AWS work easier? <a href="mailto:&#x68;&#101;&#x6c;&#x6c;&#111;&#x40;&#x63;&#108;&#111;&#x75;&#x64;&#111;&#x6e;&#x61;&#x75;&#116;&#x2e;&#x69;&#111;">Share your favorite tool with us</a>!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to avoid S3 data leaks?</title>
      <link>https://cloudonaut.io/s3-security-best-practice/</link>
      <description>
        <![CDATA[<p>Not a week goes by without a frightening announcement that an organization has leaked confidential data from Amazon S3 accidentally. Most]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <guid isPermaLink="true">https://cloudonaut.io/s3-security-best-practice/</guid>
      <pubDate>Tue, 15 Oct 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Not a week goes by without a frightening announcement that an organization has leaked confidential data from Amazon S3 accidentally. Most often, the root cause of a security breach is a misconfiguration of S3 access control.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/10/chain-door-lock@730w.webp 730w, /images/2019/10/chain-door-lock@730w2x.webp 1460w, /images/2019/10/chain-door-lock@610w.webp 610w, /images/2019/10/chain-door-lock@610w2x.webp 1220w, /images/2019/10/chain-door-lock@450w.webp 450w, /images/2019/10/chain-door-lock@450w2x.webp 900w, /images/2019/10/chain-door-lock@330w.webp 330w, /images/2019/10/chain-door-lock@330w2x.webp 660w, /images/2019/10/chain-door-lock@545w.webp 545w, /images/2019/10/chain-door-lock@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/10/chain-door-lock@730w.jpg 730w, /images/2019/10/chain-door-lock@730w2x.jpg 1460w, /images/2019/10/chain-door-lock@610w.jpg 610w, /images/2019/10/chain-door-lock@610w2x.jpg 1220w, /images/2019/10/chain-door-lock@450w.jpg 450w, /images/2019/10/chain-door-lock@450w2x.jpg 900w, /images/2019/10/chain-door-lock@330w.jpg 330w, /images/2019/10/chain-door-lock@330w2x.jpg 660w, /images/2019/10/chain-door-lock@545w.jpg 545w, /images/2019/10/chain-door-lock@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/10/chain-door-lock.jpg" alt="3 rules to avoid data leaking from S3" title="3 rules to avoid data leaking from S3"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/6-s3-security-best-practice/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>It may not have been AWS’s best decision to provide a service that allows you to both make data publicly available and store highly sensitive data. But it is what it is. Therefore, you should be very careful when configuring access control for your S3 buckets. The following rules make sure you do not make data available publicly.</p><h2 id="Rule-1-Use-IAM-policies-to-grant-access-to-your-S3-bucket"><a href="#Rule-1-Use-IAM-policies-to-grant-access-to-your-S3-bucket" class="headerlink" title="Rule #1: Use IAM policies to grant access to your S3 bucket"></a>Rule #1: Use IAM policies to grant access to your S3 bucket</h2><p>Unfortunately, there are three different ways to control access to an S3 bucket. Use the following steps to decide which option to choose:</p><ol><li>Use an <strong>IAM policy</strong> to grant access to your S3 bucket whenever the caller can authenticate as an IAM principal (user or role).</li><li>Use a <strong>bucket policy</strong> only in exceptional cases when using an IAM policy is not an option (e.g., to allow read access from the whole organization, …).</li><li>Never use <strong>bucket or object ACLs</strong>.</li></ol><p>Believe me, when doing security reviews, I’ve seen too many misconfigured bucket policies and ACLs. Sticking to IAM policies simplifies access control a lot.</p><h2 id="Rule-2-Enable-Block-Public-Access"><a href="#Rule-2-Enable-Block-Public-Access" class="headerlink" title="Rule #2: Enable Block Public Access"></a>Rule #2: Enable Block Public Access</h2><p>AWS announced <strong>Block Public Access</strong> in November 2018, a safety net to protect the data stored in your S3 buckets. There are four options available:</p><ul><li>Block public access to buckets and objects granted through <strong>new access control lists (ACLs)</strong></li><li>Block public access to buckets and objects granted through <strong>any access control lists (ACLs)</strong></li><li>Block public access to buckets and objects granted through <strong>new public bucket policies</strong></li><li>Block public and cross-account access to buckets and objects through <strong>any public bucket policies</strong></li></ul><p>Do none of your S3 buckets contain data that you want to share publicly? Lucky you!</p><p>Enable <strong>Block Public Access</strong> for all S3 buckets in your AWS account:</p><ol><li>Open the AWS Management Console.</li><li>Go to the S3 service.</li><li>Select <code>Block public access (account settings)</code> from the sub-navigation.</li><li>Enable <code>Block all public access</code>.</li><li>Sleep well. 🛌</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/10/s3-block-public-access@730w.webp 730w, /images/2019/10/s3-block-public-access@730w2x.webp 1460w, /images/2019/10/s3-block-public-access@610w.webp 610w, /images/2019/10/s3-block-public-access@610w2x.webp 1220w, /images/2019/10/s3-block-public-access@450w.webp 450w, /images/2019/10/s3-block-public-access@450w2x.webp 900w, /images/2019/10/s3-block-public-access@330w.webp 330w, /images/2019/10/s3-block-public-access@330w2x.webp 660w, /images/2019/10/s3-block-public-access@545w.webp 545w, /images/2019/10/s3-block-public-access@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/10/s3-block-public-access@730w.png 730w, /images/2019/10/s3-block-public-access@730w2x.png 1460w, /images/2019/10/s3-block-public-access@610w.png 610w, /images/2019/10/s3-block-public-access@610w2x.png 1220w, /images/2019/10/s3-block-public-access@450w.png 450w, /images/2019/10/s3-block-public-access@450w2x.png 900w, /images/2019/10/s3-block-public-access@330w.png 330w, /images/2019/10/s3-block-public-access@330w2x.png 660w, /images/2019/10/s3-block-public-access@545w.png 545w, /images/2019/10/s3-block-public-access@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/10/s3-block-public-access.png" alt="Enable Block Public Access for all S3 buckets in your AWS account" title="Enable Block Public Access for all S3 buckets in your AWS account"></picture></p><p>Alternatively, you can use the Terraform resource <a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_account_public_access_block.html" target="_blank" rel="noopener">aws_s3_account_public_access_block</a> to roll-out the configuration to your AWS accounts. Unfortunately, <a href="https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/168" target="_blank" rel="noopener">CloudFormation does still not allow you to do so</a>. 😡</p><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="attribute">resource</span> <span class="string">&quot;aws_s3_account_public_access_block&quot;</span> <span class="string">&quot;example&quot;</span> &#123;</span><br><span class="line">  <span class="attribute">block_public_acls</span>       = <span class="literal">true</span></span><br><span class="line">  block_public_policy     = <span class="literal">true</span></span><br><span class="line">  ignore_public_acls      = <span class="literal">true</span></span><br><span class="line">  restrict_public_buckets = <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>But what to do when you use S3 for both use cases: public and confidential data? In that scenario, you need to enable <em>Block Public Access</em> only for S3 buckets containing sensitive data.</p><p>By default, AWS enables all four options when you create a new S3 bucket via the AWS Management Console. However, you need to enable <em>Block Public Access</em> yourself when working with Terraform or CloudFormation.</p><p>Use the <a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block.html" target="_blank" rel="noopener">aws_s3_bucket_public_access_block</a> resource when working with Terraform.</p><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="attribute">resource</span> <span class="string">&quot;aws_s3_bucket&quot;</span> <span class="string">&quot;example&quot;</span> &#123;</span><br><span class="line">  <span class="attribute">bucket</span> = <span class="string">&quot;example&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">resource <span class="string">&quot;aws_s3_bucket_public_access_block&quot;</span> <span class="string">&quot;example&quot;</span> &#123;</span><br><span class="line">  <span class="attribute">bucket</span> = <span class="string">&quot;$&#123;aws_s3_bucket.example.id&#125;&quot;</span></span><br><span class="line"></span><br><span class="line">  block_public_acls   = <span class="literal">true</span></span><br><span class="line">  block_public_policy = <span class="literal">true</span></span><br><span class="line">  ignore_public_acls      = <span class="literal">true</span></span><br><span class="line">  restrict_public_buckets = <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Alternatively, use the <code>PublicAccessBlockConfiguration</code> property of the <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html" target="_blank" rel="noopener">AWS::S3::Bucket</a> resource when working with CloudFormation.</p><figure class="highlight nix"><table><tr><td class="code"><pre><span class="line"><span class="params">MyBucket:</span></span><br><span class="line">  <span class="params">Type:</span> &#x27;AWS::S3::Bucket&#x27;</span><br><span class="line">  <span class="params">Properties:</span></span><br><span class="line">    <span class="params">BucketName:</span> &#x27;example&#x27;</span><br><span class="line">    <span class="params">PublicAccessBlockConfiguration:</span> </span><br><span class="line">      <span class="params">BlockPublicAcls:</span> <span class="literal">true</span></span><br><span class="line">      <span class="params">BlockPublicPolicy:</span> <span class="literal">true</span></span><br><span class="line">      <span class="params">IgnorePublicAcls:</span> <span class="literal">true</span></span><br><span class="line">      <span class="params">RestrictPublicBuckets:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>Don’t forget to enable <em>Block Public Access</em> for all existing and future buckets containing private data.</p><h2 id="Rule-3-Separate-public-from-private-data"><a href="#Rule-3-Separate-public-from-private-data" class="headerlink" title="Rule #3: Separate public from private data"></a>Rule #3: Separate public from private data</h2><p>Do not mix public data and private data into the same S3 bucket. Doing so increases the risk of a misconfiguration dramatically. Also, make sure the name of your S3 bucket indicates whether it contains private or public data.</p><h2 id="Rule-4-Monitor-Trusted-Advisor-findings"><a href="#Rule-4-Monitor-Trusted-Advisor-findings" class="headerlink" title="Rule #4: Monitor Trusted Advisor findings"></a>Rule #4: Monitor Trusted Advisor findings</h2><p>I highly recommend asking a 3rd party to review your access control configuration regularly. You might want to task a professional to do so (hint: we are offering technical security reviews of IAM (including S3) and VPC networking). Another option is to ask the AWS Trusted Advisor for help.</p><p>The Trusted Advisor constantly scans your AWS account for typical weak points, not only of your security configuration, in an automated way. One of the many checks performed by the Trusted Advisor is called <code>Amazon S3 Bucket Permissions</code> which watches for public access granted by bucket policies or ACLs.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/10/s3-trusted-advisor@730w.webp 730w, /images/2019/10/s3-trusted-advisor@730w2x.webp 1460w, /images/2019/10/s3-trusted-advisor@610w.webp 610w, /images/2019/10/s3-trusted-advisor@610w2x.webp 1220w, /images/2019/10/s3-trusted-advisor@450w.webp 450w, /images/2019/10/s3-trusted-advisor@450w2x.webp 900w, /images/2019/10/s3-trusted-advisor@330w.webp 330w, /images/2019/10/s3-trusted-advisor@330w2x.webp 660w, /images/2019/10/s3-trusted-advisor@545w.webp 545w, /images/2019/10/s3-trusted-advisor@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/10/s3-trusted-advisor@730w.png 730w, /images/2019/10/s3-trusted-advisor@730w2x.png 1460w, /images/2019/10/s3-trusted-advisor@610w.png 610w, /images/2019/10/s3-trusted-advisor@610w2x.png 1220w, /images/2019/10/s3-trusted-advisor@450w.png 450w, /images/2019/10/s3-trusted-advisor@450w2x.png 900w, /images/2019/10/s3-trusted-advisor@330w.png 330w, /images/2019/10/s3-trusted-advisor@330w2x.png 660w, /images/2019/10/s3-trusted-advisor@545w.png 545w, /images/2019/10/s3-trusted-advisor@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/10/s3-trusted-advisor.png" alt="Trusted Advisor: Amazon S3 Bucket Permissions check" title="Trusted Advisor: Amazon S3 Bucket Permissions check"></picture></p><p>I highly recommend enabling <strong>Weekly Email Notification</strong> from the Trusted Advisor. That way, you’ll prevent missing a warning.</p><ol><li>Open the AWS Management Console.</li><li>Go to the Trusted Advisor service.</li><li>Select <code>Preferences</code> from the sub-navigation.</li><li>Enable weekly email notifications.</li><li>Sleep a little bit better. 🛌</li></ol><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Take your time configuring the access permissions for your S3 bucket. Follow these three rules to avoid leaking data from S3:</p><ol><li><strong>Use IAM policies to control access to your data stored in S3.</strong> Avoid using bucket policies whenever possible. Do not even think about using a bucket or object ACLs.</li><li><strong>Enable Block Public Access.</strong> If possible, do so for your AWS account to protect all buckets. If not, make sure to enable <em>Block Public Access</em> for all existing and future S3 buckets containing private data.</li><li><strong>Do not mix private and public data within an S3 bucket.</strong> Also make sure, the bucket name includes the data classification.</li><li><strong>Enable email notifications from Trusted Advisor</strong> to get notified of unintended changes to your bucket policies and bucket ACLs. Especially, watch out for the <strong>Amazon S3 Bucket Permissions</strong> check.</li></ol><hr><p>Are 3rd parties uploading data to your S3 bucket? Check for malware and viruses with <a href="https://bucketav.com/" target="_blank" rel="noopener">bucketAV - Antivirus for Amazon S3</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Prevent CloudFormation Change Sets from piling up</title>
      <link>https://cloudonaut.io/aws-cli-cloudformation-deploy-limit-exceeded/</link>
      <description>
        <![CDATA[<p>Recently, I’ve stumbled upon a problem when using <code>aws cloudformation deploy</code> within deployment pipelines (Jenkins, GitLab CI,]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cli/">cli</category>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-cli-cloudformation-deploy-limit-exceeded/</guid>
      <pubDate>Thu, 10 Oct 2019 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Recently, I’ve stumbled upon a problem when using <code>aws cloudformation deploy</code> within deployment pipelines (Jenkins, GitLab CI, …) that I wanted to share with you.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/10/pile@730w.webp 730w, /images/2019/10/pile@730w2x.webp 1460w, /images/2019/10/pile@610w.webp 610w, /images/2019/10/pile@610w2x.webp 1220w, /images/2019/10/pile@450w.webp 450w, /images/2019/10/pile@450w2x.webp 900w, /images/2019/10/pile@330w.webp 330w, /images/2019/10/pile@330w2x.webp 660w, /images/2019/10/pile@545w.webp 545w, /images/2019/10/pile@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/10/pile@730w.jpg 730w, /images/2019/10/pile@730w2x.jpg 1460w, /images/2019/10/pile@610w.jpg 610w, /images/2019/10/pile@610w2x.jpg 1220w, /images/2019/10/pile@450w.jpg 450w, /images/2019/10/pile@450w2x.jpg 900w, /images/2019/10/pile@330w.jpg 330w, /images/2019/10/pile@330w2x.jpg 660w, /images/2019/10/pile@545w.jpg 545w, /images/2019/10/pile@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/10/pile.jpg" alt="Prevent CloudFormation Change Sets from piling up" title="Prevent CloudFormation Change Sets from piling up"></picture></p><p>Usually, I’m using the AWS CLI to deploy CloudFormation stacks.</p><figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">aws cloudformation package <span class="attr">--template-file</span> example<span class="selector-class">.yml</span> <span class="attr">--output-template-file</span> output<span class="selector-class">.yml</span> <span class="attr">--s3-bucket</span> example</span><br><span class="line">aws cloudformation deploy <span class="attr">--template-file</span> output<span class="selector-class">.yml</span> <span class="attr">--stack-name</span> example <span class="attr">--no-fail-on-empty-changeset</span></span><br></pre></td></tr></table></figure><p>The <code>aws cloudformation package</code> command packages the template <code>example.yml</code> and uploads dependencies like nested stack templates to S3.</p><p>The <code>aws cloudformation deploy</code> command creates or updates the stack <code>example</code>. The option <code>--no-fail-on-empty-changeset</code> makes sure the command does not throw an error in case the template has not changed, which is very likely when using a deployment pipeline.</p><p>It is essential to know that the pipeline creates a change set whenever executing <code>aws cloudformation deploy</code>. Deploying a stack without making any changes to the template or the parameters leads to a failed change set: <code>The submitted information didn&#39;t contain changes. Submit different information to create a change set.</code></p><p>Unfortunately, the <code>aws cloudformation deploy</code> command does not clean up those failed change sets. This leads to change sets piling up. And one day or another your deployment pipeline will fail with the following error message:</p><figure class="highlight stata"><table><tr><td class="code"><pre><span class="line"><span class="keyword">An</span> <span class="keyword">error</span> occurred (LimitExceededException) when calling the CreateChangeSet operation: ChangeSet limit exceeded <span class="keyword">for</span> <span class="keyword">stack</span> ...</span><br></pre></td></tr></table></figure><p>You have reached the maximum number of change sets for a stack.</p><p>How to fix that? It would be great if <code>aws cloudformation deploy</code> would clean up failed change sets automatically. Keep an eye on <a href="https://github.com/aws/aws-cli/issues/4534" target="_blank" rel="noopener">#4534</a> for progress on that.</p><p>For now, adding the following bash script to your deployment pipeline might help. The <code>cleanup()</code> function list all stacks starting with <code>prefix</code>, fetches the change sets for each stack, and deletes change sets in status <code>FAILED</code>.</p><figure class="highlight dsconfig"><table><tr><td class="code"><pre><span class="line"><span class="comment"># cleanup (region, prefix)</span></span><br><span class="line"><span class="string">cleanup</span> () &#123;</span><br><span class="line">  <span class="string">stacks</span>=$(<span class="string">aws</span> <span class="string">cloudformation</span> <span class="built_in">list-stacks</span> <span class="built_in">--stack-status-filter</span> <span class="string">CREATE_COMPLETE</span> <span class="string">UPDATE_COMPLETE</span> <span class="built_in">--query</span> <span class="string">&quot;StackSummaries[?starts_with(StackName, \`$2\`) == \`true\`].StackName&quot;</span> <span class="built_in">--output</span> <span class="string">text</span> <span class="built_in">--region</span> $<span class="string">1</span>)</span><br><span class="line">  <span class="string">for</span> <span class="string">stack</span> <span class="string">in</span> $<span class="string">stacks</span></span><br><span class="line">  <span class="string">do</span></span><br><span class="line">    <span class="string">echo</span> <span class="string">&quot;$&#123;stack&#125;: cleaning up change sets&quot;</span></span><br><span class="line">    <span class="string">changesets</span>=$(<span class="string">aws</span> <span class="string">cloudformation</span> <span class="built_in">list-change-sets</span> <span class="built_in">--stack-name</span> $<span class="string">stack</span> <span class="built_in">--query</span> <span class="string">&#x27;Summaries[?Status==`FAILED`].ChangeSetId&#x27;</span> <span class="built_in">--output</span> <span class="string">text</span> <span class="built_in">--region</span> $<span class="string">1</span>)</span><br><span class="line">    <span class="string">for</span> <span class="string">changeset</span> <span class="string">in</span> $<span class="string">changesets</span></span><br><span class="line">    <span class="string">do</span></span><br><span class="line">      <span class="string">echo</span> <span class="string">&quot;$&#123;stack&#125;: deleting change set $&#123;changeset&#125;&quot;</span></span><br><span class="line">      <span class="string">aws</span> <span class="string">cloudformation</span> <span class="built_in">delete-change-set</span> <span class="built_in">--change-set-name</span> $&#123;<span class="string">changeset</span>&#125; <span class="built_in">--region</span> $<span class="string">1</span></span><br><span class="line">    <span class="string">done</span></span><br><span class="line">  <span class="string">done</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The following command executes the function <code>cleanup()</code> for region <code>eu-west-1</code> and stack name prefix <code>example-</code>.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">cleanup</span> eu-west-<span class="number">1</span> example-</span><br></pre></td></tr></table></figure><p>In summary, <code>aws cloudformation deploy</code> leaves behind waste that you need to deal with.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Rapid CI/CD with CodeBuild to deploy PHP and Docker</title>
      <link>https://cloudonaut.io/rapid-ci-cd-with-codebuild-to-deplpy-php-and-docker/</link>
      <description>
        <![CDATA[<p>There are many options available when you are looking for ways to implement a deployment pipeline. You might have heard about Jenkins, Ci]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/codebuild/">codebuild</category>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <guid isPermaLink="true">https://cloudonaut.io/rapid-ci-cd-with-codebuild-to-deplpy-php-and-docker/</guid>
      <pubDate>Tue, 01 Oct 2019 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>There are many options available when you are looking for ways to implement a deployment pipeline. You might have heard about Jenkins, CircleCi, BitBucket Pipelines, GitLab Pipelines, and many others.  AWS, on the other hand, offers services for CI&#x2F;CD itself: CodeBuild and CodePipeline.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/10/rapid@730w.webp 730w, /images/2019/10/rapid@730w2x.webp 1460w, /images/2019/10/rapid@610w.webp 610w, /images/2019/10/rapid@610w2x.webp 1220w, /images/2019/10/rapid@450w.webp 450w, /images/2019/10/rapid@450w2x.webp 900w, /images/2019/10/rapid@330w.webp 330w, /images/2019/10/rapid@330w2x.webp 660w, /images/2019/10/rapid@545w.webp 545w, /images/2019/10/rapid@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/10/rapid@730w.jpg 730w, /images/2019/10/rapid@730w2x.jpg 1460w, /images/2019/10/rapid@610w.jpg 610w, /images/2019/10/rapid@610w2x.jpg 1220w, /images/2019/10/rapid@450w.jpg 450w, /images/2019/10/rapid@450w2x.jpg 900w, /images/2019/10/rapid@330w.jpg 330w, /images/2019/10/rapid@330w2x.jpg 660w, /images/2019/10/rapid@545w.jpg 545w, /images/2019/10/rapid@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/10/rapid.jpg" alt="Rapid CI/CD with AWS" title="Rapid CI/CD with AWS"></picture></p><p><a href="/tag/codepipeline/">AWS CodePipeline</a> orchestrates deployment pipelines. Unfortunately, the learning curve is steep and the implementation is often complicated. Therefore, I recommend a more simple approach: use CodeBuild. In general, CodeBuild feels like CircleCI or GitLab Pipelines. However, CodePipeline offers tighter security controls and excellent integration into your AWS infrastructure.</p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/5-rapid-ci-cd-with-codebuild/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>The following figure demonstrates how a simple CI&#x2F;CD pipeline built with CodeBuild works.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/10/deployment-pipeline@730w.webp 730w, /images/2019/10/deployment-pipeline@730w2x.webp 1460w, /images/2019/10/deployment-pipeline@610w.webp 610w, /images/2019/10/deployment-pipeline@610w2x.webp 1220w, /images/2019/10/deployment-pipeline@450w.webp 450w, /images/2019/10/deployment-pipeline@450w2x.webp 900w, /images/2019/10/deployment-pipeline@330w.webp 330w, /images/2019/10/deployment-pipeline@330w2x.webp 660w, /images/2019/10/deployment-pipeline@545w.webp 545w, /images/2019/10/deployment-pipeline@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/10/deployment-pipeline@730w.png 730w, /images/2019/10/deployment-pipeline@730w2x.png 1460w, /images/2019/10/deployment-pipeline@610w.png 610w, /images/2019/10/deployment-pipeline@610w2x.png 1220w, /images/2019/10/deployment-pipeline@450w.png 450w, /images/2019/10/deployment-pipeline@450w2x.png 900w, /images/2019/10/deployment-pipeline@330w.png 330w, /images/2019/10/deployment-pipeline@330w2x.png 660w, /images/2019/10/deployment-pipeline@545w.png 545w, /images/2019/10/deployment-pipeline@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/10/deployment-pipeline.png" alt="Simple CI/CD with CodeBuild" title="Simple CI/CD with CodeBuild"></picture></p><p>AWS CodeBuild fetches code from CodeCommit and executes commands that you define in a file called <code>buildspec.yml</code> placed in the root directory of your project folder and repository. The following <code>buildspec.yml</code> changes the directory and runs the <code>npm i</code> command.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="number">0.2</span></span><br><span class="line"><span class="attr">phases:</span></span><br><span class="line">  <span class="attr">build:</span></span><br><span class="line">    <span class="attr">commands:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">cd</span> <span class="string">aws</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">npm</span> <span class="string">i</span></span><br></pre></td></tr></table></figure><p>The <code>version</code> attribute specifies the schema of the <code>buildspec.yml</code> and is defined by AWS. Multiple <code>phases</code> are supported and run in the following order:</p><ol><li><code>install</code></li><li><code>pre_build</code> (if <code>install</code> succeeded)</li><li><code>build</code> (if <code>pre_build</code> succeeded)</li><li><code>post_build</code> (if <code>build</code> failed or succeeded)</li></ol><p>Each phase runs a series of <code>commands</code> — defined by you — using the same instance of the default shell (we will use Bash) as the execution environment. Besides the environment variables that <a href="https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html" target="_blank" rel="noopener">CodeBuild provides out of the box</a>, you can also define custom environment variables to keep your <code>buildspec.yml</code> more flexible.</p><h2 id="Deployment-steps-defined-in-buildspec-yml"><a href="#Deployment-steps-defined-in-buildspec-yml" class="headerlink" title="Deployment steps defined in buildspec.yml"></a>Deployment steps defined in buildspec.yml</h2><p>The following steps are needed to deploy a dockerized PHP application to an infrastructure managed by CloudFormation based on <a href="https://github.com/cfn-modules" target="_blank" rel="noopener">cfn-modules</a>:</p><ol><li>Build the <code>nginx</code> Docker image</li><li>Build the <code>php-fpm</code> Docker image</li><li>Push both Docker images to ECR</li><li>Install cfn-modules by executing <code>npm i</code> in the <code>aws</code> folder</li><li>Package the CloudFormation template (<code>aws cloudformation package</code>)</li><li>Deploy the CloudFormation template with the new Docker images (<code>aws cloudformation deploy</code>)</li></ol><p>The following <code>buildspec.yml</code> installs Docker, executes the <code>docker login</code> command returned by <code>aws ecr get-login</code> and finally, runs the Bash script: <code>build.sh</code>. The Bash script is the place for your own customizations and helps us to keep the <code>buildspec.yml</code> clear.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="number">0.2</span></span><br><span class="line"><span class="attr">phases:</span></span><br><span class="line">  <span class="attr">install:</span></span><br><span class="line">    <span class="attr">runtime-versions:</span></span><br><span class="line">      <span class="attr">docker:</span> <span class="number">18</span></span><br><span class="line">  <span class="attr">pre_build:</span></span><br><span class="line">    <span class="attr">commands:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">$(aws</span> <span class="string">ecr</span> <span class="string">get-login</span> <span class="string">--no-include-email)</span></span><br><span class="line">  <span class="attr">build:</span></span><br><span class="line">    <span class="attr">commands:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">bash</span> <span class="string">build.sh</span></span><br></pre></td></tr></table></figure><p>Let’s look at <code>build.sh</code> to see how steps 1-6 are implemented. The <code>build.sh</code> script is invoked by the <code>buildspec.yml</code> with <code>bash build.sh</code>. Therefore, the script lives side-by-side with the <code>buildspec.yml</code> file. The first two lines state that this is a Bash script that ends execution if one of the commands fails.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="built_in">set</span> -e</span><br></pre></td></tr></table></figure><p>The next lines create the names and tags for the Docker images:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">NGINX_NAME_TAG=<span class="string">&quot;<span class="variable">$&#123;ACCOUNT_ID&#125;</span>.dkr.ecr.<span class="variable">$&#123;REGION&#125;</span>.amazonaws.com/\</span></span><br><span class="line"><span class="string"><span class="variable">$&#123;NGINX_REPO_NAME&#125;</span>:<span class="variable">$&#123;CODEBUILD_RESOLVED_SOURCE_VERSION&#125;</span>&quot;</span></span><br><span class="line">FPM_NAME_TAG=<span class="string">&quot;<span class="variable">$&#123;ACCOUNT_ID&#125;</span>.dkr.ecr.<span class="variable">$&#123;REGION&#125;</span>.amazonaws.com/\</span></span><br><span class="line"><span class="string"><span class="variable">$&#123;FPM_REPO_NAME&#125;</span>:<span class="variable">$&#123;CODEBUILD_RESOLVED_SOURCE_VERSION&#125;</span>&quot;</span></span><br></pre></td></tr></table></figure><p>Next, the script executes <code>docker build</code> to build the two Docker images, followed by <code>docker push</code> to upload the Docker images to ECR. Depending on the web application that you want to dockerize, you might want to add additional commands here (e.g., producing a JAR file, downloading dependencies, running unit tests or static analysis).</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker build -t <span class="string">&quot;<span class="variable">$&#123;NGINX_NAME_TAG&#125;</span>&quot;</span> \</span><br><span class="line">  -f docker/nginx/Dockerfile .</span><br><span class="line">docker build -t <span class="string">&quot;<span class="variable">$&#123;FPM_NAME_TAG&#125;</span>&quot;</span> \</span><br><span class="line">  -f docker/php-fpm/Dockerfile .</span><br><span class="line">docker push <span class="string">&quot;<span class="variable">$&#123;NGINX_NAME_TAG&#125;</span>&quot;</span></span><br><span class="line">docker push <span class="string">&quot;<span class="variable">$&#123;FPM_NAME_TAG&#125;</span>&quot;</span></span><br></pre></td></tr></table></figure><p>After that, we need to change to the <code>aws</code> directory, which contains the Infrastructure as Code, install the cfn-modules, and package the CloudFormation template:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> aws</span><br><span class="line">npm i</span><br><span class="line">aws cloudformation package --template-file template.yml \</span><br><span class="line">  --s3-bucket <span class="string">&quot;<span class="variable">$&#123;BUCKET_NAME&#125;</span>&quot;</span> --output-template-file .template.yml</span><br></pre></td></tr></table></figure><p>Finally, the CloudFormation template is deployed and the new Docker images are passed as parameters:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws cloudformation deploy --template-file .template.yml \</span><br><span class="line">  --stack-name php-basic --capabilities CAPABILITY_IAM \</span><br><span class="line">  --parameter-overrides <span class="string">&quot;AppImage=<span class="variable">$&#123;FPM_NAME_TAG&#125;</span>&quot;</span> \</span><br><span class="line">    <span class="string">&quot;ProxyImage=<span class="variable">$&#123;NGINX_NAME_TAG&#125;</span>&quot;</span></span><br></pre></td></tr></table></figure><h2 id="Creating-a-CodeBuild-project"><a href="#Creating-a-CodeBuild-project" class="headerlink" title="Creating a CodeBuild project"></a>Creating a CodeBuild project</h2><p>So far, we have transformed the manual deployment steps into a repeatable Shell script. Next, you’ll configure CodeBuild to connect the dots. A CodeBuild project fetches code from CodeCommit and executes the commands defined in the <code>buildspec.yml</code>. Whenever you push a new commit to your CodeCommit repository, CodeBuild will start automatically. The following snippet shows how to create a CodeBuild project with CloudFormation. Doing so allows you to use Infrastructure as Code to set up your deployment pipeline. A more detailed explanation will follow.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  [<span class="string">...</span>]</span><br><span class="line">  <span class="attr">Project:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodeBuild::Project&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Artifacts:</span></span><br><span class="line">        <span class="attr">Type:</span> <span class="string">NO_ARTIFACTS</span></span><br><span class="line">      <span class="attr">Environment:</span></span><br><span class="line">        <span class="attr">ComputeType:</span> <span class="string">BUILD_GENERAL1_SMALL</span></span><br><span class="line">        <span class="attr">EnvironmentVariables:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">ACCOUNT_ID</span></span><br><span class="line">          <span class="attr">Type:</span> <span class="string">PLAINTEXT</span></span><br><span class="line">          <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::AccountId&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">REGION</span></span><br><span class="line">          <span class="attr">Type:</span> <span class="string">PLAINTEXT</span></span><br><span class="line">          <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::Region&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">NGINX_REPO_NAME</span></span><br><span class="line">          <span class="attr">Type:</span> <span class="string">PLAINTEXT</span></span><br><span class="line">          <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">RepositoryNginx</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">FPM_REPO_NAME</span></span><br><span class="line">          <span class="attr">Type:</span> <span class="string">PLAINTEXT</span></span><br><span class="line">          <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">RepositoryFpm</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">BUCKET_NAME</span></span><br><span class="line">          <span class="attr">Type:</span> <span class="string">PLAINTEXT</span></span><br><span class="line">          <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">BucketArtifacts</span></span><br><span class="line">        <span class="attr">Image:</span> <span class="string">&#x27;aws/codebuild/standard:2.0&#x27;</span></span><br><span class="line">        <span class="attr">PrivilegedMode:</span> <span class="literal">true</span> <span class="comment"># required to build Docker images</span></span><br><span class="line">        <span class="attr">Type:</span> <span class="string">LINUX_CONTAINER</span>  </span><br><span class="line">      <span class="attr">LogsConfig:</span></span><br><span class="line">        <span class="attr">CloudWatchLogs:</span></span><br><span class="line">          <span class="attr">GroupName:</span> <span class="type">!Ref</span> <span class="string">ProjectLogGroup</span></span><br><span class="line">          <span class="attr">Status:</span> <span class="string">ENABLED</span></span><br><span class="line">      <span class="attr">ServiceRole:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;ProjectRole.Arn&#x27;</span></span><br><span class="line">      <span class="attr">Source:</span></span><br><span class="line">        <span class="attr">Location:</span> <span class="type">!Sub</span> <span class="string">&gt;</span></span><br><span class="line"><span class="string">          https://git-codecommit.$&#123;AWS::Region&#125;.amazonaws.com/</span></span><br><span class="line"><span class="string">          v1/repos/$&#123;CodeCommitRepositoryName&#125;</span></span><br><span class="line"><span class="string"></span>        <span class="attr">Type:</span> <span class="string">CODECOMMIT</span></span><br><span class="line">      <span class="attr">TimeoutInMinutes:</span> <span class="number">30</span></span><br></pre></td></tr></table></figure><p>The important pieces of the CloudFormation snippet are:</p><ul><li>The <code>Environment</code> property, which defines the custom environment variables that are used to keep the <code>buildspec.yml</code> file more flexible. Add more variables here if you need them in your modified <code>build.sh</code> script.</li><li>The <code>LogsConfig</code> property, which specifies the CloudWatch Logs location where logs are stored that you can use to debug problems with the pipeline.</li><li>The <code>ServiceRole</code> property, which points to the IAM role that is used when executing <code>buildspec.yml</code>. The role needs permissions to download the source code from CodeCommit, write to CloudWatch Logs, deploy the CloudFormation template, and push images to ECR. If you want to call other AWS services in your <code>build.sh</code> script, you likely need to add additional permissions here.</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to create a customized CloudWatch Dashboard with CloudFormation</title>
      <link>https://cloudonaut.io/how-to-create-a-customized-cloudwatch-dashboard-with-cloudformation/</link>
      <description>
        <![CDATA[<p>Which metrics are essential to evaluate the state of your cloud infrastructure? Probably a lot. A dashboard allows you to keep an eye on]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-create-a-customized-cloudwatch-dashboard-with-cloudformation/</guid>
      <pubDate>Wed, 25 Sep 2019 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Which metrics are essential to evaluate the state of your cloud infrastructure? Probably a lot. A dashboard allows you to keep an eye on all these metrics. For example, I like to monitor the following metrics for a typical 3-tier web application with the help of a CloudWatch dashboard:</p><ul><li>load balancer: client-side and server-side error rates</li><li>load balancer: target response latency and number of requests</li><li>compute: CPU and memory utilization</li><li>database: query and I&#x2F;O throughput</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/09/dashboard@730w.webp 730w, /images/2019/09/dashboard@730w2x.webp 1460w, /images/2019/09/dashboard@610w.webp 610w, /images/2019/09/dashboard@610w2x.webp 1220w, /images/2019/09/dashboard@450w.webp 450w, /images/2019/09/dashboard@450w2x.webp 900w, /images/2019/09/dashboard@330w.webp 330w, /images/2019/09/dashboard@330w2x.webp 660w, /images/2019/09/dashboard@545w.webp 545w, /images/2019/09/dashboard@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/09/dashboard@730w.jpg 730w, /images/2019/09/dashboard@730w2x.jpg 1460w, /images/2019/09/dashboard@610w.jpg 610w, /images/2019/09/dashboard@610w2x.jpg 1220w, /images/2019/09/dashboard@450w.jpg 450w, /images/2019/09/dashboard@450w2x.jpg 900w, /images/2019/09/dashboard@330w.jpg 330w, /images/2019/09/dashboard@330w2x.jpg 660w, /images/2019/09/dashboard@545w.jpg 545w, /images/2019/09/dashboard@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/09/dashboard.jpg" alt="How to create a customized CloudWatch Dashboard with CloudFormation?" title="How to create a customized CloudWatch Dashboard with CloudFormation?"></picture></p><p>But how to create a customized CloudWatch Dashboard with CloudFormation? Doing so is possible with a simple CloudFormation template. However, I choose to use a custom resource to be more flexible when generating the dashboard. The following steps are needed to create a CloudWatch dashboard with a custom resource.</p><ol><li>Create a CloudFormation template and add a Lambda-backed custom resource.</li><li>Write the code creating, updating, and deleting a CloudWatch dashboard.</li><li>Generate the JSON code describing the customized dashboard.</li></ol><p>You will learn how to do so in more detail next.</p><h2 id="Create-a-template-and-add-a-Lambda-backed-custom-resource"><a href="#Create-a-template-and-add-a-Lambda-backed-custom-resource" class="headerlink" title="Create a template and add a Lambda-backed custom resource"></a>Create a template and add a Lambda-backed custom resource</h2><p>First of all, create a CloudFormation template. Add the identifiers of the resource you want to monitor to the parameters section as shown in the following code snippet.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">DashboardName:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;The name for the dashboard.&#x27;</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">  <span class="attr">AlbFullName:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;The full-name of the ALB. (optional)&#x27;</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">    <span class="attr">Default:</span> <span class="string">&#x27;&#x27;</span></span><br><span class="line">  <span class="attr">RdsClusterName:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;The RDS Aurora Cluster name. (optional)&#x27;</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">    <span class="attr">Default:</span> <span class="string">&#x27;&#x27;</span></span><br><span class="line"><span class="comment"># ...</span></span><br></pre></td></tr></table></figure><p>Next, you need to add three resources to your template.</p><ol><li>A custom resource <code>Dashboard</code> to manage the CloudWatch dashboard.</li><li>A Lambda function <code>CustomResourceFunction</code> executing your source code for the custom resource.</li><li>An IAM role <code>CustomResourceRole</code> assumed by the Lambda function to write logs as well as creating, updating and deleting CloudWatch dashboards.</li></ol><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># ...</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Dashboard:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;Custom::Dashboard&#x27;</span></span><br><span class="line">    <span class="attr">Version:</span> <span class="string">&#x27;1.0&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">DashboardName:</span> <span class="type">!Ref</span> <span class="string">DashboardName</span></span><br><span class="line">      <span class="attr">AlbFullName:</span> <span class="type">!Ref</span> <span class="string">AlbFullName</span></span><br><span class="line">      <span class="attr">RdsClusterName:</span> <span class="type">!Ref</span> <span class="string">RdsClusterName</span></span><br><span class="line">      <span class="attr">ServiceToken:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CustomResourceFunction.Outputs.Arn&#x27;</span></span><br><span class="line">  <span class="attr">CustomResourceFunction:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Lambda::Function&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Code:</span> <span class="string">&#x27;./dashboard.js&#x27;</span></span><br><span class="line">      <span class="attr">Handler:</span> <span class="string">&#x27;dashboard.handler&#x27;</span></span><br><span class="line">      <span class="attr">MemorySize:</span> <span class="number">512</span></span><br><span class="line">      <span class="attr">Role:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CustomResourceRole.Arn&#x27;</span></span><br><span class="line">      <span class="attr">Runtime:</span> <span class="string">&#x27;nodejs8.10&#x27;</span></span><br><span class="line">      <span class="attr">Timeout:</span> <span class="number">30</span></span><br><span class="line">  <span class="attr">CustomResourceRole:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">Service:</span> <span class="string">&#x27;lambda.amazonaws.com&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">      <span class="attr">Policies:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">&#x27;customresource&#x27;</span></span><br><span class="line">        <span class="attr">PolicyDocument:</span></span><br><span class="line">          <span class="attr">Statement:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">            <span class="attr">Action:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogStream&#x27;</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">            <span class="attr">Resource:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;LogGroup.Arn&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">            <span class="attr">Action:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;cloudwatch:DeleteDashboards&#x27;</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;cloudwatch:PutDashboard&#x27;</span></span><br><span class="line">            <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br></pre></td></tr></table></figure><p>And now we’ll write some code.</p><h2 id="Implement-creating-updating-and-deleting-a-CloudWatch-dashboard"><a href="#Implement-creating-updating-and-deleting-a-CloudWatch-dashboard" class="headerlink" title="Implement creating, updating, and deleting a CloudWatch dashboard"></a>Implement creating, updating, and deleting a CloudWatch dashboard</h2><p>The following code snippet shows parts of <code>dashboard.js</code> including the handler which creates, updates, or deletes a CloudWatch dashboard. The built-in modules <code>aws-sdk</code> and <code>cfn-response</code> are used.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> cloudwatch = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">CloudWatch</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2010-08-01&#x27;</span>&#125;);</span><br><span class="line"><span class="keyword">const</span> response = <span class="built_in">require</span>(<span class="string">&#x27;cfn-response&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">handler</span> = <span class="function">(<span class="params">event, context, cb</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Executing function with event: <span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(event)&#125;</span>`</span>);</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">error</span> = (<span class="params">err</span>) =&gt; &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Error&#x27;</span>, err);</span><br><span class="line">    response.<span class="title function_">send</span>(event, context, response.<span class="property">FAILED</span>);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">if</span> (event.<span class="property">RequestType</span> === <span class="string">&#x27;Delete&#x27;</span>) &#123;</span><br><span class="line">    cloudwatch.<span class="title function_">deleteDashboards</span>(&#123;<span class="title class_">DashboardNames</span>: [event.<span class="property">ResourceProperties</span>.<span class="property">DashboardName</span>]&#125;, <span class="keyword">function</span>(<span class="params">err</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (err) &#123;</span><br><span class="line">        <span class="title function_">error</span>(err);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        response.<span class="title function_">send</span>(event, context, response.<span class="property">SUCCESS</span>, &#123;&#125;, event.<span class="property">ResourceProperties</span>.<span class="property">DashboardName</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (event.<span class="property">RequestType</span> === <span class="string">&#x27;Create&#x27;</span> || event.<span class="property">RequestType</span> === <span class="string">&#x27;Update&#x27;</span>) &#123;</span><br><span class="line">    cloudwatch.<span class="title function_">putDashboard</span>(&#123;</span><br><span class="line">      <span class="title class_">DashboardName</span>: event.<span class="property">ResourceProperties</span>.<span class="property">DashboardName</span>,</span><br><span class="line">      <span class="title class_">DashboardBody</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(<span class="title function_">generateDashboard</span>(event))</span><br><span class="line">    &#125;, <span class="keyword">function</span>(<span class="params">err</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (err) &#123;</span><br><span class="line">        <span class="title function_">error</span>(err);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Created/Updated dashboard <span class="subst">$&#123;event.ResourceProperties.DashboardName&#125;</span>.`</span>);</span><br><span class="line">        response.<span class="title function_">send</span>(event, context, response.<span class="property">SUCCESS</span>, &#123;&#125;, event.<span class="property">ResourceProperties</span>.<span class="property">DashboardName</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">error</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`unsupported request type: <span class="subst">$&#123;event.RequestType&#125;</span>`</span>));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>There is only one step missing: you need to add widgets to the dashboard.</p><h2 id="Generate-the-JSON-code-describing-the-customized-dashboard"><a href="#Generate-the-JSON-code-describing-the-customized-dashboard" class="headerlink" title="Generate the JSON code describing the customized dashboard"></a>Generate the JSON code describing the customized dashboard</h2><p>To do so, you need to generate a JSON file describing your dashboard in <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/CloudWatch-Dashboard-Body-Structure.html" target="_blank" rel="noopener">Dashboard Body Structure and Syntax</a>. I recommend to create your dashboard by clicking through the AWS Management Console first. Generating the dashboard’s JSON with the help of the <code>View/edit source</code> action gives you a good starting point.</p><p>The following code snippet shows the <code>generateDashboard</code> function, which generates the JSON defining a CloudWatch dashboard with two widgets:</p><ol><li>Widget ALB: total number of incoming requests as well as the target latency in 99 and 95 percentile</li><li>Widget RDS: maximum read and write IOPS as well as the serverless capacity of the database</li></ol><figure class="highlight php"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">generateDashboard</span>(<span class="params">event</span>) </span>&#123;</span><br><span class="line">  let widgets = [];</span><br><span class="line">  <span class="keyword">if</span> (event.ResourceProperties.AlbFullName) &#123;</span><br><span class="line">    widgets.<span class="title function_ invoke__">push</span>(&#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="string">&#x27;metric&#x27;</span>,</span><br><span class="line">      <span class="attr">properties</span>: &#123;</span><br><span class="line">        <span class="attr">metrics</span>: [</span><br><span class="line">          [<span class="string">&#x27;AWS/ApplicationELB&#x27;</span>, <span class="string">&#x27;TargetResponseTime&#x27;</span>, <span class="string">&#x27;LoadBalancer&#x27;</span>, event.ResourceProperties.AlbFullName, &#123;<span class="attr">stat</span>: <span class="string">&#x27;p99&#x27;</span>, <span class="attr">label</span>: <span class="string">&#x27;99 PCTL&#x27;</span>&#125;],</span><br><span class="line">          [<span class="string">&#x27;...&#x27;</span>, &#123;<span class="string">&#x27;stat&#x27;</span>: <span class="string">&#x27;p95&#x27;</span>, <span class="string">&#x27;label&#x27;</span>: <span class="string">&#x27;95 PCTL&#x27;</span> &#125;],</span><br><span class="line">          [&#123;<span class="attr">expression</span>: <span class="string">&#x27;SUM(METRICS(\&quot;req\&quot;))/60/5&#x27;</span>, <span class="attr">label</span>: <span class="string">&#x27;Requests&#x27;</span>, <span class="attr">id</span>: <span class="string">&#x27;reqs&#x27;</span>, <span class="attr">yAxis</span>: <span class="string">&#x27;right&#x27;</span>&#125;],</span><br><span class="line">          [<span class="string">&#x27;AWS/ApplicationELB&#x27;</span>, <span class="string">&#x27;RequestCount&#x27;</span>, <span class="string">&#x27;LoadBalancer&#x27;</span>, event.ResourceProperties.AlbFullName, &#123;<span class="attr">stat</span>: <span class="string">&#x27;Sum&#x27;</span>, <span class="attr">id</span>: <span class="string">&#x27;req&#x27;</span>, <span class="attr">visible</span>: <span class="literal">false</span> &#125;]</span><br><span class="line">        ],</span><br><span class="line">        <span class="attr">view</span>: <span class="string">&#x27;timeSeries&#x27;</span>,</span><br><span class="line">        <span class="attr">stacked</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="attr">region</span>: event.ResourceProperties.Region,</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&#x27;ALB Requests + Latency&#x27;</span>,</span><br><span class="line">        <span class="attr">period</span>: <span class="number">300</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (event.ResourceProperties.RdsClusterName) &#123;</span><br><span class="line">    widgets.<span class="title function_ invoke__">push</span>(&#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="string">&#x27;metric&#x27;</span>,</span><br><span class="line">      <span class="attr">properties</span>: &#123;</span><br><span class="line">         <span class="attr">metrics</span>: [</span><br><span class="line">          [<span class="string">&#x27;AWS/RDS&#x27;</span>, <span class="string">&#x27;VolumeReadIOPs&#x27;</span>, <span class="string">&#x27;DBClusterIdentifier&#x27;</span>, event.ResourceProperties.RdsClusterName, &#123;<span class="attr">stat</span>: <span class="string">&#x27;Maximum&#x27;</span>&#125;],</span><br><span class="line">          [<span class="string">&#x27;.&#x27;</span>, <span class="string">&#x27;VolumeWriteIOPs&#x27;</span>, <span class="string">&#x27;.&#x27;</span>, <span class="string">&#x27;.&#x27;</span>, &#123;<span class="string">&#x27;stat&#x27;</span>: <span class="string">&#x27;Maximum&#x27;</span>&#125;],</span><br><span class="line">          [<span class="string">&#x27;.&#x27;</span>, <span class="string">&#x27;ServerlessDatabaseCapacity&#x27;</span>, <span class="string">&#x27;.&#x27;</span>, <span class="string">&#x27;.&#x27;</span>, &#123;<span class="attr">stat</span>: <span class="string">&#x27;Maximum&#x27;</span>, <span class="attr">yAxis</span>: <span class="string">&#x27;right&#x27;</span>&#125;]</span><br><span class="line">         ],</span><br><span class="line">         <span class="attr">view</span>: <span class="string">&#x27;timeSeries&#x27;</span>,</span><br><span class="line">         <span class="attr">region</span>: event.ResourceProperties.Region,</span><br><span class="line">         <span class="attr">title</span>: <span class="string">&#x27;RDS IOPS + Capacity&#x27;</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// Add additional widgets here ...</span></span><br><span class="line"></span><br><span class="line">  let dashboard = &#123;widgets: []&#125;;</span><br><span class="line">  <span class="keyword">for</span> (let w in widgets) &#123;</span><br><span class="line">    let widget = widgets[w];</span><br><span class="line">    let x = (w % <span class="number">2</span>) * <span class="number">12</span>;</span><br><span class="line">    let y = Math.<span class="title function_ invoke__">floor</span>(w/<span class="number">2</span>);</span><br><span class="line">    widget.x = x;</span><br><span class="line">    widget.y = y;</span><br><span class="line">    widget.width = <span class="number">12</span>;</span><br><span class="line">    widget.height = <span class="number">6</span>;</span><br><span class="line">    dashboard.widgets.<span class="title function_ invoke__">push</span>(widget);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  console.<span class="title function_ invoke__">log</span>(`Generated <span class="attr">dashboard</span>: $&#123;JSON.<span class="title function_ invoke__">stringify</span>(dashboard)&#125;`);</span><br><span class="line">  <span class="keyword">return</span> dashboard;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>That’s it. The following screenshot illustrates the results.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/09/screenshot-cloudwatch-dashboard@730w.webp 730w, /images/2019/09/screenshot-cloudwatch-dashboard@730w2x.webp 1460w, /images/2019/09/screenshot-cloudwatch-dashboard@610w.webp 610w, /images/2019/09/screenshot-cloudwatch-dashboard@610w2x.webp 1220w, /images/2019/09/screenshot-cloudwatch-dashboard@450w.webp 450w, /images/2019/09/screenshot-cloudwatch-dashboard@450w2x.webp 900w, /images/2019/09/screenshot-cloudwatch-dashboard@330w.webp 330w, /images/2019/09/screenshot-cloudwatch-dashboard@330w2x.webp 660w, /images/2019/09/screenshot-cloudwatch-dashboard@545w.webp 545w, /images/2019/09/screenshot-cloudwatch-dashboard@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/09/screenshot-cloudwatch-dashboard@730w.png 730w, /images/2019/09/screenshot-cloudwatch-dashboard@730w2x.png 1460w, /images/2019/09/screenshot-cloudwatch-dashboard@610w.png 610w, /images/2019/09/screenshot-cloudwatch-dashboard@610w2x.png 1220w, /images/2019/09/screenshot-cloudwatch-dashboard@450w.png 450w, /images/2019/09/screenshot-cloudwatch-dashboard@450w2x.png 900w, /images/2019/09/screenshot-cloudwatch-dashboard@330w.png 330w, /images/2019/09/screenshot-cloudwatch-dashboard@330w2x.png 660w, /images/2019/09/screenshot-cloudwatch-dashboard@545w.png 545w, /images/2019/09/screenshot-cloudwatch-dashboard@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/09/screenshot-cloudwatch-dashboard.png" alt="CloudWatch Dashboard" title="CloudWatch Dashboard"></picture></p><p>Looking for a more advanced example? Check out the source code of our CloudFormation module<a href="https://github.com/cfn-modules/cloudwatch-dashboard" target="_blank" rel="noopener">cfn-modules&#x2F;cloudwatch-dashboard</a>.</p><p>Let’s knuckle down! 👩‍💻</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Review: Amazon Aurora Serverless - A cloud-native and production-ready relational database?</title>
      <link>https://cloudonaut.io/review-amazon-aurora-serverless/</link>
      <description>
        <![CDATA[<p>It was never easier to scale your compute layer. EC2 Auto Scaling, Fargate, and Lambda enable horizontal scaling. But how do you scale yo]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/review/">Review</category>
      <category domain="https://cloudonaut.io/tag/rds/">rds</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/review/">review</category>
      <guid isPermaLink="true">https://cloudonaut.io/review-amazon-aurora-serverless/</guid>
      <pubDate>Tue, 17 Sep 2019 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>It was never easier to scale your compute layer. EC2 Auto Scaling, Fargate, and Lambda enable horizontal scaling. But how do you scale your database? Use a NoSQL database like DynamoDB, one could say. But what if you don’t want to miss all the advantages of an SQL database? You should check out Amazon Aurora Serverless, a cloud-native SQL database.</p><blockquote><p>AWS announced the 2nd generation of Aurora Serverless in April 2022. Check out: <a href="/review-aurora-serverless-v2/">Review: Aurora Serverless v2</a>.</p></blockquote><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/08/review@730w.webp 730w, /images/2019/08/review@730w2x.webp 1460w, /images/2019/08/review@610w.webp 610w, /images/2019/08/review@610w2x.webp 1220w, /images/2019/08/review@450w.webp 450w, /images/2019/08/review@450w2x.webp 900w, /images/2019/08/review@330w.webp 330w, /images/2019/08/review@330w2x.webp 660w, /images/2019/08/review@545w.webp 545w, /images/2019/08/review@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/08/review@730w.jpg 730w, /images/2019/08/review@730w2x.jpg 1460w, /images/2019/08/review@610w.jpg 610w, /images/2019/08/review@610w2x.jpg 1220w, /images/2019/08/review@450w.jpg 450w, /images/2019/08/review@450w2x.jpg 900w, /images/2019/08/review@330w.jpg 330w, /images/2019/08/review@330w2x.jpg 660w, /images/2019/08/review@545w.jpg 545w, /images/2019/08/review@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/08/review.jpg" alt="Review" title="Review"></picture></p><p>The MySQL-compatible edition is generally available since August 2018. Recently, AWS announced a PostgreSQL-compatible edition as well. It’s high time to put the service through its paces.</p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/4-review-amazon-aurora-serverless/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="Introducing-Amazon-Aurora-Serverless"><a href="#Introducing-Amazon-Aurora-Serverless" class="headerlink" title="Introducing Amazon Aurora Serverless"></a>Introducing Amazon Aurora Serverless</h2><p>As shown in the following figure, an Aurora Serverless database cluster consists of two layers:</p><ul><li>The <strong>storage layer</strong> replicates the data among multiple availability zones by default. On top of that, the storage capacity scales from 10 GiB to 64 TiB. Also, the I&#x2F;O throughput of the storage layer scales nearly endlessly.</li><li>The <strong>compute layer</strong> scales vertically from 1 ACU (approximately 1 vCPU and 2 GiB memory) to 256 ACU (approximately 64 vCPU and 488 GiB memory) and adapts to the current workload automatically. It is even possible to pause the whole compute layer.</li></ul><p>The scalable compute layer is a game-changer for unpredictable workloads or scenarios where there are no queries to the database for significant timespans.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/09/introducing-aurora-serverless@730w.webp 730w, /images/2019/09/introducing-aurora-serverless@730w2x.webp 1460w, /images/2019/09/introducing-aurora-serverless@610w.webp 610w, /images/2019/09/introducing-aurora-serverless@610w2x.webp 1220w, /images/2019/09/introducing-aurora-serverless@450w.webp 450w, /images/2019/09/introducing-aurora-serverless@450w2x.webp 900w, /images/2019/09/introducing-aurora-serverless@330w.webp 330w, /images/2019/09/introducing-aurora-serverless@330w2x.webp 660w, /images/2019/09/introducing-aurora-serverless@545w.webp 545w, /images/2019/09/introducing-aurora-serverless@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/09/introducing-aurora-serverless@730w.png 730w, /images/2019/09/introducing-aurora-serverless@730w2x.png 1460w, /images/2019/09/introducing-aurora-serverless@610w.png 610w, /images/2019/09/introducing-aurora-serverless@610w2x.png 1220w, /images/2019/09/introducing-aurora-serverless@450w.png 450w, /images/2019/09/introducing-aurora-serverless@450w2x.png 900w, /images/2019/09/introducing-aurora-serverless@330w.png 330w, /images/2019/09/introducing-aurora-serverless@330w2x.png 660w, /images/2019/09/introducing-aurora-serverless@545w.png 545w, /images/2019/09/introducing-aurora-serverless@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/09/introducing-aurora-serverless.png" alt="Introducing Amazon Aurora Serverless" title="Introducing Amazon Aurora Serverless"></picture></p><p>Want to learn more about the storage layer? Werner Vogel explains the <a href="https://www.allthingsdistributed.com/2019/03/amazon-aurora-design-cloud-native-relational-database.html" target="_blank" rel="noopener">design of Amazon Aurora</a>. For more information about Aurora Serverless, I do recommend the re:Invent session <a href="https://www.youtube.com/watch?v=4DqNk7ZTYjA" target="_blank" rel="noopener">Aurora Serverless: Scalable, Cost-Effective Application Deployment (DAT336)</a>.</p><h2 id="Pausing-the-database"><a href="#Pausing-the-database" class="headerlink" title="Pausing the database"></a>Pausing the database</h2><p>Obviously, the best way to reduce costs in a pay-per-use pricing model is to tear down unused resources. Aurora Serverless supports to pause the compute layer when there are no database queries for five minutes.</p><p>To repeat that in other words: the compute layer of your database scales to 0. You will not pay for the compute layer when paused. Of course, charges apply for the storage layer, which stores your data even when the compute layer is paused.</p><p>Establishing a database connection to modify or query data will resume a paused database cluster automatically. There’s only one catch to this: resuming the database adds latency to the first incoming requests. In my experience, it takes approximately 15 seconds to resume a stopped database. Peter measured the activation time more accurately and published the results in his blog post <a href="https://www.percona.com/blog/2019/01/09/amazon-aurora-serverless-the-sleeping-beauty/" target="_blank" rel="noopener">Amazon Aurora Serverless – The Sleeping Beauty</a>.</p><p>So there is a trade-off between cost reduction and long latencies for some requests. We are using Aurora Serverless for a web-based accounting solution that is only used by a few people a few times per day. High costs savings justify waiting a few seconds for the database to resume in this scenario. Important note: pausing the database is optional. It is up to you to enable or disable auto-pausing.</p><p>Speaking about cost savings, let’s have a look at the pricing for Aurora Serverless.</p><h2 id="Pricing"><a href="#Pricing" class="headerlink" title="Pricing"></a>Pricing</h2><p>The following table compares the costs for the compute layer between Aurora Serverless and Aurora (with provisioned instances). The calculation does not include storage costs and is based on prices for <code>us-east-1</code>. Assuming that Aurora Serverless is running 24&#x2F;7 without scaling the compute layer (ACU) you pay a 50% surcharge on the price for the compute layer compared to using Aurora.</p><table class="table table-striped table-responsive"><thead><tr><th>ACU</th><th>Aurora Serverless</th><th>Aurora</th><th>Surcharge</th></tr></thead><tbody><tr><td>1</td><td>$0.06 per hour</td><td>$0.041 per hour</td><td>46.34 %</td></tr><tr><td>32</td><td>$1.92 per hour</td><td>$1.160 per hour</td><td>65.52 %</td></tr><tr><td>256</td><td>$15.36 per hour</td><td>$9.280 per hour</td><td>65.52 %</td></tr></tbody></table><p>In my opinion, an additional charge of this amount for the service offered is not justified. However, you can still make significant cost savings by using Aurora Serverless.</p><p>For example, think about an application that is only used during working days from 9 to 5. Outside office hours, there is no or almost no load on the system. Switching from Aurora to Auora Serverless will result in costs savings of up to 65 % for the compute layer, as shown in the following calculation:</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">Aurora</span>:            <span class="number">24</span> hours * <span class="number">30</span> days * $<span class="number">0</span>.<span class="number">041</span> = $<span class="number">29</span>.<span class="number">52</span> per month</span><br><span class="line"><span class="attribute">Aurora</span> Serverless:  <span class="number">8</span> hours * <span class="number">21</span> days * $<span class="number">0</span>.<span class="number">060</span> = $<span class="number">10</span>.<span class="number">08</span></span><br></pre></td></tr></table></figure><p>So, depending on your workload costs for the compute layer of your database will either increase or decrease significantly.</p><h2 id="Single-AZ-with-multi-AZ-failover"><a href="#Single-AZ-with-multi-AZ-failover" class="headerlink" title="Single-AZ with multi-AZ failover"></a>Single-AZ with multi-AZ failover</h2><p>Reliability is critical for a database. As mentioned before, the storage layer is distributed among multiple availability zones by default. To be more precise: data is replicated six times among three availability zones. That’s impressive.</p><p>However, Aurora Serverless operates in single-AZ with multi-AZ failover mode. The compute layer consists of a single instance. In case of an outage, Aurora Serverless will spin up a new instance in another availability zone. As the storage layer is replicated among multiple availability zones, data is not at risk. But it will take some time until the new instance can serve requests.</p><p>This brings us to a big limitation of Aurora Serverless: AWS does not guarantee an SLA for Aurora Serverless. The <a href="https://aws.amazon.com/rds/aurora/sla/" target="_blank" rel="noopener">Aurora SLA</a> applies to clusters with database instances running in at least two availability zones only, a requirement that Aurora Serverless does not fulfill.</p><h2 id="Secure-by-default"><a href="#Secure-by-default" class="headerlink" title="Secure by default"></a>Secure by default</h2><p>We often complain about clumsy security defaults, see <a href="/ec2-instance-connect-is-an-insecure-default/">EC2 Instance Connect is an insecure default!</a>, for example.</p><p>But Aurora Serverless deserves special praise at this point as it is introducing two crucial security defaults for your database:</p><ol><li>There is no way to configure a public IP address for Aurora Serverless. So there is no way an attacker can access your database from the Internet.</li><li>Aurora Serverless enforces encryption-at-rest based with KMS.</li></ol><p>This mitigates security risks caused by suboptimal configuration.</p><h2 id="RESTful-API-for-SQL-Data-API"><a href="#RESTful-API-for-SQL-Data-API" class="headerlink" title="RESTful API for SQL: Data API"></a>RESTful API for SQL: Data API</h2><p>It has always been a challenge to manage database connections when building Serverless applications even though the problems with the VPC-integration of AWS Lambda has been resolved lately<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup>.</p><p>Luckily, AWS announced the Data API for Aurora Serverless (MySQL-compatible edition only) at the beginning of this year. The Data API allows us to encapsulate SQL statements in RESTful API requests. A perfect fit for Serverless applications which make use of RESTful APIs a lot.</p><p>So far, the Data API is only available in the following regions: US East (N. Virginia), US East (Ohio), US West (Oregon), EU (Ireland), and Asia Pacific (Tokyo). Keep in mind that the Data API requires Aurora Serverless, Aurora (Classic) is not supported.</p><h2 id="Missing-features"><a href="#Missing-features" class="headerlink" title="Missing features"></a>Missing features</h2><p>Unfortunately, Aurora Serverless still misses some essential features from Aurora (Classic).</p><p>Most important, Aurora Serverless does not support replicas. Instead, only a single instance is running at a time. Therefore, Aurora Serverless cannot offer a hot standby (see <a href="#Single-AZ-with-multi-AZ-failover">Single-AZ with multi-AZ failover</a>). Additionally, read requests cannot be distributed among multiple read replicas, one of the critical benefits of Aurora (Classic).</p><p>And there are more missing features:</p><ul><li>It is not possible to load&#x2F;store data from&#x2F;to S3.</li><li>The point-in-time recovery called Backtrack is not available.</li><li>Does not support multi-master clusters.</li><li>RDS Performance Insights are not available.</li></ul><p>And there are more missing features compared to Aurora (Classic). See <a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless.html#aurora-serverless.limitations" target="_blank" rel="noopener">Limitations of Aurora Serverless</a> for a complete list.</p><p>After discussing some missing features, I’d like to close with an evaluation of the service maturity.</p><h2 id="Service-Maturity-Table"><a href="#Service-Maturity-Table" class="headerlink" title="Service Maturity Table"></a>Service Maturity Table</h2><p>The following table summarizes the maturity of the service:</p><table class="table table-striped table-responsive"><thead><tr><th>Criteria</th><th>Support</th><th>Score</th></tr></thead><tbody><tr><td>Feature Completeness</td><td>⚠️</td><td>2</td></tr><tr><td>Tags (Grouping + Billing)</td><td>✅</td><td>10</td></tr><tr><td>CloudFormation + Terraform support</td><td>✅</td><td>10</td></tr><tr><td>Emits CloudWatch Events</td><td>✅</td><td>10</td></tr><tr><td>IAM granularity</td><td>✅<sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup><sup><a href="#fn:3" id="fnref:3" class="footnote-ref">3</a></sup></td><td>9</td></tr><tr><td>Integrated with AWS Config</td><td>❌<sup><a href="#fn:4" id="fnref:4" class="footnote-ref">4</a></sup></td><td>0</td></tr><tr><td>Auditing via AWS CloudTrail</td><td>✅</td><td>10</td></tr><tr><td>Available in all commercial regions</td><td>⚠️<sup><a href="#fn:5" id="fnref:5" class="footnote-ref">5</a></sup></td><td>8</td></tr><tr><td>SLA</td><td>❌<sup><a href="#fn:6" id="fnref:6" class="footnote-ref">6</a></sup></td><td>0</td></tr><tr><td><strong>Total Maturity Score (0-10)</strong></td><td>⚠️</td><td><strong>6.6</strong></td></tr></tbody></table><p>Our maturity score for Amazon Aurora Serverless is 6.6 on a scale from 0 to 10.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Significant cost savings are possible for unsteady workloads with Aurora Serverless. However, keep in mind that cost may also increase when switching steady workloads from Aurora to Aurora Serverless.</p><p>For now, Aurora Serverless does not support replication. Even though the storage layer is distributed over six nodes in three AZs, the compute layer uses only one instance. That’s a bummer. This even means that the Aurora SLA does not apply to Aurora Serverless. Also, Aurora Serverless does not support read replicas which limits the ability to scale.</p><p>No question, Aurora Serverless is an exciting service. However, you should weigh the advantages against the described limitations carefully. I’m looking forward to accompanying the further development of Aurora Serverless and am sure that many of the restrictions will soon be a thing of the past.</p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. https://aws.amazon.com/blogs/compute/announcing-improved-vpc-networking-for-aws-lambda-functions/ <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazonrds.html <a href="#fnref:2" class="footnote-backref">↩</a></p></li><li id="fn:3"><p>3. https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazonrdsdataapi.html <a href="#fnref:3" class="footnote-backref">↩</a></p></li><li id="fn:4"><p>4. https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html <a href="#fnref:4" class="footnote-backref">↩</a></p></li><li id="fn:5"><p>5. https://aws.amazon.com/rds/aurora/pricing/ <a href="#fnref:5" class="footnote-backref">↩</a></p></li><li id="fn:6"><p>6. https://aws.amazon.com/rds/aurora/sla/ <a href="#fnref:6" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Show your Tool: AWSInfo</title>
      <link>https://cloudonaut.io/show-your-tool-awsinfo/</link>
      <description>
        <![CDATA[<p>In this series, we present AWS tooling from the community, for the community. We talk directly with the tool makers. Who are they? What p]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/show-your-tool/">Show your Tool</category>
      <category domain="https://cloudonaut.io/tag/cli/">cli</category>
      <guid isPermaLink="true">https://cloudonaut.io/show-your-tool-awsinfo/</guid>
      <pubDate>Tue, 10 Sep 2019 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>In this series, we present AWS tooling from the community, for the community. We talk directly with the tool makers. Who are they? What problem does the tool solve? And what motivates them to contribute to open-source AWS tooling.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/09/show-your-tool-awsinfo@730w.webp 730w, /images/2019/09/show-your-tool-awsinfo@730w2x.webp 1460w, /images/2019/09/show-your-tool-awsinfo@610w.webp 610w, /images/2019/09/show-your-tool-awsinfo@610w2x.webp 1220w, /images/2019/09/show-your-tool-awsinfo@450w.webp 450w, /images/2019/09/show-your-tool-awsinfo@450w2x.webp 900w, /images/2019/09/show-your-tool-awsinfo@330w.webp 330w, /images/2019/09/show-your-tool-awsinfo@330w2x.webp 660w, /images/2019/09/show-your-tool-awsinfo@545w.webp 545w, /images/2019/09/show-your-tool-awsinfo@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/09/show-your-tool-awsinfo@730w.jpg 730w, /images/2019/09/show-your-tool-awsinfo@730w2x.jpg 1460w, /images/2019/09/show-your-tool-awsinfo@610w.jpg 610w, /images/2019/09/show-your-tool-awsinfo@610w2x.jpg 1220w, /images/2019/09/show-your-tool-awsinfo@450w.jpg 450w, /images/2019/09/show-your-tool-awsinfo@450w2x.jpg 900w, /images/2019/09/show-your-tool-awsinfo@330w.jpg 330w, /images/2019/09/show-your-tool-awsinfo@330w2x.jpg 660w, /images/2019/09/show-your-tool-awsinfo@545w.jpg 545w, /images/2019/09/show-your-tool-awsinfo@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/09/show-your-tool-awsinfo.jpg" alt="Show your Tool" title="Show your Tool"></picture></p><p>This time, we talk with Florian Motlik about <a href="https://theserverlessway.com/tools/awsinfo/" target="_blank" rel="noopener">AWSInfo</a>. You can find Florian on <a href="https://github.com/flomotlik" target="_blank" rel="noopener">GitHub</a> and <a href="https://x.com/flomotlik" target="_blank" rel="noopener">Twitter</a>.</p><p><em>cloudonaut: Tell us a little about yourself, your history with AWS, and your motivation to develop AWS tooling.</em><br>Florian Motlik: I always enjoyed building things that other people can use to become more productive. At Codeship, a CI&#x2F;CD Startup I co-founded, I’ve worked with many customers on how to improve their processes. This has shown me a lot of different ways people use or build tools or what’s missing for them.</p><p>When we started using AWS in 2012, it was clear that AWS is great at building scalable services, but isn’t that good at building tools for smaller or medium-sized teams (or small to medium-sized teams in large organizations) so I started to build tools. After working at Serverless Inc in 2016 and becoming a consultant in 2017, this became more obvious, so I began building out my own tool suite. Since then AWS has invested in CLI tooling a lot, so it’s getting much better.</p><p><em>cloudonaut: What problem does your tool solve?</em><br>Florian Motlik: With cloud services, you regularly need to look up some information, see if something is properly deployed or what the status of some resource is. Going through the AWS Management Console 1000s of times a day is slow though, and the AWS CLI is a great tool but who can remember all the specific command names and flags. I wanted something that kind of replicates the data from the AWS console in my terminal but makes it easy for me to filter out and find what I’m looking for. I need a good read-only client for my AWS Infrastructure.</p><p>It also needed to be easy to extend and easy to adapt for teams if they want to get different information than the tool provides by default. That’s why it is built on top of the AWS CLI and allows you to print the CLI commands AWSInfo is using under the hood so you can copy and adapt them to your needs easily.</p><p><em>cloudonaut: Who should use your tool? Who should not?</em><br>Florian Motlik: It’s pretty much a tool for anyone using AWS in any way. Whenever you want to look up the IP of your EC2 Instance, check if your DynamoDB has the proper indices defined or see what the policy attached to a specific role states exactly you want to use AWSInfo.</p><p><em>cloudonaut: Show us a short demo of your tool</em><br>Florian Motlik: Watch the <a href="https://vimeo.com/358865451" target="_blank" rel="noopener">Introduction to AWSInfo</a> on Vimeo.</p><div class="embed-responsive embed-responsive-16by9">  <iframe src="https://player.vimeo.com/video/358865451" class="embed-responsive-item"></iframe></div><p><em>cloudonaut: How can we use your tool?</em><br>Florian Motlik: It’s a bash based tool that builds on top of the AWS CLI, so you don’t need anything but bash and the AWS CLI. The easiest way to get the tool though is by installing it as a Docker container as all dependencies are properly resolved then.</p><p>The docs on <a href="https://theserverlessway.com/tools/awsinfo/" target="_blank" rel="noopener">my website</a> will give you a good introduction.</p><p><em>cloudonaut: Where can we find more information about your tool?</em><br>Florian Motlik: You will find detailed information at <a href="https://theserverlessway.com/tools/awsinfo/" target="_blank" rel="noopener">https://theserverlessway.com/tools/awsinfo/</a>.</p><p><em>cloudonaut: Are you aware of tools that solve a similar problem than yours? What’s the difference?</em><br>Florian Motlik: AWLess started with a similar idea as far as I know but has gone far beyond that. The AWS-Shell is another interesting tool but doesn’t seem to be in current development. Other than that and the AWS CLI directly I’m not aware of any other tooling with that specific goal in mind. </p><p>The main difference to other tools is the ability to easily see which commands are used under the hood and can be moved into your scripts or Makefile and be adapted. AWSInfo is trying hard not to be smart, which makes it much easier to adapt to your use cases. Once a tool becomes too smart, it’s hard to change or contribute to it for people not deeply involved in its development. I wanted to prevent that issue from the beginning.</p><p><em>cloudonaut: What’s the roadmap for your tool? Are you planning any significant releases?</em><br>Florian Motlik: I’m constantly implementing new commands for AWSInfo. Basically whenever I’m in a customer project and either miss support for a specific service or a command I’m adding it. The downside of the bash approach with AWSInfo is that each service or command needs to be implemented separately, but that happens pretty quickly. The upside is that the information shown in the output is hand-selected to reflect the most important details, similar to the information presented in the AWS Management Console.</p><p><em>cloudonaut: How do you stay motivated to maintain your open source project?</em><br>Florian Motlik: As I’m working with my tools daily, they make me more productive and allow me to provide better consulting to my customers. It’s also really motivating to see other people start using my tool and become much more productive and happy with how they interact with AWS when they don’t have to click around the AWS Management Console all day.</p><p><em>cloudonaut: Are you attending any conferences within the next few months where the community can get in touch?</em><br>Florian Motlik: I’ll be at a few local events over the next few months here in Vienna and maybe a few events in Germany as well. With two small kids travel to conferences is a bit rare currently, but if someone wants to get in touch send me an email to <a href="mailto:&#102;&#x6c;&#x6f;&#64;&#x74;&#104;&#x65;&#115;&#x65;&#x72;&#x76;&#101;&#114;&#108;&#x65;&#115;&#x73;&#119;&#97;&#x79;&#x2e;&#99;&#x6f;&#x6d;">flo@theserverlessway.com</a>, or on <a href="https://x.com/flomotlik" target="_blank" rel="noopener">Twitter</a>. And of course, raise an issue on <a href="https://github.com/flomotlik" target="_blank" rel="noopener">GitHub</a> on any of my open-source projects and check out <a href="https://theserverlessway.com/" target="_blank" rel="noopener">my website</a> with guides, blog posts, and other AWS related content.</p><p>–</p><p>What tools do you use to make your AWS work easier? <a href="mailto:&#x68;&#101;&#108;&#x6c;&#x6f;&#64;&#x63;&#x6c;&#x6f;&#x75;&#x64;&#111;&#110;&#x61;&#117;&#x74;&#x2e;&#x69;&#x6f;">Share your favorite tool with us</a>!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to sell pay per use SaaS to AWS customers in the AWS Marketplace</title>
      <link>https://cloudonaut.io/how-to-sell-pay-per-use-saas-to-aws-customers-in-the-aws-marketplace/</link>
      <description>
        <![CDATA[<p>AWS Marketplace allows you to sell software to AWS customers. The customer can either run the software on its own (using AMIs and optiona]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <category domain="https://cloudonaut.io/tag/aws-marketplace/">aws-marketplace</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-sell-pay-per-use-saas-to-aws-customers-in-the-aws-marketplace/</guid>
      <pubDate>Wed, 04 Sep 2019 17:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>AWS Marketplace allows you to sell software to AWS customers. The customer can either run the software on its own (using AMIs and optional CloudFormation), or you can offer the software as a service (SaaS). You can also offer containers and machine learning algorithms in the AWS Marketplace.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/09/marketplace@730w.webp 730w, /images/2019/09/marketplace@730w2x.webp 1460w, /images/2019/09/marketplace@610w.webp 610w, /images/2019/09/marketplace@610w2x.webp 1220w, /images/2019/09/marketplace@450w.webp 450w, /images/2019/09/marketplace@450w2x.webp 900w, /images/2019/09/marketplace@330w.webp 330w, /images/2019/09/marketplace@330w2x.webp 660w, /images/2019/09/marketplace@545w.webp 545w, /images/2019/09/marketplace@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/09/marketplace@730w.jpg 730w, /images/2019/09/marketplace@730w2x.jpg 1460w, /images/2019/09/marketplace@610w.jpg 610w, /images/2019/09/marketplace@610w2x.jpg 1220w, /images/2019/09/marketplace@450w.jpg 450w, /images/2019/09/marketplace@450w2x.jpg 900w, /images/2019/09/marketplace@330w.jpg 330w, /images/2019/09/marketplace@330w2x.jpg 660w, /images/2019/09/marketplace@545w.jpg 545w, /images/2019/09/marketplace@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/09/marketplace.jpg" alt="Marketplace" title="Marketplace"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/3-aws-marketplace-sass-subscriptions/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>The following table summaries your options to sell software in the AWS Marketplace.</p><table class="table table-striped table-responsive"><thead><tr><th>Software delivery</th><th>Pricing</th></tr></thead><tbody><tr><td>AMI</td><td>free<br>BYOL<br>hourly<br>monthly<br>annual<br>pay per use</td></tr><tr><td>AMI + CloudFormation</td><td>free<br>BYOL<br>hourly<br>monthly<br>annual<br>pay per use</td></tr><tr><td>AMIs + CloudFormation</td><td>same as above for first AMI only</td></tr><tr><td>SaaS</td><td>pay per use<br>contract</td></tr></tbody></table><p>We started selling <a href="https://marbot.io/" target="_blank" rel="noopener">our Slack based Incident Management for AWS</a> in the AWS Marketplace in June 2019. So far, we are thrilled with the results. We simplified purchasing marbot, which increased the number of customers. Now, marbot appears on the AWS bill of our customers. If you plan to start a side business in the AWS tooling space, we highly recommend to check out AWS Marketplace!</p><p>In this post, you will learn how to sell pay per use SaaS in the AWS Marketplace. I will show you the overall process and finish with code snippets to implement the process.</p><h2 id="Requirements"><a href="#Requirements" class="headerlink" title="Requirements"></a>Requirements</h2><ol><li>I suggest you create a new AWS account (aka <strong>Marketplace Account</strong>) to interact with the <a href="https://aws.amazon.com/marketplace/management/" target="_blank" rel="noopener">AWS Marketplace Management Portal</a></li><li><a href="https://docs.aws.amazon.com/marketplace/latest/userguide/seller-registration-process.html" target="_blank" rel="noopener">Register as an AWS Marketplace seller</a></li><li>Create your SaaS product in the <a href="https://aws.amazon.com/marketplace/management/products/" target="_blank" rel="noopener">AWS Marketplace Management Portal</a>. Creating a product is a chicken and egg problem. You need some information from AWS (like a <code>ProductCode</code> and an SNS topic), and AWS needs information from you. The process has several steps and comes with a testing stage.</li><li>Create a new AWS account (aka <strong>SaaS Account</strong>) where you run your software.</li></ol><h2 id="Flows"><a href="#Flows" class="headerlink" title="Flows"></a>Flows</h2><p>Once you have created your product, you have to implement three different flows:</p><ol><li>Deal with subscribes: validate the token you receive from AWS Marketplace via an HTTP POST request</li><li>Send metering data to AWS Marketplace</li><li>Deal with unsubscribes: received via an SNS topic</li></ol><p>The tricky part is to understand how various parties interact with each other.</p><ol><li>Customer</li><li>AWS</li><li>Your Marketplace Account</li><li>Your SaaS Account</li></ol><p>Let’s go through the flows in detail.</p><h3 id="Subscribe-flow"><a href="#Subscribe-flow" class="headerlink" title="Subscribe flow"></a>Subscribe flow</h3><p>The following figure demonstrates the interaction.</p><ol><li>An AWS customer clicks on the subscribe button in the AWS Marketplace</li><li>AWS Marketplace forwards the new customer to your website (using an HTTP POST request) with a token</li><li>Your app assumes an IAM role in the Marketplace Account and calls the <a href="https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_ResolveCustomer.html" target="_blank" rel="noopener">ResolveCustomer API</a> to validate the token.</li><li>AWS Marketplace publishes a <code>subscribe-success</code> message to the SNS topic</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/09/aws-marketplace-saas-flow-subscribe@730w.webp 730w, /images/2019/09/aws-marketplace-saas-flow-subscribe@730w2x.webp 1460w, /images/2019/09/aws-marketplace-saas-flow-subscribe@610w.webp 610w, /images/2019/09/aws-marketplace-saas-flow-subscribe@610w2x.webp 1220w, /images/2019/09/aws-marketplace-saas-flow-subscribe@450w.webp 450w, /images/2019/09/aws-marketplace-saas-flow-subscribe@450w2x.webp 900w, /images/2019/09/aws-marketplace-saas-flow-subscribe@330w.webp 330w, /images/2019/09/aws-marketplace-saas-flow-subscribe@330w2x.webp 660w, /images/2019/09/aws-marketplace-saas-flow-subscribe@545w.webp 545w, /images/2019/09/aws-marketplace-saas-flow-subscribe@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/09/aws-marketplace-saas-flow-subscribe@730w.png 730w, /images/2019/09/aws-marketplace-saas-flow-subscribe@730w2x.png 1460w, /images/2019/09/aws-marketplace-saas-flow-subscribe@610w.png 610w, /images/2019/09/aws-marketplace-saas-flow-subscribe@610w2x.png 1220w, /images/2019/09/aws-marketplace-saas-flow-subscribe@450w.png 450w, /images/2019/09/aws-marketplace-saas-flow-subscribe@450w2x.png 900w, /images/2019/09/aws-marketplace-saas-flow-subscribe@330w.png 330w, /images/2019/09/aws-marketplace-saas-flow-subscribe@330w2x.png 660w, /images/2019/09/aws-marketplace-saas-flow-subscribe@545w.png 545w, /images/2019/09/aws-marketplace-saas-flow-subscribe@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/09/aws-marketplace-saas-flow-subscribe.png" alt="AWS Marketplace SaaS Flow: Subscribe" title="AWS Marketplace SaaS Flow: Subscribe"></picture></p><p>Once you received the <code>subscribe-success</code> message, you can report usage for the customer.</p><h3 id="Report-Usage-flow"><a href="#Report-Usage-flow" class="headerlink" title="Report Usage flow"></a>Report Usage flow</h3><p>Every hour, you have to report the usage for every customer. For example, marbot is billed per active user. So we are sending the number of active users for every customer every hour.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/09/aws-marketplace-saas-flow-report-usage@730w.webp 730w, /images/2019/09/aws-marketplace-saas-flow-report-usage@730w2x.webp 1460w, /images/2019/09/aws-marketplace-saas-flow-report-usage@610w.webp 610w, /images/2019/09/aws-marketplace-saas-flow-report-usage@610w2x.webp 1220w, /images/2019/09/aws-marketplace-saas-flow-report-usage@450w.webp 450w, /images/2019/09/aws-marketplace-saas-flow-report-usage@450w2x.webp 900w, /images/2019/09/aws-marketplace-saas-flow-report-usage@330w.webp 330w, /images/2019/09/aws-marketplace-saas-flow-report-usage@330w2x.webp 660w, /images/2019/09/aws-marketplace-saas-flow-report-usage@545w.webp 545w, /images/2019/09/aws-marketplace-saas-flow-report-usage@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/09/aws-marketplace-saas-flow-report-usage@730w.png 730w, /images/2019/09/aws-marketplace-saas-flow-report-usage@730w2x.png 1460w, /images/2019/09/aws-marketplace-saas-flow-report-usage@610w.png 610w, /images/2019/09/aws-marketplace-saas-flow-report-usage@610w2x.png 1220w, /images/2019/09/aws-marketplace-saas-flow-report-usage@450w.png 450w, /images/2019/09/aws-marketplace-saas-flow-report-usage@450w2x.png 900w, /images/2019/09/aws-marketplace-saas-flow-report-usage@330w.png 330w, /images/2019/09/aws-marketplace-saas-flow-report-usage@330w2x.png 660w, /images/2019/09/aws-marketplace-saas-flow-report-usage@545w.png 545w, /images/2019/09/aws-marketplace-saas-flow-report-usage@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/09/aws-marketplace-saas-flow-report-usage.png" alt="AWS Marketplace SaaS Flow: Report Usage" title="AWS Marketplace SaaS Flow: Report Usage"></picture></p><p>To do so, your app assumes an IAM role in the Marketplace Account and calls the <a href="https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_BatchMeterUsage.html" target="_blank" rel="noopener">BatchMeterUsage API</a> to report usage.</p><h3 id="Unsubscribe-flow"><a href="#Unsubscribe-flow" class="headerlink" title="Unsubscribe flow"></a>Unsubscribe flow</h3><p>At any time, a customer can cancel the subscription.</p><ol><li>AWS Marketplace publishes an <code>unsubscribe-pending</code> message to the SNS topic.</li><li>One hour passes (you can still report usage for the customer to finish the started hour)</li><li>AWS Marketplace publishes an <code>unsubscribe-success</code> message to the SNS topic.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/09/aws-marketplace-saas-flow-unsubscribe@730w.webp 730w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@730w2x.webp 1460w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@610w.webp 610w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@610w2x.webp 1220w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@450w.webp 450w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@450w2x.webp 900w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@330w.webp 330w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@330w2x.webp 660w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@545w.webp 545w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/09/aws-marketplace-saas-flow-unsubscribe@730w.png 730w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@730w2x.png 1460w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@610w.png 610w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@610w2x.png 1220w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@450w.png 450w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@450w2x.png 900w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@330w.png 330w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@330w2x.png 660w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@545w.png 545w, /images/2019/09/aws-marketplace-saas-flow-unsubscribe@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/09/aws-marketplace-saas-flow-unsubscribe.png" alt="AWS Marketplace SaaS Flow: Unsubscribe" title="AWS Marketplace SaaS Flow: Unsubscribe"></picture></p><p>You cannot report usage anymore for the customer.</p><p>If you are interested in the implementation details, read on.</p><h2 id="Your-SaaS-Account-infrastructure"><a href="#Your-SaaS-Account-infrastructure" class="headerlink" title="Your SaaS Account infrastructure"></a>Your SaaS Account infrastructure</h2><p>The following CloudFormation template creates the SQS queue in your SaaS account that will receive the unsubscribe notification.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">MarketplaceTopicArn:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;SNS topic Arn created by AWS Marketplace.&#x27;</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">    <span class="attr">Default:</span> <span class="string">&#x27;arn:aws:sns:us-east-1:287250355862:aws-mp-subscription-notification-xxxxxxxxxxxxxxxxxxxxxxxxx&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">DeadLetterQueue:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::SQS::Queue&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">MessageRetentionPeriod:</span> <span class="number">1209600</span></span><br><span class="line">  <span class="attr">Queue:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::SQS::Queue&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">MessageRetentionPeriod:</span> <span class="number">1209600</span></span><br><span class="line">      <span class="attr">RedrivePolicy:</span></span><br><span class="line">        <span class="attr">deadLetterTargetArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;DeadLetterQueue.Arn&#x27;</span></span><br><span class="line">        <span class="attr">maxReceiveCount:</span> <span class="number">5</span></span><br><span class="line">      <span class="attr">VisibilityTimeout:</span> <span class="number">90</span></span><br><span class="line">  <span class="attr">QueuePolicy:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::SQS::QueuePolicy&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;sqs:SendMessage&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Queue.Arn&#x27;</span></span><br><span class="line">          <span class="attr">Condition:</span></span><br><span class="line">            <span class="attr">ArnEquals:</span></span><br><span class="line">              <span class="attr">&#x27;aws:SourceArn&#x27;:</span> <span class="type">!Ref</span> <span class="string">MarketplaceTopicArn</span></span><br><span class="line">      <span class="attr">Queues:</span> [<span class="type">!Ref</span> <span class="string">Queue</span>]</span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">QueueArn:</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Queue.Arn&#x27;</span></span><br></pre></td></tr></table></figure><p>You will learn to process the messages later.</p><h2 id="Your-Marketplace-Account-infrastructure"><a href="#Your-Marketplace-Account-infrastructure" class="headerlink" title="Your Marketplace Account infrastructure"></a>Your Marketplace Account infrastructure</h2><p>The following CloudFormation template creates the SNS subscription to connect the SNS topic created by AWS marketplace with the SQS queue in the SaaS Account. Besides that, an IAM role is created that you can use in the SaaS Account to validate registration tokens and report usage.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">MarketplaceTopicArn:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;SNS topic Arn created by AWS Marketplace.&#x27;</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">    <span class="attr">Default:</span> <span class="string">&#x27;arn:aws:sns:us-east-1:287250355862:aws-mp-subscription-notification-xxxxxxxxxxxxxxxxxxxxxxxxx&#x27;</span></span><br><span class="line">  <span class="attr">SaaSAccountId:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;AWS SaaS Account id using the role.&#x27;</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">  <span class="attr">SaaSAccountQueueArn:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;SQS queue ARN which should receive the AWS Marketplace notifications in the SaaS Account.&#x27;</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Subscription:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::SNS::Subscription&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Endpoint:</span> <span class="type">!Ref</span> <span class="string">SaaSAccountQueueArn</span></span><br><span class="line">      <span class="attr">Protocol:</span> <span class="string">sqs</span></span><br><span class="line">      <span class="attr">Region:</span> <span class="string">&#x27;us-east-1&#x27;</span></span><br><span class="line">      <span class="attr">TopicArn:</span> <span class="type">!Ref</span> <span class="string">MarketplaceTopicArn</span></span><br><span class="line">  <span class="attr">Role:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">AWS:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:iam::$&#123;SaaSAccountId&#125;:root&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">      <span class="attr">Policies:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">root</span></span><br><span class="line">        <span class="attr">PolicyDocument:</span></span><br><span class="line">          <span class="attr">Statement:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">            <span class="attr">Action:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;aws-marketplace:BatchMeterUsage&#x27;</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;aws-marketplace:ResolveCustomer&#x27;</span></span><br><span class="line">            <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">RoleArn:</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Role.Arn&#x27;</span></span><br></pre></td></tr></table></figure><p>Let’s look at the software side of things.</p><h2 id="Validate-token"><a href="#Validate-token" class="headerlink" title="Validate token"></a>Validate token</h2><p>Once a new customer subscribes to your SaaS product, AWS Marketplace forwards the new customer to your website using an HTTP POST request. The <code>x-amzn-marketplace-token</code> parameter needs to be validated on your side. The code assumes the IAM role in the SaaS Account first before the <a href="https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_ResolveCustomer.html" target="_blank" rel="noopener">ResolveCustomer API</a> call is made to validate the token.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> marketplacemetering = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">MarketplaceMetering</span>(&#123;</span><br><span class="line">  <span class="attr">apiVersion</span>: <span class="string">&#x27;2016-01-14&#x27;</span>,</span><br><span class="line">  <span class="attr">region</span>: <span class="string">&#x27;us-east-1&#x27;</span>,</span><br><span class="line">  <span class="attr">credentials</span>: <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">TemporaryCredentials</span>(&#123;</span><br><span class="line">    <span class="title class_">RoleArn</span>: <span class="string">&#x27;REPLACE_ME&#x27;</span> <span class="comment">// TODO replace with RoleArn output from CloudFormation stack</span></span><br><span class="line">  &#125;)</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">marketplacemetering.<span class="title function_">resolveCustomer</span>(&#123;</span><br><span class="line">  <span class="title class_">RegistrationToken</span>: <span class="string">&#x27;REPLACE_ME&#x27;</span> <span class="comment">// TODO replace with token from POST request</span></span><br><span class="line">&#125;, <span class="function">(<span class="params">err, data</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err.<span class="property">code</span> === <span class="string">&#x27;InvalidTokenException&#x27;</span>) &#123;</span><br><span class="line">      <span class="comment">// invalid token</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">     <span class="keyword">throw</span> err;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (data.<span class="property">ProductCode</span> === <span class="string">&#x27;REPLACE_ME&#x27;</span>) &#123; <span class="comment">// TODO replace with your product code from AWS Marketplace</span></span><br><span class="line">       <span class="comment">// success, continue with registration in your own system</span></span><br><span class="line">       <span class="comment">// attach the data.CustomerIdentifier with your own records for metering!</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// invalid product code</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>If the token is valid, you likely want to collect additional information from the new customer and add some entries to your database.</p><h2 id="Report-usage"><a href="#Report-usage" class="headerlink" title="Report usage"></a>Report usage</h2><p>Every hour, you have to report the usage of every customer. The code assumes the IAM role in the SaaS Account first before the <a href="https://docs.aws.amazon.com/marketplacemetering/latest/APIReference/API_BatchMeterUsage.html" target="_blank" rel="noopener">BatchMeterUsage API</a> call is made.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> marketplacemetering = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">MarketplaceMetering</span>(&#123;</span><br><span class="line">  <span class="attr">apiVersion</span>: <span class="string">&#x27;2016-01-14&#x27;</span>,</span><br><span class="line">  <span class="attr">region</span>: <span class="string">&#x27;us-east-1&#x27;</span>,</span><br><span class="line">  <span class="attr">credentials</span>: <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">TemporaryCredentials</span>(&#123;</span><br><span class="line">    <span class="title class_">RoleArn</span>: <span class="string">&#x27;REPLACE_ME&#x27;</span> <span class="comment">// TODO replace with RoleArn output from CloudFormation stack</span></span><br><span class="line">  &#125;)</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">marketplacemetering.<span class="title function_">batchMeterUsage</span>(&#123;</span><br><span class="line">  <span class="title class_">ProductCode</span>: <span class="string">&#x27;REPLACE_ME&#x27;</span>, <span class="comment">// TODO replace with your product code from AWS Marketplace</span></span><br><span class="line">  <span class="title class_">UsageRecords</span>: [&#123;</span><br><span class="line">    <span class="title class_">CustomerIdentifier</span>: <span class="string">&#x27;REPLACE_ME&#x27;</span>, <span class="comment">// TODO replace with the value that you received when validating the token</span></span><br><span class="line">    <span class="title class_">Dimension</span>: <span class="string">&#x27;REPLACE_ME&#x27;</span>, <span class="comment">// TODO replace with the dimension you charge for, e.g. users</span></span><br><span class="line">    <span class="title class_">Timestamp</span>: <span class="keyword">new</span> <span class="title class_">Date</span>(),</span><br><span class="line">    <span class="title class_">Quantity</span>: <span class="number">0</span> <span class="comment">// TODO replace with the usage</span></span><br><span class="line">  &#125;]</span><br><span class="line">&#125;, <span class="function">(<span class="params">err, data</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) &#123;</span><br><span class="line">    <span class="keyword">throw</span> err;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (data.<span class="property">Results</span>.<span class="property">length</span> !== <span class="number">1</span>) &#123;</span><br><span class="line">       <span class="comment">// expexted one result</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> result = data.<span class="property">Results</span>[<span class="number">0</span>];</span><br><span class="line">      <span class="keyword">if</span> (result.<span class="property">Status</span> === <span class="string">&#x27;Success&#x27;</span>) &#123;</span><br><span class="line">        <span class="comment">// success</span></span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (result.<span class="property">Status</span> === <span class="string">&#x27;CustomerNotSubscribed&#x27;</span>) &#123;</span><br><span class="line">        <span class="comment">// AWS Marketplace responded with CustomerNotSubscribed, the customer is not paying anymore, have you received the SQS message?!</span></span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (result.<span class="property">Status</span> === <span class="string">&#x27;DuplicateRecord&#x27;</span>) &#123;</span><br><span class="line">        <span class="comment">// not a big deal</span></span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">         <span class="comment">// unexpected status</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="Unsubscribe"><a href="#Unsubscribe" class="headerlink" title="Unsubscribe"></a>Unsubscribe</h2><p>Sad, but true! Sometimes a customer leaves us. AWS Marketplace will send a message to the SNS Topic that you connected to your SQS queue.</p><p>The message format is only <a href="https://docs.aws.amazon.com/marketplace/latest/userguide/subscription-notification.html" target="_blank" rel="noopener">partially documented</a>. We observed the following message schema:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;action&quot;</span><span class="punctuation">:</span> <span class="string">&quot;subscribe-success&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;customer-identifier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;xxxxxxxxxxx&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;product-code&quot;</span><span class="punctuation">:</span> <span class="string">&quot;xxxxxxxxxxxxxxxxxxxxxxxxx&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>The <code>action</code> can be one of the following:</p><ul><li><code>subscribe-success</code>: can be ignored for pay per use SaaS (only important if you integrate directly into API Gateway with keys and usage plans).</li><li><code>subscribe-fail</code>: never seen this. Not sure how to handle it.</li><li><code>unsubscribe-pending</code>: the customer is not paying anymore. You can still send usage data for the current hour.</li><li><code>unsubscribe-success</code>: the customer is not paying anymore. Remove or disable in your database.</li><li><code>entitlement-updated</code>: can be ignored for pay per use SaaS.</li></ul><h2 id="Critique"><a href="#Critique" class="headerlink" title="Critique"></a>Critique</h2><p>Besides our overall satisfaction with AWS Marketplace, we also experienced a few issues.</p><ol><li>AWS Marketplace operates from the US and is always paid in USD via credit card. This sometimes confuses our clients from Germany which pay their AWS bill in EUR via direct debit.</li><li>Sales taxes are a mystery. We are not sure if AWS Marketplace is comparable to Apple’s App Store, where Apple is the seller.</li><li>Notification messages are mostly undocumented. Shout out to <a href="https://x.com/hoegertn" target="_blank" rel="noopener">@hoegertn</a> for sharing his learnings with me!</li></ol><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>AWS Marketplace is an easy way to sell software to AWS customers. You have to implement three flows: new subscribers, report usage, and unsubscribes. It was never easier to monetize your AWS related business!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Avoid the 60 minutes timeout when using the AWS CLI with IAM roles</title>
      <link>https://cloudonaut.io/avoid-the-60-minutes-timeout-when-using-the-aws-cli-with-iam-roles/</link>
      <description>
        <![CDATA[<p>You can configure the AWS CLI to <a href="https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#using-aws-iam-roles" target="_bla]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cli/">cli</category>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <guid isPermaLink="true">https://cloudonaut.io/avoid-the-60-minutes-timeout-when-using-the-aws-cli-with-iam-roles/</guid>
      <pubDate>Tue, 27 Aug 2019 10:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>You can configure the AWS CLI to <a href="https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#using-aws-iam-roles" target="_blank" rel="noopener">assume an IAM role for you</a> in combination with MFA. If you are a power user of the CLI, you will realize that you have to enter your MFA token every 60 minutes, which is annoying.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/08/timeout@730w.webp 730w, /images/2019/08/timeout@730w2x.webp 1460w, /images/2019/08/timeout@610w.webp 610w, /images/2019/08/timeout@610w2x.webp 1220w, /images/2019/08/timeout@450w.webp 450w, /images/2019/08/timeout@450w2x.webp 900w, /images/2019/08/timeout@330w.webp 330w, /images/2019/08/timeout@330w2x.webp 660w, /images/2019/08/timeout@545w.webp 545w, /images/2019/08/timeout@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/08/timeout@730w.jpg 730w, /images/2019/08/timeout@730w2x.jpg 1460w, /images/2019/08/timeout@610w.jpg 610w, /images/2019/08/timeout@610w2x.jpg 1220w, /images/2019/08/timeout@450w.jpg 450w, /images/2019/08/timeout@450w2x.jpg 900w, /images/2019/08/timeout@330w.jpg 330w, /images/2019/08/timeout@330w2x.jpg 660w, /images/2019/08/timeout@545w.jpg 545w, /images/2019/08/timeout@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/08/timeout.jpg" alt="Avoid the 60 minute timeout when using the AWS CLI with IAM roles" title="Avoid the 60 minute timeout when using the AWS CLI with IAM roles"></picture></p><p>You will learn how to fix that in the following.</p><h2 id="AWS-account-setup"><a href="#AWS-account-setup" class="headerlink" title="AWS account setup"></a>AWS account setup</h2><p>Let’s assume we have three AWS accounts.</p><table class="table table-striped table-responsive"><thead><tr><th>Account id</th><th>Alias</th><th>Description</th></tr></thead><tbody><tr><td>000000000000</td><td>iam</td><td>Only IAM users are created in this account</td></tr><tr><td>111111111111</td><td>dev</td><td>Development workloads</td></tr><tr><td>222222222222</td><td>prod</td><td>Production workloads</td></tr></tbody></table><p>Besides that:</p><ol><li>In the <code>iam</code> account, an IAM user named <code>michael</code> is created. MFA is enabled, and an access key is generated.</li><li>In the <code>dev</code> and <code>prod</code> accounts, the following IAM role is created (CloudFormation template):</li></ol><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">AdminRole:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">AWS:</span> <span class="string">&#x27;arn:aws:iam::000000000000:root&#x27;</span> <span class="comment"># replace this with your iam account id</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">          <span class="attr">Condition:</span></span><br><span class="line">            <span class="attr">Bool:</span></span><br><span class="line">              <span class="attr">&#x27;aws:MultiFactorAuthPresent&#x27;:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">ManagedPolicyArns:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;arn:aws:iam::aws:policy/AdministratorAccess&#x27;</span></span><br><span class="line">      <span class="attr">MaxSessionDuration:</span> <span class="number">43200</span> <span class="comment"># 12 hours in seconds</span></span><br><span class="line">      <span class="attr">RoleName:</span> <span class="string">Admin</span></span><br></pre></td></tr></table></figure><blockquote><p>Ensure that you set the <code>MaxSessionDuration</code> property! The default is 60 minutes.</p></blockquote><h2 id="Configuring-the-AWS-CLI"><a href="#Configuring-the-AWS-CLI" class="headerlink" title="Configuring the AWS CLI"></a>Configuring the AWS CLI</h2><p>The AWS CLI stores the configuration in <code>~/.aws/credentials</code> (or <code>%UserProfile%\.aws\credentials</code> if you are using Windows).</p><p>First of all, configure the access key from the <code>michael</code> IAM user using the <code>aws_access_key_id</code> and <code>aws_secret_access_key</code> configuration values. The value between the square brackets is called the profile name.</p><figure class="highlight markdown"><table><tr><td class="code"><pre><span class="line">[iam]</span><br><span class="line">aws<span class="emphasis">_access_</span>key<span class="emphasis">_id = AKIA<span class="strong">****</span><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span></span></span><br><span class="line"><span class="emphasis">aws_</span>secret<span class="emphasis">_access_</span>key = <span class="strong">****</span><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span><span class="strong">****</span></span><br></pre></td></tr></table></figure><p>After that, configure the IAM roles you want to assume. The following configuration values are used:</p><table class="table table-striped table-responsive"><thead><tr><th>Configuration value</th><th>Description</th></tr></thead><tbody><tr><td>role_arn</td><td>ARN of the role you want to assume</td></tr><tr><td>source_profile</td><td>Reference the profile of the IAM user</td></tr><tr><td>mfa_serial</td><td>ARN of the virtual MFA device or the serial number for a hardware device</td></tr><tr><td>duration_seconds</td><td>The expiry of the credentials returned by the assume role call</td></tr></tbody></table><blockquote><p>Ensure that you set the <code>duration_seconds</code> property! The default is 60 minutes.</p></blockquote><p>Add the following profiles to the <code>credentials</code> file. </p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[dev]</span></span><br><span class="line"><span class="attr">role_arn</span> = arn:aws:iam::<span class="number">111111111111</span>:role/Admin</span><br><span class="line"><span class="attr">source_profile</span> = iam</span><br><span class="line"><span class="attr">mfa_serial</span> = arn:aws:iam::<span class="number">000000000000</span>:mfa/michael</span><br><span class="line"><span class="attr">duration_seconds</span> = <span class="number">43200</span></span><br><span class="line"></span><br><span class="line"><span class="section">[prod]</span></span><br><span class="line"><span class="attr">role_arn</span> = arn:aws:iam::<span class="number">222222222222</span>:role/Admin</span><br><span class="line"><span class="attr">source_profile</span> = iam</span><br><span class="line"><span class="attr">mfa_serial</span> = arn:aws:iam::<span class="number">000000000000</span>:mfa/michael</span><br><span class="line"><span class="attr">duration_seconds</span> = <span class="number">43200</span></span><br></pre></td></tr></table></figure><h2 id="Using-the-profiles"><a href="#Using-the-profiles" class="headerlink" title="Using the profiles"></a>Using the profiles</h2><p>The <code>--profile</code> parameter lets you specify the profile you want to use when working with the CLI.</p><figure class="highlight jboss-cli"><table><tr><td class="code"><pre><span class="line">aws <span class="params">--profile</span> dev s3 <span class="keyword">ls</span></span><br><span class="line">aws <span class="params">--profile</span> prod s3 <span class="keyword">ls</span></span><br></pre></td></tr></table></figure><p>The AWS CLI will ask you for your MFA token the first time you make a call.</p><p>You can also set the <code>AWS_PROFILE</code> environment variable to avoid typing <code>--profile ...</code> all the time.</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> <span class="attribute">AWS_PROFILE</span>=dev</span><br><span class="line">aws s3 ls</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>To avoid frequent re-enter of the MFA token when using the AWS CLI, you have to adjust the <code>MaxSessionDuration</code> of the IAM role and the <code>duration_seconds</code> configuration value of the AWS CLI.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>We have a podcast!</title>
      <link>https://cloudonaut.io/we-have-a-podcast/</link>
      <description>
        <![CDATA[<p>We launched the <a href="https://podcast.cloudonaut.io/" target="_blank" rel="noopener">cloudonaut Podcast</a>! Every other week, Andreas]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/we-have-a-podcast/</guid>
      <pubDate>Wed, 21 Aug 2019 17:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>We launched the <a href="https://podcast.cloudonaut.io/" target="_blank" rel="noopener">cloudonaut Podcast</a>! Every other week, Andreas or I prepare the topic of the podcast. The topic is not known to the other one, which results in surprising conversations inspired by our daily work with AWS.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/08/podcast@730w.webp 730w, /images/2019/08/podcast@730w2x.webp 1460w, /images/2019/08/podcast@610w.webp 610w, /images/2019/08/podcast@610w2x.webp 1220w, /images/2019/08/podcast@450w.webp 450w, /images/2019/08/podcast@450w2x.webp 900w, /images/2019/08/podcast@330w.webp 330w, /images/2019/08/podcast@330w2x.webp 660w, /images/2019/08/podcast@545w.webp 545w, /images/2019/08/podcast@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/08/podcast@730w.jpg 730w, /images/2019/08/podcast@730w2x.jpg 1460w, /images/2019/08/podcast@610w.jpg 610w, /images/2019/08/podcast@610w2x.jpg 1220w, /images/2019/08/podcast@450w.jpg 450w, /images/2019/08/podcast@450w2x.jpg 900w, /images/2019/08/podcast@330w.jpg 330w, /images/2019/08/podcast@330w2x.jpg 660w, /images/2019/08/podcast@545w.jpg 545w, /images/2019/08/podcast@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/08/podcast.jpg" alt="We have a podcast!" title="We have a podcast!"></picture></p><p>Subscribe to the podcast:</p><ul><li><a href="https://itunes.apple.com/podcast/id1476505149" target="_blank" rel="noopener">Apple Podcasts</a></li><li><a href="https://open.spotify.com/show/1M44gYEuSZs3YX6zDUcVZs" target="_blank" rel="noopener">Spotify</a></li><li><a href="https://www.google.com/podcasts?feed=aHR0cHM6Ly9wb2RjYXN0LmNsb3Vkb25hdXQuaW8vZmVlZC9tcDM=" target="_blank" rel="noopener">Google Podcasts</a></li><li><a href="https://www.deezer.com/show/447462" target="_blank" rel="noopener">Deezer</a></li></ul><p>Alternatively, <a href="https://podcast.cloudonaut.io/" target="_blank" rel="noopener">listen on the web</a> or <a href="https://podcast.cloudonaut.io/feed/mp3" target="_blank" rel="noopener">subscribe to the raw feed</a>.</p><p>We are interested in your feedback! What’s the ideal length of a podcast for you? What topics are you interested in? Send us an email to <a href="mailto:&#x68;&#x65;&#x6c;&#108;&#x6f;&#64;&#x63;&#108;&#111;&#x75;&#100;&#x6f;&#x6e;&#97;&#117;&#116;&#46;&#105;&#111;">hello@cloudonaut.io</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>EC2 Instance Connect is an insecure default!</title>
      <link>https://cloudonaut.io/ec2-instance-connect-is-an-insecure-default/</link>
      <description>
        <![CDATA[<p>Two months before, Michael wrote about why <a href="/aws-ssm-is-a-trojan-horse-fix-it-now/">AWS SSM is a trojan horse</a>. Shortly after]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <guid isPermaLink="true">https://cloudonaut.io/ec2-instance-connect-is-an-insecure-default/</guid>
      <pubDate>Tue, 20 Aug 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Two months before, Michael wrote about why <a href="/aws-ssm-is-a-trojan-horse-fix-it-now/">AWS SSM is a trojan horse</a>. Shortly after that, AWS released <a href="https://aws.amazon.com/about-aws/whats-new/2019/06/introducing-amazon-ec2-instance-connect/" target="_blank" rel="noopener">EC2 Instance Connect</a>, which is even worse. <strong>If you use Amazon Linux 2 or Ubuntu, the chances are high that everyone in your AWS account can SSH into every EC2 instance!</strong></p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/08/rant@730w.webp 730w, /images/2019/08/rant@730w2x.webp 1460w, /images/2019/08/rant@610w.webp 610w, /images/2019/08/rant@610w2x.webp 1220w, /images/2019/08/rant@450w.webp 450w, /images/2019/08/rant@450w2x.webp 900w, /images/2019/08/rant@330w.webp 330w, /images/2019/08/rant@330w2x.webp 660w, /images/2019/08/rant@545w.webp 545w, /images/2019/08/rant@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/08/rant@730w.jpg 730w, /images/2019/08/rant@730w2x.jpg 1460w, /images/2019/08/rant@610w.jpg 610w, /images/2019/08/rant@610w2x.jpg 1220w, /images/2019/08/rant@450w.jpg 450w, /images/2019/08/rant@450w2x.jpg 900w, /images/2019/08/rant@330w.jpg 330w, /images/2019/08/rant@330w2x.jpg 660w, /images/2019/08/rant@545w.jpg 545w, /images/2019/08/rant@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/08/rant.jpg" alt="Rant" title="Rant"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/2-ec2-instance-connect-is-an-insecure-default/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="What-is-EC2-Instance-Connect"><a href="#What-is-EC2-Instance-Connect" class="headerlink" title="What is EC2 Instance Connect"></a>What is EC2 Instance Connect</h2><p>EC2 Instance Connect makes your public SSH key available on an EC2 instance using the <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html" target="_blank" rel="noopener">metadata service</a>. To push your public key to an EC2 instance, you use the <a href="https://docs.aws.amazon.com/ec2-instance-connect/latest/APIReference/API_SendSSHPublicKey.html" target="_blank" rel="noopener">SendSSHPublicKey API</a>.</p><p>The SSH daemon (<code>sshd</code>) needs to be configured to ask the metadata service for all public keys when someone connects via SSH. This configuration is enabled for you by default on Amazon Linux 2 and Ubuntu AMIs. We discovered this <a href="https://github.com/widdix/aws-ec2-ssh/issues/142" target="_blank" rel="noopener">breaking change in November 2018</a>.</p><h2 id="How-the-backdoor-works"><a href="#How-the-backdoor-works" class="headerlink" title="How the backdoor works"></a>How the backdoor works</h2><p>These three conditions must be met:</p><ol><li>Use the official Amazon Linux 2 or Ubuntu AMIs</li><li>Allow traffic on port 22. If you used SSH in the past to manage your instances, the port is already open.</li><li>Your IAM user or role needs permissions (action <code>ec2-instance-connect:SendSSHPublicKey</code>). Very likely, you have those permissions thanks to managed policies like <code>AdministratorAccess</code>or <code>PowerUserAccess</code>.</li></ol><p>You can now open an SSH connection to every EC2 instance in your AWS account.</p><h2 id="Protect-yourself"><a href="#Protect-yourself" class="headerlink" title="Protect yourself"></a>Protect yourself</h2><p>If you are not interested in using EC2 Instance Connect, you can do one of the following:</p><ul><li><a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-connect-uninstall.html" target="_blank" rel="noopener">Uninstall EC2 Instance Connect</a>.</li><li>Do not allow traffic on port 22.</li><li>Deny the action <code>ec2-instance-connect:SendSSHPublicKey</code> globally in your SCP.</li></ul><p>If you want to use EC2 Instance Connect:</p><ol><li>Keep your Security Groups as close as possible and use a VPN to connect to your VPC.</li><li>Grant the <code>ec2-instance-connect:SendSSHPublicKey</code> action carefully. According to <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazonec2instanceconnectservice.html" target="_blank" rel="noopener">IAM docs</a>, you only can use a condition to restrict the OS user. According to the <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-connect-set-up.html" target="_blank" rel="noopener">EC2 Instance Connect docs</a>, you can also restrict access to specific EC2 instances using resource-level permissions. Unfortunately, EC2 Instance tags don’t seem to be supported.</li></ol><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>AWS released a new feature called EC2 Instance Connect. It is enabled by default on Amazon Linux 2 and Ubuntu AMIs. We would appreciate if AWS would ask us - the customers - before they change defaults that increase our security risks. And we would also love to see up-to-date documentation on the IAM configuration. The information is contradictory at the moment, which is not satisfactory when security is at risk!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Verify SNS messages delivered via HTTP(S) in Node.js</title>
      <link>https://cloudonaut.io/verify-sns-messages-delivered-via-http-or-https-in-node-js/</link>
      <description>
        <![CDATA[<p>Are you implementing an HTTP&#x2F;HTTPS endpoint for SNS? If so, you should definetly verify the incoming messages. Otherwise, anyone on]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/sns/">sns</category>
      <guid isPermaLink="true">https://cloudonaut.io/verify-sns-messages-delivered-via-http-or-https-in-node-js/</guid>
      <pubDate>Wed, 14 Aug 2019 10:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Are you implementing an HTTP&#x2F;HTTPS endpoint for SNS? If so, you should definetly verify the incoming messages. Otherwise, anyone on the Internet can deliver messages to your HTTP&#x2F;HTTPS endpoint. Which is a security risk.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/08/code@730w.webp 730w, /images/2019/08/code@730w2x.webp 1460w, /images/2019/08/code@610w.webp 610w, /images/2019/08/code@610w2x.webp 1220w, /images/2019/08/code@450w.webp 450w, /images/2019/08/code@450w2x.webp 900w, /images/2019/08/code@330w.webp 330w, /images/2019/08/code@330w2x.webp 660w, /images/2019/08/code@545w.webp 545w, /images/2019/08/code@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/08/code@730w.jpg 730w, /images/2019/08/code@730w2x.jpg 1460w, /images/2019/08/code@610w.jpg 610w, /images/2019/08/code@610w2x.jpg 1220w, /images/2019/08/code@450w.jpg 450w, /images/2019/08/code@450w2x.jpg 900w, /images/2019/08/code@330w.jpg 330w, /images/2019/08/code@330w2x.jpg 660w, /images/2019/08/code@545w.jpg 545w, /images/2019/08/code@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/08/code.jpg" alt="Code" title="Code"></picture></p><p>How do you verify incoming messages? The <a href="https://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.verify.signature.html" target="_blank" rel="noopener">SNS documentation</a> answers this question:</p><blockquote><p>You should verify the authenticity of a notification, subscription confirmation, or unsubscribe confirmation message sent by Amazon SNS.</p></blockquote><p>In a nutshell, each SNS message contains a signature that we have to verify.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/08/verify-sns-message@730w.webp 730w, /images/2019/08/verify-sns-message@730w2x.webp 1460w, /images/2019/08/verify-sns-message@610w.webp 610w, /images/2019/08/verify-sns-message@610w2x.webp 1220w, /images/2019/08/verify-sns-message@450w.webp 450w, /images/2019/08/verify-sns-message@450w2x.webp 900w, /images/2019/08/verify-sns-message@330w.webp 330w, /images/2019/08/verify-sns-message@330w2x.webp 660w, /images/2019/08/verify-sns-message@545w.webp 545w, /images/2019/08/verify-sns-message@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/08/verify-sns-message@730w.png 730w, /images/2019/08/verify-sns-message@730w2x.png 1460w, /images/2019/08/verify-sns-message@610w.png 610w, /images/2019/08/verify-sns-message@610w2x.png 1220w, /images/2019/08/verify-sns-message@450w.png 450w, /images/2019/08/verify-sns-message@450w2x.png 900w, /images/2019/08/verify-sns-message@330w.png 330w, /images/2019/08/verify-sns-message@330w2x.png 660w, /images/2019/08/verify-sns-message@545w.png 545w, /images/2019/08/verify-sns-message@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/08/verify-sns-message.png" alt="Verify SNS message: check the signature" title="Verify SNS message: check the signature"></picture></p><p>The npm module <a href="https://www.npmjs.com/package/sns-validator" target="_blank" rel="noopener">sns-validator</a> does the job. Unfortunately, the module is old and lacks support for save caching and certificate download retries. Therefore, I decided to implement this on my own, which wasn’t as hard as expected. Let’s get started.</p><p>First, you need to install a few dependencies:</p><ul><li><code>request</code> and <code>requestretry</code> to perform HTTP(S) requests with retries</li><li><code>lru-cache</code> to safely cache certificates without running out of memory</li></ul><p>Install the modules with:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm i request requestretry lru-cache</span><br></pre></td></tr></table></figure><p>Create a new JavaScript file (e.g., <code>index.js</code>) and import the dependencies we need:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> crypto = <span class="built_in">require</span>(<span class="string">&#x27;crypto&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> requestretry = <span class="built_in">require</span>(<span class="string">&#x27;requestretry&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">LRU</span> = <span class="built_in">require</span>(<span class="string">&#x27;lru-cache&#x27;</span>);</span><br></pre></td></tr></table></figure><p>According to the <a href="https://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.verify.signature.html" target="_blank" rel="noopener">SNS documentation</a>, we have to use different fields of the message based on the <code>Type</code> of the message delivered by SNS. </p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">fieldsForSignature</span>(<span class="params">type</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (type === <span class="string">&#x27;SubscriptionConfirmation&#x27;</span> || type === <span class="string">&#x27;UnsubscribeConfirmation&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> [<span class="string">&#x27;Message&#x27;</span>, <span class="string">&#x27;MessageId&#x27;</span>, <span class="string">&#x27;SubscribeURL&#x27;</span>, <span class="string">&#x27;Timestamp&#x27;</span>, <span class="string">&#x27;Token&#x27;</span>, <span class="string">&#x27;TopicArn&#x27;</span>, <span class="string">&#x27;Type&#x27;</span>];</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (type === <span class="string">&#x27;Notification&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> [<span class="string">&#x27;Message&#x27;</span>, <span class="string">&#x27;MessageId&#x27;</span>, <span class="string">&#x27;Subject&#x27;</span>, <span class="string">&#x27;Timestamp&#x27;</span>, <span class="string">&#x27;TopicArn&#x27;</span>, <span class="string">&#x27;Type&#x27;</span>];</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> [];</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>We also have to come up with a way to download the certificate that we need to verify the signature. The certificate is attached to the message in the form of a URL. We have to download the certificate before we can verify the signature. Downloading things can fail for many reasons. Therefore, we retry failed download requests. To optimize for performance, we also want to cache downloaded certificates. Let’s look at the code.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">CERT_CACHE</span> = <span class="keyword">new</span> <span class="title function_">LRU</span>(&#123;<span class="attr">max</span>: <span class="number">5000</span>, <span class="attr">maxAge</span>: <span class="number">1000</span> * <span class="number">60</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fetchCert</span>(<span class="params">certUrl, cb</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> cachedCertificate = <span class="variable constant_">CERT_CACHE</span>.<span class="title function_">get</span>(certUrl);</span><br><span class="line">  <span class="keyword">if</span> (cachedCertificate !== <span class="literal">undefined</span>) &#123;</span><br><span class="line">    <span class="title function_">cb</span>(<span class="literal">null</span>, cachedCertificate);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">requestretry</span>(&#123;</span><br><span class="line">      <span class="attr">method</span>: <span class="string">&#x27;GET&#x27;</span>,</span><br><span class="line">      <span class="attr">url</span>: certUrl,</span><br><span class="line">      <span class="attr">maxAttempts</span>: <span class="number">3</span>,</span><br><span class="line">      <span class="attr">retryDelay</span>: <span class="number">100</span>,</span><br><span class="line">      <span class="attr">timeout</span>: <span class="number">3000</span></span><br><span class="line">    &#125;, <span class="function">(<span class="params">err, res, certificate</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (err) &#123;</span><br><span class="line">        <span class="title function_">cb</span>(err);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (res.<span class="property">statusCode</span> === <span class="number">200</span>) &#123;</span><br><span class="line">          <span class="variable constant_">CERT_CACHE</span>.<span class="title function_">set</span>(certUrl, certificate);</span><br><span class="line">          <span class="title function_">cb</span>(<span class="literal">null</span>, certificate);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          <span class="title function_">cb</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`expected 200 status code, received: <span class="subst">$&#123;res.statusCode&#125;</span>`</span>));</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The cache stores a maximum of 5000 certificates and the certificates expire after 1 minute from the cache.</p><p>Last but not least, we do some input validation:</p><ul><li>the fields <code>SignatureVersion</code>, <code>SigningCertURL</code>, <code>Type</code>, and <code>Signature</code> must be available</li><li><code>SignatureVersion</code> must be <code>1</code></li><li>the <code>SigningCertURL</code> must start with <code>https://</code> and we only want to download certificates from AWS</li></ul><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">CERT_URL_PATTERN</span> = <span class="regexp">/^https:\/\/sns\.[a-zA-Z0-9-]&#123;3,&#125;\.amazonaws\.com(\.cn)?\/SimpleNotificationService-[a-zA-Z0-9]&#123;32&#125;\.pem$/</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">validate</span>(<span class="params">message, cb</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!(<span class="string">&#x27;SignatureVersion&#x27;</span> <span class="keyword">in</span> message &amp;&amp; <span class="string">&#x27;SigningCertURL&#x27;</span> <span class="keyword">in</span> message &amp;&amp; <span class="string">&#x27;Type&#x27;</span> <span class="keyword">in</span> message &amp;&amp; <span class="string">&#x27;Signature&#x27;</span> <span class="keyword">in</span> message)) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;missing field&#x27;</span>);</span><br><span class="line">    <span class="title function_">cb</span>(<span class="literal">null</span>, <span class="literal">false</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (message.<span class="property">SignatureVersion</span> !== <span class="string">&#x27;1&#x27;</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;invalid SignatureVersion&#x27;</span>);</span><br><span class="line">    <span class="title function_">cb</span>(<span class="literal">null</span>, <span class="literal">false</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (!<span class="variable constant_">CERT_URL_PATTERN</span>.<span class="title function_">test</span>(message.<span class="property">SigningCertURL</span>)) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;invalid certificate URL&#x27;</span>);</span><br><span class="line">    <span class="title function_">cb</span>(<span class="literal">null</span>, <span class="literal">false</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">fetchCert</span>(message.<span class="property">SigningCertURL</span>, <span class="function">(<span class="params">err, certificate</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (err) &#123;</span><br><span class="line">        <span class="title function_">cb</span>(err);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// TODO verifiy signature (insert next code block here)</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Finally, the signature is verified.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> verify = crypto.<span class="title function_">createVerify</span>(<span class="string">&#x27;sha1WithRSAEncryption&#x27;</span>);</span><br><span class="line"><span class="title function_">fieldsForSignature</span>(message.<span class="property">Type</span>).<span class="title function_">forEach</span>(<span class="function"><span class="params">key</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (key <span class="keyword">in</span> message) &#123;</span><br><span class="line">    verify.<span class="title function_">write</span>(<span class="string">`<span class="subst">$&#123;key&#125;</span>\n<span class="subst">$&#123;message[key]&#125;</span>\n`</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line">verify.<span class="title function_">end</span>();</span><br><span class="line"><span class="keyword">const</span> result = verify.<span class="title function_">verify</span>(certificate, message.<span class="property">Signature</span>, <span class="string">&#x27;base64&#x27;</span>);</span><br><span class="line"><span class="title function_">cb</span>(<span class="literal">null</span>, result);</span><br></pre></td></tr></table></figure><p>You can test the code with a message like this:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="title function_">validate</span>(&#123;</span><br><span class="line">  <span class="title class_">Type</span>: <span class="string">&#x27;Notification&#x27;</span>,</span><br><span class="line">  <span class="title class_">MessageId</span>: <span class="string">&#x27;4c807a89-9ef9-543b-bfab-2f4ed41e91b4&#x27;</span>,</span><br><span class="line">  <span class="title class_">TopicArn</span>: <span class="string">&#x27;arn:aws:sns:us-east-1:853553028582:marbot-dev-alert-Topic-8CT7ZJRNSA5Y&#x27;</span>,</span><br><span class="line">  <span class="title class_">Subject</span>: <span class="string">&#x27;INSUFFICIENT_DATA: &quot;insufficient test&quot; in US East (N. Virginia)&#x27;</span>,</span><br><span class="line">  <span class="title class_">Message</span>: <span class="string">&#x27;&#123;&quot;AlarmName&quot;:&quot;insufficient test&quot;,&quot;AlarmDescription&quot;:null,&quot;AWSAccountId&quot;:&quot;853553028582&quot;,&quot;NewStateValue&quot;:&quot;INSUFFICIENT_DATA&quot;,&quot;NewStateReason&quot;:&quot;tets&quot;,&quot;StateChangeTime&quot;:&quot;2019-08-09T10:19:19.614+0000&quot;,&quot;Region&quot;:&quot;US East (N. Virginia)&quot;,&quot;OldStateValue&quot;:&quot;OK&quot;,&quot;Trigger&quot;:&#123;&quot;MetricName&quot;:&quot;CallCount2&quot;,&quot;Namespace&quot;:&quot;AWS/Usage&quot;,&quot;StatisticType&quot;:&quot;Statistic&quot;,&quot;Statistic&quot;:&quot;AVERAGE&quot;,&quot;Unit&quot;:null,&quot;Dimensions&quot;:[&#123;&quot;value&quot;:&quot;API&quot;,&quot;name&quot;:&quot;Type&quot;&#125;,&#123;&quot;value&quot;:&quot;PutMetricData&quot;,&quot;name&quot;:&quot;Resource&quot;&#125;,&#123;&quot;value&quot;:&quot;CloudWatch&quot;,&quot;name&quot;:&quot;Service&quot;&#125;,&#123;&quot;value&quot;:&quot;None&quot;,&quot;name&quot;:&quot;Class&quot;&#125;],&quot;Period&quot;:300,&quot;EvaluationPeriods&quot;:1,&quot;ComparisonOperator&quot;:&quot;GreaterThanThreshold&quot;,&quot;Threshold&quot;:1.0,&quot;TreatMissingData&quot;:&quot;- TreatMissingData:                    missing&quot;,&quot;EvaluateLowSampleCountPercentile&quot;:&quot;&quot;&#125;&#125;&#x27;</span>,</span><br><span class="line">  <span class="title class_">Timestamp</span>: <span class="string">&#x27;2019-08-09T10:19:19.644Z&#x27;</span>,</span><br><span class="line">  <span class="title class_">SignatureVersion</span>: <span class="string">&#x27;1&#x27;</span>,</span><br><span class="line">  <span class="title class_">Signature</span>: <span class="string">&#x27;gnCKAUYX6YlBW3dkOmrSFvdB6r82Q2He+7uZV9072sdCP0DSaR46ka/4ymSdDfqilqxjJ9hajd9l7j8ZsL98vYdUbut/1IJ2hsuALF9nd/HwNLPPWvKXaK/Y3Hp57izOpeBAkuR6koitSbXX50lEj7FraaMVQfpexm01z7IUcx4vCCvZBTdQLbkWw+TYWkWNsMrqarW39zy474SmTBCSZlz1eoV6tCwYk2Z2G2awiXpnfsQRRZvHn4ot176oY+ADAFJ0sIa44effQXq+tAWE6/Z3M5rjtfg6OULDM+NGEmnVZL3xyWK8bIzB48ZclQo3ZsvLPGmCNQLlFpaP/3fGGg==&#x27;</span>,</span><br><span class="line">  <span class="title class_">SigningCertURL</span>: <span class="string">&#x27;https://sns.us-east-1.amazonaws.com/SimpleNotificationService-6aad65c2f9911b05cd53efda11f913f9.pem&#x27;</span>,</span><br><span class="line">  <span class="title class_">UnsubscribeURL</span>: <span class="string">&#x27;https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&amp;SubscriptionArn=arn:aws:sns:us-east-1:853553028582:marbot-dev-alert-Topic-8CT7ZJRNSA5Y:86a160f0-c3c5-4ae1-ae50-2903eede0af1&#x27;</span></span><br><span class="line">&#125;, <span class="function">(<span class="params">err, result</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(err);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;result&#x27;</span>, result);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>You should verify the authenticity of a message sent by Amazon SNS. The <a href="https://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.verify.signature.html" target="_blank" rel="noopener">SNS documentation</a> provides an in-depth description of the needed steps which can be implemented in Node.js as shown in this blog post.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Review: AWS Backup - A centralized place for managing backups?</title>
      <link>https://cloudonaut.io/review-aws-backup/</link>
      <description>
        <![CDATA[<p>AWS Backup aims to become a centralized place for managing backups. If possible, AWS Backup uses existing features to create backups (e.g]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/review/">Review</category>
      <category domain="https://cloudonaut.io/tag/backup/">backup</category>
      <category domain="https://cloudonaut.io/tag/review/">review</category>
      <guid isPermaLink="true">https://cloudonaut.io/review-aws-backup/</guid>
      <pubDate>Thu, 08 Aug 2019 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>AWS Backup aims to become a centralized place for managing backups. If possible, AWS Backup uses existing features to create backups (e.g., RDS snapshots). Sometimes, AWS Backup is the only way to create a backup (e.g., EFS file systems).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/08/review@730w.webp 730w, /images/2019/08/review@730w2x.webp 1460w, /images/2019/08/review@610w.webp 610w, /images/2019/08/review@610w2x.webp 1220w, /images/2019/08/review@450w.webp 450w, /images/2019/08/review@450w2x.webp 900w, /images/2019/08/review@330w.webp 330w, /images/2019/08/review@330w2x.webp 660w, /images/2019/08/review@545w.webp 545w, /images/2019/08/review@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/08/review@730w.jpg 730w, /images/2019/08/review@730w2x.jpg 1460w, /images/2019/08/review@610w.jpg 610w, /images/2019/08/review@610w2x.jpg 1220w, /images/2019/08/review@450w.jpg 450w, /images/2019/08/review@450w2x.jpg 900w, /images/2019/08/review@330w.jpg 330w, /images/2019/08/review@330w2x.jpg 660w, /images/2019/08/review@545w.jpg 545w, /images/2019/08/review@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/08/review.jpg" alt="Review" title="Review"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/1-review-aws-backup/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p><strong>Backups</strong> (sometimes also referred to as recovery points) are stored in <strong>vaults</strong>. It is possible to protect a vault with an optional resource-based policy, e.g., to prevent anyone from deleting backups. The backup <strong>plan</strong> defines when backups are made and for how long the backups are stored. To be more precise, you only define when a <strong>backup job</strong> should start. After that, a job tries to start within a configurable period. You can also configure a timeout for the job. Finally, you <strong>assign</strong> resources to backup plans based on tags or direct assignments. After a disaster, you <strong>restore</strong> a backup by creating a <strong>restore job</strong>.</p><h2 id="Supported-data-sources"><a href="#Supported-data-sources" class="headerlink" title="Supported data sources"></a>Supported data sources</h2><p>AWS Backup backs up and restores the following data sources:</p><ul><li>DynamoDB tables</li><li>EFS file systems (supports cold storage to save cost<sup><a href="#fn:12" id="fnref:12" class="footnote-ref">12</a></sup>)</li><li>EBS volumes</li><li>RDS databases (except Amazon Aurora)</li><li>Storage Gateway</li></ul><p>The following data sources are not supported yet:</p><ul><li>S3 buckets</li><li>EC2 instances</li><li>Elastisearch domains</li><li>Redshift clusters</li><li>EMR clusters</li><li>Cognito user pools</li><li>DocumentDB clusters</li><li>ElastiCache clusters</li><li>Neptune clusters</li><li>CloudDirectory directories</li><li>…</li></ul><p>Keep in mind that restores are more complicated than a single click if you manage your infrastructure with CloudFormation. You should still practice restoring your data regularly.</p><h2 id="Backup-Consistency"><a href="#Backup-Consistency" class="headerlink" title="Backup Consistency"></a>Backup Consistency</h2><p>If you create a backup, you might expect that all data up to a point in time where the backup was triggered appears in the backup. The following table shows what you can expect in reality. </p><table class="table table-striped table-responsive"><thead><tr><th>Data Source</th><th>Backup consistency guarantees</th></tr></thead><tbody><tr><td>DynamoDB</td><td>❌ <sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup> / ✅ PITR<sup><a href="#fn:13" id="fnref:13" class="footnote-ref">13</a></sup></td></tr><tr><td>EFS</td><td>❌ <sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup></td></tr><tr><td>EBS</td><td>⚠️ Crash consistent<sup><a href="#fn:3" id="fnref:3" class="footnote-ref">3</a></sup></td></tr><tr><td>RDS</td><td>✅ Application consistent<sup><a href="#fn:4" id="fnref:4" class="footnote-ref">4</a></sup></td></tr><tr><td>Storage Gateway</td><td>✅ Application consistent<sup><a href="#fn:5" id="fnref:5" class="footnote-ref">5</a></sup></td></tr></tbody></table><h2 id="Notifications"><a href="#Notifications" class="headerlink" title="Notifications"></a>Notifications</h2><p>AWS Backup can deliver notifications to SNS. My first impression was that the service does not publish failures to SNS<sup><a href="#fn:6" id="fnref:6" class="footnote-ref">6</a></sup>. Failures can happen for many reasons:</p><ol><li>The backup can not start within the defined time span <code>StartWindowMinutes</code></li><li>The backup cannot complete within the specified period <code>CompletionWindowMinutes</code></li><li>Some other error occurs (e.g., the RDS API throws an error)</li></ol><p>The Reddit user <a href="https://www.reddit.com/r/aws/comments/cniw6s/review_aws_backup_a_centralized_place_for/ewdwnu2/" target="_blank" rel="noopener">greyskymorning</a> figured out that failures are published as an <code>BACKUP_JOB_COMPLETED</code> event.</p><h2 id="Service-Maturity-Table"><a href="#Service-Maturity-Table" class="headerlink" title="Service Maturity Table"></a>Service Maturity Table</h2><p>The following overview shows the maturity of the service.</p><table class="table table-striped table-responsive"><thead><tr><th>Criteria</th><th>Support</th><th>Score</th></tr></thead><tbody><tr><td>Feature Completeness</td><td>⚠️</td><td>4</td></tr><tr><td>Tags (Grouping + Billing)</td><td>✅</td><td>10</td></tr><tr><td>CloudFormation + Terraform support</td><td>✅</td><td>10</td></tr><tr><td>Emits CloudWatch Events</td><td>❌<sup><a href="#fn:7" id="fnref:7" class="footnote-ref">7</a></sup></td><td>0</td></tr><tr><td>IAM granularity</td><td>⚠️<sup><a href="#fn:8" id="fnref:8" class="footnote-ref">8</a></sup></td><td>8</td></tr><tr><td>Integrated with AWS Config</td><td>❌<sup><a href="#fn:9" id="fnref:9" class="footnote-ref">9</a></sup></td><td>0</td></tr><tr><td>Auditing via AWS CloudTrail</td><td>✅<sup><a href="#fn:10" id="fnref:10" class="footnote-ref">10</a></sup></td><td>10</td></tr><tr><td>Available in all commercial regions</td><td>⚠️<sup><a href="#fn:11" id="fnref:11" class="footnote-ref">11</a></sup></td><td>8</td></tr><tr><td><strong>Total Maturity Score (0-10)</strong></td><td>⚠️</td><td><strong>6.3</strong></td></tr></tbody></table><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p><strong>Our maturity score for AWS Backup is 6.3 on a scale from 0 to 10.</strong> Therefore, I recommend to evaluate and use AWS Backup with some restrictions listed below.</p><ul><li>AWS Backup is not yet the universal tool that creates backups of everything in a magic way.</li><li>AWS Backup works fine for the supported services: DynamoDB, EFS, EBS, RDS, and Storage Gateway.</li><li>There is no satisfactory alternative for backing up EFS besides AWS Backup.</li><li>Keep in mind the provided consistency guarantees. You don’t know the exact time the backup job runs.</li><li>I don’t think that AWS Backup is made for setups managed entirely with CloudFormation&#x2F;Terraform. E.g., there is no way to create an EFS file system from a backup in CloudFormation.</li><li>Only EFS backups support cold storage to save costs.</li></ul><p>In summary, I like the idea that AWS Backup will be the single point where we configure and monitor the backups for all data stores. I will follow the announcements from AWS carefully.</p><p>We have added AWS Backup to our CloudFormation templates: <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">aws-cf-templates</a> and <a href="https://github.com/cfn-modules/docs" target="_blank" rel="noopener">cfn-modules</a>.</p><hr><p>Looking for a comprehensive introduction to computing, storing, and networking in the AWS cloud? Get a copy of our book <a href="https://bit.ly/amazon-web-services-in-action-2nd-edition" target="_blank" rel="noopener">Amazon Web Services in Action</a>! </p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/backuprestore_HowItWorks.html <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. https://docs.aws.amazon.com/efs/latest/ug/awsbackup.html#backup-consistency <a href="#fnref:2" class="footnote-backref">↩</a></p></li><li id="fn:3"><p>3. https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-creating-snapshot.html <a href="#fnref:3" class="footnote-backref">↩</a></p></li><li id="fn:4"><p>4. For MySQL and MariaDB, only when InnoDB is used https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_WorkingWithAutomatedBackups.html#Overview.BackupDeviceRestrictions <a href="#fnref:4" class="footnote-backref">↩</a></p></li><li id="fn:5"><p>5. https://docs.aws.amazon.com/storagegateway/latest/APIReference/API_ListVolumeRecoveryPoints.html <a href="#fnref:5" class="footnote-backref">↩</a></p></li><li id="fn:6"><p>6. https://docs.aws.amazon.com/aws-backup/latest/devguide/sns-notifications.html <a href="#fnref:6" class="footnote-backref">↩</a></p></li><li id="fn:7"><p>7. https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html <a href="#fnref:7" class="footnote-backref">↩</a></p></li><li id="fn:8"><p>8. https://docs.aws.amazon.com/IAM/latest/UserGuide/list_awsbackup.html <a href="#fnref:8" class="footnote-backref">↩</a></p></li><li id="fn:9"><p>9. https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html <a href="#fnref:9" class="footnote-backref">↩</a></p></li><li id="fn:10"><p>10. https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-aws-service-specific-topics.html <a href="#fnref:10" class="footnote-backref">↩</a></p></li><li id="fn:11"><p>11. https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/ <a href="#fnref:11" class="footnote-backref">↩</a></p></li><li id="fn:12"><p>12. https://aws.amazon.com/backup/pricing/ <a href="#fnref:12" class="footnote-backref">↩</a></p></li><li id="fn:13"><p>13. Chttps://docs.aws.amazon.com/amazondynamodb/latest/developerguide/PointInTimeRecovery_Howitworks.html <a href="#fnref:13" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Calling AppSync GraphQL from Lambda</title>
      <link>https://cloudonaut.io/calling-appsync-graphql-from-lambda/</link>
      <description>
        <![CDATA[<p>AWS AppSync provides an easy way to run a GraphQL API that triggers AWS Lambda functions and other AWS services. If you start with AppSyn]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/appsync/">appsync</category>
      <guid isPermaLink="true">https://cloudonaut.io/calling-appsync-graphql-from-lambda/</guid>
      <pubDate>Fri, 26 Jul 2019 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>AWS AppSync provides an easy way to run a GraphQL API that triggers AWS Lambda functions and other AWS services. If you start with AppSync, you likely have existing systems running next to it. Sooner or later, you want to call the GraphQL API from your Lambda function (Node.js). For example, to trigger an AppSync subscription from another system.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/07/call@730w.webp 730w, /images/2019/07/call@730w2x.webp 1460w, /images/2019/07/call@610w.webp 610w, /images/2019/07/call@610w2x.webp 1220w, /images/2019/07/call@450w.webp 450w, /images/2019/07/call@450w2x.webp 900w, /images/2019/07/call@330w.webp 330w, /images/2019/07/call@330w2x.webp 660w, /images/2019/07/call@545w.webp 545w, /images/2019/07/call@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/07/call@730w.jpg 730w, /images/2019/07/call@730w2x.jpg 1460w, /images/2019/07/call@610w.jpg 610w, /images/2019/07/call@610w2x.jpg 1220w, /images/2019/07/call@450w.jpg 450w, /images/2019/07/call@450w2x.jpg 900w, /images/2019/07/call@330w.jpg 330w, /images/2019/07/call@330w2x.jpg 660w, /images/2019/07/call@545w.jpg 545w, /images/2019/07/call@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/07/call.jpg" alt="Calling AppSync GraphQL from Lambda" title="Calling AppSync GraphQL from Lambda"></picture></p><p>To do so, you need:</p><ol><li>A GraphQL client library</li><li>Authentication</li><li>Send your GraphQL request to the AppSync endpoint</li></ol><p>Let’s walk through the steps.</p><h2 id="Installing-the-library-dependencies"><a href="#Installing-the-library-dependencies" class="headerlink" title="Installing the library dependencies"></a>Installing the library dependencies</h2><p>The easiest way to talk to an AppSync GraphQL API is by using the <code>aws-appsync</code> package which wraps the <code>apollo</code> GraphQL client package. The libraries assume that they run in a browser environment where the <a href="https://developer.mozilla.org/docs/Web/API/Fetch_API" target="_blank" rel="noopener">Fetch API</a> is available. The package <code>cross-fetch</code> provides a polyfill to bring The Fetch API to Node.js environments as well. The package <code>graphql-tag</code> is used to parse a GraphQL query.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm i aws-appsync@1.8.1</span><br><span class="line">npm i cross-fetch@3.0.4</span><br><span class="line">npm i graphql-tag@2.10.1</span><br></pre></td></tr></table></figure><h2 id="Creating-a-client-with-authentication"><a href="#Creating-a-client-with-authentication" class="headerlink" title="Creating a client with authentication"></a>Creating a client with authentication</h2><p>AppSync supports multiple authentication types. If your API uses <code>AWS_IAM</code>, you are all fine. If not:</p><ol><li>In your AppSync Api Settings, go to <strong>Additional auth providers</strong> and add <code>AWS_IAM</code></li><li>Add the schema directive <code>@aws_iam</code> to the mutation like this</li></ol><figure class="highlight cmake"><table><tr><td class="code"><pre><span class="line">type Mutation &#123;</span><br><span class="line">  <span class="comment"># used internally to trigger the subscription</span></span><br><span class="line">  <span class="keyword">test</span>(value: <span class="keyword">String</span>!): <span class="keyword">String</span></span><br><span class="line">  @aws_iam</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>In your JavaScript code, create a client object:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> appsync = <span class="built_in">require</span>(<span class="string">&#x27;aws-appsync&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> gql = <span class="built_in">require</span>(<span class="string">&#x27;graphql-tag&#x27;</span>);</span><br><span class="line"><span class="built_in">require</span>(<span class="string">&#x27;cross-fetch/polyfill&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> graphqlClient = <span class="keyword">new</span> appsync.<span class="title class_">AWSAppSyncClient</span>(&#123;</span><br><span class="line">  <span class="attr">url</span>: <span class="string">&#x27;APPSYNC_ENDPOINT_URL&#x27;</span>,</span><br><span class="line">  <span class="attr">region</span>: process.<span class="property">env</span>.<span class="property">AWS_REGION</span>,</span><br><span class="line">  <span class="attr">auth</span>: &#123;</span><br><span class="line">    <span class="attr">type</span>: <span class="string">&#x27;AWS_IAM&#x27;</span>,</span><br><span class="line">    <span class="attr">credentials</span>: &#123;</span><br><span class="line">      <span class="attr">accessKeyId</span>: process.<span class="property">env</span>.<span class="property">AWS_ACCESS_KEY_ID</span>,</span><br><span class="line">      <span class="attr">secretAccessKey</span>: process.<span class="property">env</span>.<span class="property">AWS_SECRET_ACCESS_KEY</span>,</span><br><span class="line">      <span class="attr">sessionToken</span>: process.<span class="property">env</span>.<span class="property">AWS_SESSION_TOKEN</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">disableOffline</span>: <span class="literal">true</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>Now, you are ready to send your GraphQL request to the AppSync endpoint</p><h2 id="Sending-your-GraphQL-request-to-the-AppSync-endpoint"><a href="#Sending-your-GraphQL-request-to-the-AppSync-endpoint" class="headerlink" title="Sending your GraphQL request to the AppSync endpoint"></a>Sending your GraphQL request to the AppSync endpoint</h2><p>Finally, you create your GraphQL query and send it to the endpoint.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> mutation = gql`<span class="language-graphql"><span class="keyword">mutation</span> Test<span class="punctuation">(</span><span class="variable">$value</span>: String<span class="punctuation">!</span><span class="punctuation">)</span> <span class="punctuation">&#123;</span></span></span><br><span class="line"><span class="language-graphql">  test<span class="punctuation">(</span><span class="symbol">value</span><span class="punctuation">:</span> <span class="variable">$value</span>)</span></span><br><span class="line"><span class="language-graphql"><span class="punctuation">&#125;</span>`</span>;</span><br><span class="line"><span class="keyword">await</span> graphqlClient.<span class="title function_">mutate</span>(&#123;</span><br><span class="line">  mutation,</span><br><span class="line">  <span class="attr">variables</span>: &#123;</span><br><span class="line">    <span class="attr">value</span>: <span class="string">&#x27;test&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>Keep in mind that your Lambda function needs the following permission to invoke the AppSync API endpoint (replace <code>REGION</code> with the region identifier and <code>AWS_ACCOUNT_ID</code> with the AWS account id):</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="string">&quot;appsync:GraphQL&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:appsync:REGION:ACCOUNT_ID:apis/API_ID/types/Mutation/fields/test&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Invoking a AppSync GraphQL API endpoint from Lambda requires a few tricks. A typical use case for calling an AppSync API from a Lambda function is to trigger a subscription from an external system.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Dockerizing legacy applications with confd</title>
      <link>https://cloudonaut.io/dockerizing-legacy-applications-with-confd/</link>
      <description>
        <![CDATA[<p>A legacy application typically uses files to read configuration parameters. But working with configuration files is cumbersome when build]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <guid isPermaLink="true">https://cloudonaut.io/dockerizing-legacy-applications-with-confd/</guid>
      <pubDate>Fri, 19 Jul 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>A legacy application typically uses files to read configuration parameters. But working with configuration files is cumbersome when building Docker images for the use with ECS (EC2 or Fargate). In theory, you could copy configuration files to the EC2 instances and mount them into your containers. However, this approach is cumbersome and does not work with Fargate at all. Alternatively, you could create a Docker image for every configuration. Building and managing so many different Docker images for each application will cause a lot of headaches.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/07/progamming@730w.webp 730w, /images/2019/07/progamming@730w2x.webp 1460w, /images/2019/07/progamming@610w.webp 610w, /images/2019/07/progamming@610w2x.webp 1220w, /images/2019/07/progamming@450w.webp 450w, /images/2019/07/progamming@450w2x.webp 900w, /images/2019/07/progamming@330w.webp 330w, /images/2019/07/progamming@330w2x.webp 660w, /images/2019/07/progamming@545w.webp 545w, /images/2019/07/progamming@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/07/progamming@730w.jpg 730w, /images/2019/07/progamming@730w2x.jpg 1460w, /images/2019/07/progamming@610w.jpg 610w, /images/2019/07/progamming@610w2x.jpg 1220w, /images/2019/07/progamming@450w.jpg 450w, /images/2019/07/progamming@450w2x.jpg 900w, /images/2019/07/progamming@330w.jpg 330w, /images/2019/07/progamming@330w2x.jpg 660w, /images/2019/07/progamming@545w.jpg 545w, /images/2019/07/progamming@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/07/progamming.jpg" alt="Dockerizing legacy applications with confd" title="Dockerizing legacy applications with confd"></picture></p><p>It is a best practice to use environment variables to configure an application running inside a Docker container. Doing so allows you to start the Docker image with a customized configuration on any platform.</p><p>You could rewrite your application to read configuration parameters from environment variables instead of files. However, there is a much simpler way. The tool <a href="https://github.com/kelseyhightower/confd/" target="_blank" rel="noopener">confd</a> generates configuration files based on templates and inserts configuration parameters handed over via environment variables.</p><p>You will learn how to dockerize your legacy application with little effort next. Let’s start with creating a <code>Dockerfile</code>.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">FROM</span> amazonlinux:<span class="number">2</span></span><br></pre></td></tr></table></figure><p>The following section installs and configures <code>confd</code>. You will learn more about how to create configuration files with <code>confd</code> in a second — no need to change anything here.</p><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="comment"># confd</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> curl -s -L https://github.com/kelseyhightower/confd/releases/download/v0.16.0/confd-0.16.0-linux-amd64 -o /usr/local/bin/confd &amp;&amp; <span class="built_in">chmod</span> +x /usr/local/bin/confd</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> docker/confd /etc/confd</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">mkdir</span> /var/www/html/conf</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> docker/custom-entrypoint /usr/local/bin/</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">chmod</span> u+x /usr/local/bin/custom-entrypoint</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;custom-entrypoint&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>In the following steps, you will learn how to write configuration files on container startup based on environment variables with <code>confd</code>.</p><p>The legacy application uses a configuration file to configure the database connection: <code>conf/app.ini</code></p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[database]</span></span><br><span class="line"><span class="attr">host</span>=mysql</span><br><span class="line"><span class="attr">name</span>=test</span><br><span class="line"><span class="attr">user</span>=app</span><br><span class="line"><span class="attr">password</span>=secret</span><br></pre></td></tr></table></figure><p>Our goal is to use environment variables for each property. To do so with <code>confd</code>, we need to create a configuration template. The following snippet shows the template <code>docker/confd/templates/app.ini.tmpl</code> for our <code>conf/app.ini</code> configuration file.</p><p>In the template, each value has been replaced with a placeholder.</p><figure class="highlight awk"><table><tr><td class="code"><pre><span class="line">[database]</span><br><span class="line">host=<span class="string">&quot;&#123;&#123;getv &quot;</span><span class="regexp">/database/</span>host<span class="string">&quot;&#125;&#125;&quot;</span></span><br><span class="line">name=<span class="string">&quot;&#123;&#123;getv &quot;</span><span class="regexp">/database/</span>name<span class="string">&quot;&#125;&#125;&quot;</span></span><br><span class="line">user=<span class="string">&quot;&#123;&#123;getv &quot;</span><span class="regexp">/database/u</span>se<span class="string">r&quot;&#125;&#125;&quot;</span></span><br><span class="line">password=<span class="string">&quot;&#123;&#123;getv &quot;</span><span class="regexp">/database/</span>password<span class="string">&quot;&#125;&#125;&quot;</span></span><br></pre></td></tr></table></figure><p>For example, the following example references the environment variable <code>DATABASE_HOST</code>.</p><figure class="highlight handlebars"><table><tr><td class="code"><pre><span class="line"><span class="template-variable">&#123;&#123;<span class="name">getv</span> <span class="string">&quot;/database/host&quot;</span>&#125;&#125;</span></span><br></pre></td></tr></table></figure><p>Besides the template you need to create a <code>confd</code> configuration file as shown in the following snippet from <code>docker/confd/conf.d/app.toml</code>:</p><ul><li><code>src</code> the template file</li><li><code>dest</code> the destination path for the generated configuration file</li><li><code>keys</code> the keys used within the template file</li></ul><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="section">[template]</span></span><br><span class="line"><span class="attr">src</span> = <span class="string">&quot;app.ini.tmpl&quot;</span></span><br><span class="line"><span class="attr">dest</span> = <span class="string">&quot;/var/www/html/conf/app.ini&quot;</span></span><br><span class="line"><span class="attr">keys</span> = [</span><br><span class="line">  <span class="string">&quot;/database/host&quot;</span>,</span><br><span class="line">  <span class="string">&quot;/database/name&quot;</span>,</span><br><span class="line">  <span class="string">&quot;/database/user&quot;</span>,</span><br><span class="line">  <span class="string">&quot;/database/password&quot;</span>,</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>One last step, the script <code>custom-entrypoint</code> is executed whenever you start a container. The script runs <code>confd</code> and executes the <code>CMD</code> afterward.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;generating config&quot;</span></span><br><span class="line">confd -onetime -backend <span class="built_in">env</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;executing <span class="variable">$@</span>&quot;</span></span><br><span class="line"><span class="built_in">exec</span> <span class="string">&quot;<span class="variable">$@</span>&quot;</span></span><br></pre></td></tr></table></figure><p>When dockerizing your application, you need to create your configuration files with <code>confd</code>?: Create a new template file <code>.tmpl</code> and store it at <code>docker/confd/templates/</code>. Next, create a new configuration file <code>.toml</code> and store it at <code>docker/confd/conf.d/</code>. Finally, modify the template and configuration file according to this example.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>By using <code>confd</code>, it is simple to build a Docker image reading configuration parameters from environment variables. No need for rebuilding the way your legacy application reads configuration files.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to dockerize your PHP application for AWS Fargate?</title>
      <link>https://cloudonaut.io/how-to-dockerize-your-php-application-for-aws-fargate/</link>
      <description>
        <![CDATA[<p>The biggest game-changer for Docker on AWS was the announcement of AWS Fargate. Operating Docker containers could not be easier. With AWS]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-dockerize-your-php-application-for-aws-fargate/</guid>
      <pubDate>Fri, 12 Jul 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The biggest game-changer for Docker on AWS was the announcement of AWS Fargate. Operating Docker containers could not be easier. With AWS Fargate, you launch Docker containers in the cloud without any need of managing virtual machines.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/07/build-docker@730w.webp 730w, /images/2019/07/build-docker@730w2x.webp 1460w, /images/2019/07/build-docker@610w.webp 610w, /images/2019/07/build-docker@610w2x.webp 1220w, /images/2019/07/build-docker@450w.webp 450w, /images/2019/07/build-docker@450w2x.webp 900w, /images/2019/07/build-docker@330w.webp 330w, /images/2019/07/build-docker@330w2x.webp 660w, /images/2019/07/build-docker@545w.webp 545w, /images/2019/07/build-docker@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/07/build-docker@730w.jpg 730w, /images/2019/07/build-docker@730w2x.jpg 1460w, /images/2019/07/build-docker@610w.jpg 610w, /images/2019/07/build-docker@610w2x.jpg 1220w, /images/2019/07/build-docker@450w.jpg 450w, /images/2019/07/build-docker@450w2x.jpg 900w, /images/2019/07/build-docker@330w.jpg 330w, /images/2019/07/build-docker@330w2x.jpg 660w, /images/2019/07/build-docker@545w.jpg 545w, /images/2019/07/build-docker@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/07/build-docker.jpg" alt="Dockerize your PHP application for AWS Fargate" title="Dockerize your PHP application for AWS Fargate"></picture></p><p>All you need is a Docker image of your application. You will learn how to dockerize your PHP web application for the use with AWS Fargate in the following.</p><h2 id="What-is-a-Docker-image"><a href="#What-is-a-Docker-image" class="headerlink" title="What is a Docker image?"></a>What is a Docker image?</h2><p>A Docker image is similar to a virtual machine image, such as an Amazon Machine Image (AMI) that is used to launch an EC2 instance. The Docker image contains an operating system, the runtime environment, 3rd party libraries, and your application. The following figure illustrates how you can fetch an image and start a container on any platform.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/07/docker-image@730w.webp 730w, /images/2019/07/docker-image@730w2x.webp 1460w, /images/2019/07/docker-image@610w.webp 610w, /images/2019/07/docker-image@610w2x.webp 1220w, /images/2019/07/docker-image@450w.webp 450w, /images/2019/07/docker-image@450w2x.webp 900w, /images/2019/07/docker-image@330w.webp 330w, /images/2019/07/docker-image@330w2x.webp 660w, /images/2019/07/docker-image@545w.webp 545w, /images/2019/07/docker-image@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/07/docker-image@730w.jpg 730w, /images/2019/07/docker-image@730w2x.jpg 1460w, /images/2019/07/docker-image@610w.jpg 610w, /images/2019/07/docker-image@610w2x.jpg 1220w, /images/2019/07/docker-image@450w.jpg 450w, /images/2019/07/docker-image@450w2x.jpg 900w, /images/2019/07/docker-image@330w.jpg 330w, /images/2019/07/docker-image@330w2x.jpg 660w, /images/2019/07/docker-image@545w.jpg 545w, /images/2019/07/docker-image@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/07/docker-image.jpg" alt="Distribute your application among multiple machines with a Docker image" title="Distribute your application among multiple machines with a Docker image"></picture></p><h2 id="Two-containers-NGINX-and-PHP-FPM"><a href="#Two-containers-NGINX-and-PHP-FPM" class="headerlink" title="Two containers: NGINX and PHP-FPM"></a>Two containers: NGINX and PHP-FPM</h2><p>But how do you create a Docker image for your web application? By creating a script that builds the image step by step: a so-called Dockerfile.</p><p>Our example is a PHP application written in PHP without using any frameworks. The project’s folder structure:</p><ul><li><code>conf</code> the configuration directory (contains .ini files)</li><li><code>css</code> the stylesheet directory (contains .css files)</li><li><code>img</code> the images directory (contains .gif files)</li><li><code>lib</code> the libraries directory (contains .php files)</li><li><code>index.php</code> the main file</li></ul><p>Add a <code>docker</code> directory containing the Docker configuration (e.g., the Dockerfile).</p><p>A typical setup to serve a PHP application consists of:</p><ol><li>A web server (for example NGINX)</li><li>A PHP process (for example PHP-FPM)</li></ol><p>Therefore, we need to run two processes: NGINX and PHP-FPM. However, a container should only run exactly one process at a time. Which means we need to build two images. The following figure shows the two containers: the NGINX container receives the request from the client and forwards PHP requests to the PHP-FPM container. Both containers run on the same host to avoid additional network latency.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/07/docker-proxy-pattern@730w.webp 730w, /images/2019/07/docker-proxy-pattern@730w2x.webp 1460w, /images/2019/07/docker-proxy-pattern@610w.webp 610w, /images/2019/07/docker-proxy-pattern@610w2x.webp 1220w, /images/2019/07/docker-proxy-pattern@450w.webp 450w, /images/2019/07/docker-proxy-pattern@450w2x.webp 900w, /images/2019/07/docker-proxy-pattern@330w.webp 330w, /images/2019/07/docker-proxy-pattern@330w2x.webp 660w, /images/2019/07/docker-proxy-pattern@545w.webp 545w, /images/2019/07/docker-proxy-pattern@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/07/docker-proxy-pattern@730w.jpg 730w, /images/2019/07/docker-proxy-pattern@730w2x.jpg 1460w, /images/2019/07/docker-proxy-pattern@610w.jpg 610w, /images/2019/07/docker-proxy-pattern@610w2x.jpg 1220w, /images/2019/07/docker-proxy-pattern@450w.jpg 450w, /images/2019/07/docker-proxy-pattern@450w2x.jpg 900w, /images/2019/07/docker-proxy-pattern@330w.jpg 330w, /images/2019/07/docker-proxy-pattern@330w2x.jpg 660w, /images/2019/07/docker-proxy-pattern@545w.jpg 545w, /images/2019/07/docker-proxy-pattern@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/07/docker-proxy-pattern.jpg" alt="Proxy pattern: NGINX and PHP-FPM containers running on the same machine" title="Proxy pattern: NGINX and PHP-FPM containers running on the same machine"></picture></p><h2 id="Building-the-NGINX-image"><a href="#Building-the-NGINX-image" class="headerlink" title="Building the NGINX image"></a>Building the NGINX image</h2><p>Start with creating a Docker image for NGINX. NGINX serves the static files. In our example application, the static files are stored in the <code>css</code> and <code>img</code> directory already. On top of that, NGINX forwards PHP requests to PHP-FPM.</p><p>The following snippet shows the configuration file <code>docker/nginx/default.conf</code> which tells NGINX to serve static files from <code>/var/www/html</code> and forward PHP requests to PHP-FPM. You do not need to make any changes to the NGINX configuration when dockerizing your web application.</p><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="section">server</span> &#123;</span><br><span class="line">  <span class="attribute">listen</span>       <span class="number">80</span>;</span><br><span class="line">  <span class="attribute">server_name</span>  localhost;</span><br><span class="line">  <span class="attribute">root</span>         /var/www/html;</span><br><span class="line">  <span class="attribute">index</span>        index.php;</span><br><span class="line">  <span class="comment"># pass the PHP scripts to FastCGI server </span></span><br><span class="line">  <span class="comment"># listening on 127.0.0.1:9000</span></span><br><span class="line">  <span class="section">location</span> <span class="regexp">~ \.php$</span> &#123;</span><br><span class="line">    <span class="attribute">fastcgi_pass</span>   php:<span class="number">9000</span>;</span><br><span class="line">    <span class="attribute">fastcgi_index</span>  index.php;</span><br><span class="line">    <span class="attribute">fastcgi_param</span>  SCRIPT_FILENAME  <span class="variable">$document_root</span><span class="variable">$fastcgi_script_name</span>;</span><br><span class="line">    <span class="attribute">fastcgi_param</span>  SCRIPT_NAME      <span class="variable">$fastcgi_script_name</span>;</span><br><span class="line">    <span class="attribute">include</span>        fastcgi_params;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Next, you need a Dockerfile for building your own NGINX image. The following snippet shows the Dockerfile <code>docker/nginx/Dockerfile</code> that we created for our sample application.</p><p>The first instruction defines the base image. When creating an image, we don’t have to start from scratch. We can use a pre-built base image.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">FROM</span> nginx:<span class="number">1</span>.<span class="number">14</span></span><br></pre></td></tr></table></figure><p>The next instruction copies the NGINX configuration file from your disk to the Docker image.</p><figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line"><span class="keyword">COPY</span> docker<span class="regexp">/nginx/</span><span class="keyword">default</span>.conf <span class="regexp">/etc/</span>nginx<span class="regexp">/conf.d/</span><span class="keyword">default</span>.conf</span><br></pre></td></tr></table></figure><p>The next two instructions copy the <code>css</code> and <code>img</code> directories from your local disk to the NGINX root directory <code>/var/www/html/</code> in the Docker image.</p><blockquote><p>Depending on where you are storing the static files of your web application, you’ll need to modify these instructions accordingly. Make sure you are copying all static files to <code>/var/www/html/</code>.</p></blockquote><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">COPY css /<span class="selector-tag">var</span>/www/<span class="selector-tag">html</span>/css</span><br><span class="line">COPY <span class="selector-tag">img</span> /<span class="selector-tag">var</span>/www/<span class="selector-tag">html</span>/<span class="selector-tag">img</span></span><br></pre></td></tr></table></figure><p>The next instruction runs the <code>chown</code> command to transfer ownership of all static files to the nginx user. The nginx user is part of the base image.</p><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">chown</span> -R nginx:nginx /var/www/html</span></span><br></pre></td></tr></table></figure><p>The Dockerfile is ready. It’s time to build your first image.</p><figure class="highlight awk"><table><tr><td class="code"><pre><span class="line">docker build -t php-basic-nginx:latest -f docker<span class="regexp">/nginx/</span>Dockerfile .</span><br></pre></td></tr></table></figure><p>The following snippet explains the docker build command in more detail.</p><figure class="highlight livecodeserver"><table><tr><td class="code"><pre><span class="line">docker build                 =&gt; Docker <span class="keyword">command</span> <span class="title">to</span> <span class="title">build</span> <span class="title">a</span> <span class="title">new</span> <span class="title">image</span></span><br><span class="line">  -t php-basic-nginx:latest  =&gt; Add <span class="keyword">a</span> tag (name) <span class="built_in">to</span> <span class="keyword">the</span> <span class="built_in">new</span> image</span><br><span class="line">  -f docker/nginx/Dockerfile =&gt; Location <span class="keyword">of</span> <span class="keyword">the</span> Dockerfile</span><br><span class="line">  .                          =&gt; Use <span class="keyword">the</span> current <span class="built_in">directory</span> <span class="keyword">as</span> <span class="keyword">the</span> </span><br><span class="line">                                build context (all paths, e.g. </span><br><span class="line">                                <span class="keyword">in</span> COPY, are <span class="built_in">relative</span> <span class="built_in">to</span> <span class="keyword">the</span> </span><br><span class="line">                                build context)</span><br></pre></td></tr></table></figure><p>The next step is building the PHP-FPM image. The following snippets show the Dockerfile <code>docker/php-fpm/Dockerfile</code> used by our sample application.</p><h2 id="Building-the-PHP-FPM-image"><a href="#Building-the-PHP-FPM-image" class="headerlink" title="Building the PHP-FPM image"></a>Building the PHP-FPM image</h2><p>The first instruction defines the base image. We are using a base image with PHP 7.3 pre-installed for our sample application.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">FROM</span> php:<span class="number">7</span>.<span class="number">3</span>-fpm-stretch</span><br></pre></td></tr></table></figure><p>It is followed by enabling the PHP configuration optimized for production workloads and installing the PHP extensions <code>pdo</code> and <code>pdo_mysql</code>.</p><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="comment"># PHP</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">mv</span> <span class="string">&quot;<span class="variable">$PHP_INI_DIR</span>/php.ini-production&quot;</span> <span class="string">&quot;<span class="variable">$PHP_INI_DIR</span>/php.ini&quot;</span></span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> docker-php-ext-install -j$(<span class="built_in">nproc</span>) pdo pdo_mysql</span></span><br></pre></td></tr></table></figure><p>The following instructions copy the PHP files from your disk to the root directory of PHP-FPM.</p><blockquote><p>When dockerizing your application, you will most likely need to modify these COPY instructions to make sure all PHP files are copied to the image. Also, as you add new PHP files to your application, make sure to add them to this list as well.</p></blockquote><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Copy PHP files</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> index.php /var/www/html/</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> lib /var/www/html/lib</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">chown</span> -R www-data:www-data /var/www/html</span></span><br></pre></td></tr></table></figure><p>The following instructions tell Docker that PHP-FPM will expose port 9000 and start the PHP-FPM process by default. You do not need to change anything here.</p><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Expose and start PHP-FPM</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">9000</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;php-fpm&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>Next, use the <code>docker build</code> command to create your PHP-FPM image:</p><figure class="highlight awk"><table><tr><td class="code"><pre><span class="line">docker build -t php-basic-php-fpm:latest -f docker<span class="regexp">/php-fpm/</span>Dockerfile .</span><br></pre></td></tr></table></figure><h2 id="What’s-next"><a href="#What’s-next" class="headerlink" title="What’s next?"></a>What’s next?</h2><p>You have successfully built two Docker images: NGINX and PHP-FPM. The following steps are missing to start Docker containers based on your images with AWS Fargate.</p><ol><li>Push both images to a Docker registry (e.g., ECR).</li><li>Create an ECS cluster.</li><li>Create an ECS launch configuration.</li><li>Create an ECS service.</li></ol>]]>
      </content:encoded>
    </item>
    <item>
      <title>Analyze CloudWatch Logs like a pro</title>
      <link>https://cloudonaut.io/analyze-cloudwatch-logs-like-a-pro/</link>
      <description>
        <![CDATA[<blockquote>
<p>This post was originally published on the <a href="https://marbot.io/blog/analyze-cloudwatch-logs-like-a-pro.html" target="_]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <guid isPermaLink="true">https://cloudonaut.io/analyze-cloudwatch-logs-like-a-pro/</guid>
      <pubDate>Tue, 02 Jul 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<blockquote><p>This post was originally published on the <a href="https://marbot.io/blog/analyze-cloudwatch-logs-like-a-pro.html" target="_blank" rel="noopener">marbot blog</a>.</p></blockquote><p>Centralizing the logs from all your systems is critical in a cloud infrastructure. Typical solutions to store and analyze log messages are: Elastic Stack (Elasticsearch + Kibana), Loggly, Splunk, and Sumo Logic.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/07/like-a-pro@730w.webp 730w, /images/2019/07/like-a-pro@730w2x.webp 1460w, /images/2019/07/like-a-pro@610w.webp 610w, /images/2019/07/like-a-pro@610w2x.webp 1220w, /images/2019/07/like-a-pro@450w.webp 450w, /images/2019/07/like-a-pro@450w2x.webp 900w, /images/2019/07/like-a-pro@330w.webp 330w, /images/2019/07/like-a-pro@330w2x.webp 660w, /images/2019/07/like-a-pro@545w.webp 545w, /images/2019/07/like-a-pro@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/07/like-a-pro@730w.jpg 730w, /images/2019/07/like-a-pro@730w2x.jpg 1460w, /images/2019/07/like-a-pro@610w.jpg 610w, /images/2019/07/like-a-pro@610w2x.jpg 1220w, /images/2019/07/like-a-pro@450w.jpg 450w, /images/2019/07/like-a-pro@450w2x.jpg 900w, /images/2019/07/like-a-pro@330w.jpg 330w, /images/2019/07/like-a-pro@330w2x.jpg 660w, /images/2019/07/like-a-pro@545w.jpg 545w, /images/2019/07/like-a-pro@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/07/like-a-pro.jpg" alt="Analyze CloudWatch Logs like a pro" title="Analyze CloudWatch Logs like a pro"></picture></p><p>I prefer Amazon CloudWatch Logs in most cases. Why? Because CloudWatch Logs is a fully-managed service and scales horizontally. Also, CloudWatch Logs is billed by used storage and data ingestion, which means there are no idle costs.</p><p>The analytics functionality of CloudWatch Logs was minimal compared to the competitors. However, AWS released a new feature in November 2018: CloudWatch Logs Insights. You will learn how to analyze your log messages with CloudWatch Logs Insights like a pro in the following.</p><h2 id="What-is-CloudWatch-Logs-Insights"><a href="#What-is-CloudWatch-Logs-Insights" class="headerlink" title="What is CloudWatch Logs Insights?"></a>What is CloudWatch Logs Insights?</h2><p>CloudWatch Logs Insights is an extension of CloudWatch Logs.</p><p>The key benefits of CloudWatch Logs Insights are:</p><ul><li>Fast execution</li><li>Insightful visualization</li><li>Powerful syntax</li></ul><p>Analyzing log messages with CloudWatch Logs Insights costs $0.005 per GB of data scanned (see <a href="https://aws.amazon.com/cloudwatch/pricing/" target="_blank" rel="noopener">CloudWatch pricing</a> for costs in other regions than U.S. East N. Virginia).</p><h2 id="How-to-query-logs"><a href="#How-to-query-logs" class="headerlink" title="How to query logs?"></a>How to query logs?</h2><p>As shown in the following screenshot, five steps are needed to query log messages with CloudWatch Logs Insights.</p><ol><li>Open CloudWatch Logs Insights.</li><li>Select a log group.</li><li>Select a relative or absolute timespan.</li><li>Type in a query.</li><li>Press the <em>Run query</em> button.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/07/cloudwatch-logs-insights-01@730w.webp 730w, /images/2019/07/cloudwatch-logs-insights-01@730w2x.webp 1460w, /images/2019/07/cloudwatch-logs-insights-01@610w.webp 610w, /images/2019/07/cloudwatch-logs-insights-01@610w2x.webp 1220w, /images/2019/07/cloudwatch-logs-insights-01@450w.webp 450w, /images/2019/07/cloudwatch-logs-insights-01@450w2x.webp 900w, /images/2019/07/cloudwatch-logs-insights-01@330w.webp 330w, /images/2019/07/cloudwatch-logs-insights-01@330w2x.webp 660w, /images/2019/07/cloudwatch-logs-insights-01@545w.webp 545w, /images/2019/07/cloudwatch-logs-insights-01@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/07/cloudwatch-logs-insights-01@730w.png 730w, /images/2019/07/cloudwatch-logs-insights-01@730w2x.png 1460w, /images/2019/07/cloudwatch-logs-insights-01@610w.png 610w, /images/2019/07/cloudwatch-logs-insights-01@610w2x.png 1220w, /images/2019/07/cloudwatch-logs-insights-01@450w.png 450w, /images/2019/07/cloudwatch-logs-insights-01@450w2x.png 900w, /images/2019/07/cloudwatch-logs-insights-01@330w.png 330w, /images/2019/07/cloudwatch-logs-insights-01@330w2x.png 660w, /images/2019/07/cloudwatch-logs-insights-01@545w.png 545w, /images/2019/07/cloudwatch-logs-insights-01@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/07/cloudwatch-logs-insights-01.png" alt="CloudWatch Logs Insights: Query" title="CloudWatch Logs Insights: Query"></picture></p><p>The following snippet shows a simple query which fetches all log messages and displays the fields <code>@timestamp</code> and <code>@message</code> - both default fields -  sorted by <code>@timestamp</code>.</p><figure class="highlight mel"><table><tr><td class="code"><pre><span class="line">fields @timestamp, @message</span><br><span class="line">| <span class="keyword">sort</span> @timestamp desc</span><br></pre></td></tr></table></figure><p>CloudWatch Logs supports both plain text messages as well as structured (JSON) messages.</p><h3 id="Query-and-parse-plain-text-log-messages"><a href="#Query-and-parse-plain-text-log-messages" class="headerlink" title="Query and parse plain text log messages"></a>Query and parse plain text log messages</h3><p>The API Gateway sends plain text log messages to CloudWatch Logs. The following snippet shows a  log message indicating that the API Gateway received a response from a downstream integration.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">Received</span> response. Status: <span class="number">200</span>, Integration latency: <span class="number">78</span> ms</span><br></pre></td></tr></table></figure><p>The following query filters only the log messages containing <code>Received response.</code>.</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">fields <span class="variable">@timestamp</span>, <span class="variable">@message</span></span><br><span class="line"><span class="operator">|</span> <span class="keyword">filter</span> <span class="variable">@message</span> <span class="keyword">like</span> <span class="string">&#x27;Received response.&#x27;</span></span><br><span class="line"><span class="operator">|</span> sort <span class="variable">@timestamp</span> <span class="keyword">desc</span></span><br></pre></td></tr></table></figure><p>You can also use a regular expression to filter log messages, as shown in the following example.</p><figure class="highlight mel"><table><tr><td class="code"><pre><span class="line">fields @timestamp, @message</span><br><span class="line">| <span class="keyword">filter</span> @message like /^.*Status\:\s(\d*),\sIntegration\slatency\:\s(\d*)\sms$/</span><br><span class="line">| <span class="keyword">sort</span> @timestamp desc</span><br></pre></td></tr></table></figure><p>To analyze plain text log messages it is helpful to parse essential values. For example, the following query parses the status code <code>@status</code> and latency <code>@latency</code> with the help of a regular expression.</p><figure class="highlight mel"><table><tr><td class="code"><pre><span class="line">fields @timestamp, @message, @latency, @status </span><br><span class="line">| <span class="keyword">filter</span> @message like <span class="string">&#x27;Received response.&#x27;</span></span><br><span class="line">| parse @message /^.*Status\:\s(?&lt;@status&gt;\d*),\sIntegration\slatency\:\s(?&lt;@latency&gt;\d*)\sms$/</span><br><span class="line">| <span class="keyword">filter</span> @latency &gt; <span class="number">100</span> and @status = <span class="number">200</span></span><br><span class="line">| <span class="keyword">sort</span> @latency desc</span><br></pre></td></tr></table></figure><p>When you do have control over the system that produces log messages, I highly recommend sending structured log messages instead of plain text messages. The following section shows queries for JSON log messages.</p><h3 id="Query-JSON-log-messages"><a href="#Query-JSON-log-messages" class="headerlink" title="Query JSON log messages"></a>Query JSON log messages</h3><p>A structured log message contains a log message as well as a JSON object with structured data.</p><p>For example, the log event consist of a message …</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line">Processing <span class="keyword">event</span>.</span><br></pre></td></tr></table></figure><p>… and structured data.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;action&quot;</span><span class="punctuation">:</span> <span class="string">&quot;close&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;stage&quot;</span><span class="punctuation">:</span> <span class="string">&quot;prod&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Querying structured data is much simpler compared to plain text log messages — no need to write regular expressions to filter and parse data.</p><p>The following query filters log messages based on the fields <code>action</code> and <code>stage</code>, both parsed by CloudWatch Logs automatically.</p><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">fields <span class="meta">@timestamp</span>, <span class="meta">@message</span></span><br><span class="line">| <span class="type">filter</span> <span class="variable">action</span> <span class="operator">=</span> <span class="string">&#x27;close&#x27;</span> <span class="type">and</span> <span class="variable">stage</span> <span class="operator">=</span> <span class="string">&#x27;prod&#x27;</span></span><br><span class="line">| sort <span class="meta">@timestamp</span> desc</span><br></pre></td></tr></table></figure><p>It is helpful to sort the log messages by the stream as well. Because otherwise log messages from different Lambda invocations, EC2 instances, … will show up together.</p><figure class="highlight mel"><table><tr><td class="code"><pre><span class="line">fields @timestamp, @message</span><br><span class="line">| <span class="keyword">sort</span> @logStream, @timestamp desc</span><br></pre></td></tr></table></figure><p>Scrolling through endless lines of log messages is not very helpful when debugging. Luckily, you can even visualize log messages with CloudWatch Logs Insights.</p><h2 id="How-to-visualize-logs"><a href="#How-to-visualize-logs" class="headerlink" title="How to visualize logs?"></a>How to visualize logs?</h2><p>The following query creates two statistics to visualize the billed duration of Lambda function invocations: <code>sum</code> the sum of the duration of all invocations, as well as the 95 percentile (<code>pct</code>) the duration of all invocations. The data is grouped into 5-minute buckets.</p><p>Use the query on any log group of a Lambda function.</p><figure class="highlight less"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">fields</span> @<span class="selector-tag">timestamp</span>, @<span class="selector-tag">message</span></span><br><span class="line">| <span class="selector-tag">stats</span> <span class="selector-tag">sum</span>(<span class="variable">@billedDuration</span>), <span class="selector-tag">pct</span>(<span class="variable">@billedDuration</span>, <span class="number">95</span>) <span class="selector-tag">by</span> <span class="selector-tag">bin</span>(<span class="number">5</span>m)</span><br></pre></td></tr></table></figure><p>The following screenshot shows the result of the visualized query.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/07/cloudwatch-logs-insights-02@730w.webp 730w, /images/2019/07/cloudwatch-logs-insights-02@730w2x.webp 1460w, /images/2019/07/cloudwatch-logs-insights-02@610w.webp 610w, /images/2019/07/cloudwatch-logs-insights-02@610w2x.webp 1220w, /images/2019/07/cloudwatch-logs-insights-02@450w.webp 450w, /images/2019/07/cloudwatch-logs-insights-02@450w2x.webp 900w, /images/2019/07/cloudwatch-logs-insights-02@330w.webp 330w, /images/2019/07/cloudwatch-logs-insights-02@330w2x.webp 660w, /images/2019/07/cloudwatch-logs-insights-02@545w.webp 545w, /images/2019/07/cloudwatch-logs-insights-02@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/07/cloudwatch-logs-insights-02@730w.png 730w, /images/2019/07/cloudwatch-logs-insights-02@730w2x.png 1460w, /images/2019/07/cloudwatch-logs-insights-02@610w.png 610w, /images/2019/07/cloudwatch-logs-insights-02@610w2x.png 1220w, /images/2019/07/cloudwatch-logs-insights-02@450w.png 450w, /images/2019/07/cloudwatch-logs-insights-02@450w2x.png 900w, /images/2019/07/cloudwatch-logs-insights-02@330w.png 330w, /images/2019/07/cloudwatch-logs-insights-02@330w2x.png 660w, /images/2019/07/cloudwatch-logs-insights-02@545w.png 545w, /images/2019/07/cloudwatch-logs-insights-02@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/07/cloudwatch-logs-insights-02.png" alt="CloudWatch Logs Insights: Visualization" title="CloudWatch Logs Insights: Visualization"></picture></p><p>Visualizing logs is also possible with plain text log messages.</p><p>You already got to know the log message from the API Gateway before.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">Received</span> response. Status: <span class="number">200</span>, Integration latency: <span class="number">78</span> ms</span><br></pre></td></tr></table></figure><p>The following query creates a visualization including the count of responses as well as the 95 percentile of the latency.</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">fields <span class="variable">@timestamp</span>, <span class="variable">@message</span>, <span class="variable">@latency</span>, <span class="variable">@status</span> </span><br><span class="line"><span class="operator">|</span> <span class="keyword">filter</span> <span class="variable">@message</span> <span class="keyword">like</span> <span class="string">&#x27;Received response.&#x27;</span></span><br><span class="line"><span class="operator">|</span> parse <span class="variable">@message</span> <span class="operator">/</span><span class="operator">^</span>.<span class="operator">*</span>Status\:\s(?<span class="operator">&lt;</span><span class="variable">@status</span><span class="operator">&gt;</span>\d<span class="operator">*</span>),\sIntegration\slatency\:\s(?<span class="operator">&lt;</span><span class="variable">@latency</span><span class="operator">&gt;</span>\d<span class="operator">*</span>)\sms$<span class="operator">/</span></span><br><span class="line"><span class="operator">|</span> stats <span class="built_in">count</span>(<span class="operator">*</span>), pct(<span class="variable">@latency</span>, <span class="number">95</span>) <span class="keyword">by</span> bin(<span class="number">15</span>m)</span><br></pre></td></tr></table></figure><h2 id="Limitations"><a href="#Limitations" class="headerlink" title="Limitations"></a>Limitations</h2><p>Compared to other solutions like  Elastic Stack (Elasticsearch + Kibana), Loggly, Splunk, and Sumo Logic, CloudWatch Logs Insights has a few limitations:</p><ul><li>A query cannot analyze data from multiple log groups.</li><li>The ability to visualize data is limited.</li></ul><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>The query and visualization capabilities of <strong>Insights</strong> have upgraded <strong>CloudWatch Logs</strong> substantially. The fact that CloudWatch Logs and Insights is billed per usage (storage, data ingestion, analyzed data) is a huge benefit.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>School's Out For Summer</title>
      <link>https://cloudonaut.io/schools-out-for-summer/</link>
      <description>
        <![CDATA[<p>Michael and I have just returned from a trip to France where we enjoyed the sun, the beach, the pool, and - of course - the cheese.</p>
<]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/schools-out-for-summer/</guid>
      <pubDate>Mon, 01 Jul 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Michael and I have just returned from a trip to France where we enjoyed the sun, the beach, the pool, and - of course - the cheese.</p><p>We are giving away 25 printed books <a href="https://www.manning.com/books/amazon-web-services-in-action-second-edition" target="_blank" rel="noopener">Amazon Web Services in Action</a> to students who are keen to get started with AWS during their summer break. We will schedule a Ask Me Anything (AMA) remote session for all 25 students at the end of summer as well.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/07/summer@730w.webp 730w, /images/2019/07/summer@730w2x.webp 1460w, /images/2019/07/summer@610w.webp 610w, /images/2019/07/summer@610w2x.webp 1220w, /images/2019/07/summer@450w.webp 450w, /images/2019/07/summer@450w2x.webp 900w, /images/2019/07/summer@330w.webp 330w, /images/2019/07/summer@330w2x.webp 660w, /images/2019/07/summer@545w.webp 545w, /images/2019/07/summer@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/07/summer@730w.jpg 730w, /images/2019/07/summer@730w2x.jpg 1460w, /images/2019/07/summer@610w.jpg 610w, /images/2019/07/summer@610w2x.jpg 1220w, /images/2019/07/summer@450w.jpg 450w, /images/2019/07/summer@450w2x.jpg 900w, /images/2019/07/summer@330w.jpg 330w, /images/2019/07/summer@330w2x.jpg 660w, /images/2019/07/summer@545w.jpg 545w, /images/2019/07/summer@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/07/summer.jpg" alt="School's Out For Summer" title="School's Out For Summer"></picture></p><p>Want to participate? Please send an email with the following information to <a href="mailto:&#104;&#101;&#x6c;&#x6c;&#111;&#x40;&#99;&#108;&#x6f;&#x75;&#x64;&#x6f;&#x6e;&#x61;&#117;&#x74;&#x2e;&#x69;&#111;">hello@cloudonaut.io</a> until July 7th.</p><ul><li>Your first name.</li><li>Your school or university.</li><li>Your email address.</li><li>Your international postal address.</li></ul><p><strong>Please share this initiative within your network.</strong></p><p>And don’t forget to enjoy your summer break!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS CloudTrail: your audit log is incomplete</title>
      <link>https://cloudonaut.io/aws-cloudtrail-your-audit-log-is-incomplete/</link>
      <description>
        <![CDATA[<p>Recently, I was investigating the size of a security breach caused by leaked AWS credentials. The first place to go in such a scenario is]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/cloudtrail/">cloudtrail</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-cloudtrail-your-audit-log-is-incomplete/</guid>
      <pubDate>Wed, 26 Jun 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Recently, I was investigating the size of a security breach caused by leaked AWS credentials. The first place to go in such a scenario is the audit log recorded by CloudTrail. When configured correctly, CloudTrail captures the requests to the AWS API and stores them on S3 or forwards them to CloudWatch Logs. Analyzing the audit log allows you to answer questions like:</p><ul><li>Did IAM user <code>xyz</code> change any parts of the cloud infrastructure within the last seven days?</li><li>Who has made changes to the security groups within the AWS account?</li><li>Did anyone start an EC2 instance in <code>us-east-1</code>?</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/06/auditlog@730w.webp 730w, /images/2019/06/auditlog@730w2x.webp 1460w, /images/2019/06/auditlog@610w.webp 610w, /images/2019/06/auditlog@610w2x.webp 1220w, /images/2019/06/auditlog@450w.webp 450w, /images/2019/06/auditlog@450w2x.webp 900w, /images/2019/06/auditlog@330w.webp 330w, /images/2019/06/auditlog@330w2x.webp 660w, /images/2019/06/auditlog@545w.webp 545w, /images/2019/06/auditlog@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/06/auditlog@730w.jpg 730w, /images/2019/06/auditlog@730w2x.jpg 1460w, /images/2019/06/auditlog@610w.jpg 610w, /images/2019/06/auditlog@610w2x.jpg 1220w, /images/2019/06/auditlog@450w.jpg 450w, /images/2019/06/auditlog@450w2x.jpg 900w, /images/2019/06/auditlog@330w.jpg 330w, /images/2019/06/auditlog@330w2x.jpg 660w, /images/2019/06/auditlog@545w.jpg 545w, /images/2019/06/auditlog@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/06/auditlog.jpg" alt="AWS CloudTrail: your audit log is incomplete" title="AWS CloudTrail: your audit log is incomplete"></picture></p><p>So far, so good. Based on the CloudTrail logs, no one was using the leaked AWS credentials to modify the cloud infrastructure. But unfortunately, I could not answer the question, whether someone was using the AWS credentials to download sensitive data from S3.</p><p>Why not? Because of poor defaults and an unfortunate price model. By default, CloudTrail does not capture read and write requests to S3 - so-called data events. I assume that is because AWS is charging $0.10 per 100,000 data events.</p><p>I highly encourage you to check your CloudTrail configuration and enable capturing data events for all S3 buckets. Or at least for the S3 buckets, that may contain sensitive data.</p><p>Read on to learn how to configure CloudTrail with the AWS Management Console or AWS CloudFormation.</p><h2 id="AWS-Management-Console-enable-S3-data-events"><a href="#AWS-Management-Console-enable-S3-data-events" class="headerlink" title="AWS Management Console: enable S3 data events"></a>AWS Management Console: enable S3 data events</h2><ol><li>Open the AWS Management Console.</li><li>Go to the CloudTrail service.</li><li>Edit an existing trail or create a new one.</li><li>Enable data events for S3 buckets, as shown in the following screenshot.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/06/cloudtrail-enable-data-events@730w.webp 730w, /images/2019/06/cloudtrail-enable-data-events@730w2x.webp 1460w, /images/2019/06/cloudtrail-enable-data-events@610w.webp 610w, /images/2019/06/cloudtrail-enable-data-events@610w2x.webp 1220w, /images/2019/06/cloudtrail-enable-data-events@450w.webp 450w, /images/2019/06/cloudtrail-enable-data-events@450w2x.webp 900w, /images/2019/06/cloudtrail-enable-data-events@330w.webp 330w, /images/2019/06/cloudtrail-enable-data-events@330w2x.webp 660w, /images/2019/06/cloudtrail-enable-data-events@545w.webp 545w, /images/2019/06/cloudtrail-enable-data-events@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/06/cloudtrail-enable-data-events@730w.png 730w, /images/2019/06/cloudtrail-enable-data-events@730w2x.png 1460w, /images/2019/06/cloudtrail-enable-data-events@610w.png 610w, /images/2019/06/cloudtrail-enable-data-events@610w2x.png 1220w, /images/2019/06/cloudtrail-enable-data-events@450w.png 450w, /images/2019/06/cloudtrail-enable-data-events@450w2x.png 900w, /images/2019/06/cloudtrail-enable-data-events@330w.png 330w, /images/2019/06/cloudtrail-enable-data-events@330w2x.png 660w, /images/2019/06/cloudtrail-enable-data-events@545w.png 545w, /images/2019/06/cloudtrail-enable-data-events@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/06/cloudtrail-enable-data-events.png" alt="AWS Management Console: enable S3 data events" title="AWS Management Console: enable S3 data events"></picture></p><h2 id="AWS-CloudFormation-enable-S3-data-events"><a href="#AWS-CloudFormation-enable-S3-data-events" class="headerlink" title="AWS CloudFormation: enable S3 data events"></a>AWS CloudFormation: enable S3 data events</h2><p>The following code snippet illustrates how to create a trail which stores audit logs and S3 and captures data events from all S3 buckets.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">TrailBucket:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::S3::Bucket&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">TrailBucketPolicy:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::S3::BucketPolicy&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Bucket:</span> <span class="type">!Ref</span> <span class="string">TrailBucket</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">Service:</span> <span class="string">&#x27;cloudtrail.amazonaws.com&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;s3:GetBucketAcl&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:s3:::$&#123;TrailBucket&#125;&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">Service:</span> <span class="string">&#x27;cloudtrail.amazonaws.com&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;s3:PutObject&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:s3:::$&#123;TrailBucket&#125;/AWSLogs/$&#123;AWS::AccountId&#125;/*&#x27;</span></span><br><span class="line">          <span class="attr">Condition:</span></span><br><span class="line">            <span class="attr">StringEquals:</span></span><br><span class="line">              <span class="attr">&#x27;s3:x-amz-acl&#x27;:</span> <span class="string">&#x27;bucket-owner-full-control&#x27;</span></span><br><span class="line">  <span class="attr">Trail:</span></span><br><span class="line">    <span class="attr">DependsOn:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">TrailBucketPolicy</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudTrail::Trail&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">IncludeGlobalServiceEvents:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">IsLogging:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">IsMultiRegionTrail:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">EventSelectors:</span> [</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="attr">DataResources:</span> [&#123;<span class="attr">Type:</span> <span class="string">&#x27;AWS::S3::Object&#x27;</span>, <span class="attr">Values:</span> [<span class="string">&#x27;arn:aws:s3:::&#x27;</span>]&#125;],</span><br><span class="line">          <span class="attr">IncludeManagementEvents:</span> <span class="literal">true</span>,</span><br><span class="line">          <span class="attr">ReadWriteType:</span> <span class="string">All</span></span><br><span class="line">        &#125;</span><br><span class="line">      ]</span><br><span class="line">      <span class="attr">S3BucketName:</span> <span class="type">!Ref</span> <span class="string">TrailBucket</span></span><br></pre></td></tr></table></figure><p>However, keep in mind that recording data events add some extra costs.</p><ul><li>CloudTrail: $0.10 per 100,000 data events</li><li>S3: additional write requests and storage</li><li>CloudWatch: additional data ingestion and storage</li></ul><p>So please do the math, before enabling data events for high-throughput S3 buckets. And keep an eye on your AWS costs.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>I highly recommend enabling capturing S3 data events with CloudTrail. Doing so allows you to find out who was reading or writing data to S3. However, keep in mind that data events add substantial extra costs for high-throughput S3 buckets.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>EFS with TLS behind a proxy</title>
      <link>https://cloudonaut.io/efs-with-tls-behind-a-proxy/</link>
      <description>
        <![CDATA[<p>Encryption of data at rest and in transit is the new normal. Or as Werner Vogels (Amazon, CTO) says: “Dance like nobody’s watching. Encry]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/efs/">efs</category>
      <guid isPermaLink="true">https://cloudonaut.io/efs-with-tls-behind-a-proxy/</guid>
      <pubDate>Wed, 19 Jun 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Encryption of data at rest and in transit is the new normal. Or as Werner Vogels (Amazon, CTO) says: “Dance like nobody’s watching. Encrypt like everyone is.” The <em>Amazon Elastic File System (EFS)</em> supports both: encryption at rest and encryption in transit.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/06/network@730w.webp 730w, /images/2019/06/network@730w2x.webp 1460w, /images/2019/06/network@610w.webp 610w, /images/2019/06/network@610w2x.webp 1220w, /images/2019/06/network@450w.webp 450w, /images/2019/06/network@450w2x.webp 900w, /images/2019/06/network@330w.webp 330w, /images/2019/06/network@330w2x.webp 660w, /images/2019/06/network@545w.webp 545w, /images/2019/06/network@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/06/network@730w.jpg 730w, /images/2019/06/network@730w2x.jpg 1460w, /images/2019/06/network@610w.jpg 610w, /images/2019/06/network@610w2x.jpg 1220w, /images/2019/06/network@450w.jpg 450w, /images/2019/06/network@450w2x.jpg 900w, /images/2019/06/network@330w.jpg 330w, /images/2019/06/network@330w2x.jpg 660w, /images/2019/06/network@545w.jpg 545w, /images/2019/06/network@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/06/network.jpg" alt="EFS Encryption In Transit" title="EFS Encryption In Transit"></picture></p><p>As often, the tooling for the cloud does not deal very well with the fact that there isn’t unrestricted Internet connectivity from every subnet. This article explains how to set up an encrypted connection from an EC2 instance - running in a subnet without a route to an Internet Gateway or NAT gateway - to EFS. That includes scenarios, where your EC2 instance is required to use a proxy for HTTP and HTTPS connections.</p><p>How does encryption in transit from EC2 to EFS work? First, the <code>efs-utils</code> tool configures and starts <code>stunnel</code> to establish a TLS connection between the instance and the file system. Next, the tool creates an NFS mount. The following figure illustrates the architecture.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/06/efs-tls@730w.webp 730w, /images/2019/06/efs-tls@730w2x.webp 1460w, /images/2019/06/efs-tls@610w.webp 610w, /images/2019/06/efs-tls@610w2x.webp 1220w, /images/2019/06/efs-tls@450w.webp 450w, /images/2019/06/efs-tls@450w2x.webp 900w, /images/2019/06/efs-tls@330w.webp 330w, /images/2019/06/efs-tls@330w2x.webp 660w, /images/2019/06/efs-tls@545w.webp 545w, /images/2019/06/efs-tls@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/06/efs-tls@730w.png 730w, /images/2019/06/efs-tls@730w2x.png 1460w, /images/2019/06/efs-tls@610w.png 610w, /images/2019/06/efs-tls@610w2x.png 1220w, /images/2019/06/efs-tls@450w.png 450w, /images/2019/06/efs-tls@450w2x.png 900w, /images/2019/06/efs-tls@330w.png 330w, /images/2019/06/efs-tls@330w2x.png 660w, /images/2019/06/efs-tls@545w.png 545w, /images/2019/06/efs-tls@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/06/efs-tls.png" alt="Use efs-util to create EFS mount with TLS" title="Use efs-util to create EFS mount with TLS"></picture></p><p>How to mount an EFS file system with TLS behind a proxy or from a subnet without Internet connectivity? First of all, you need to install the <code>efs-utils</code> tool on your EC2 instance.</p><figure class="highlight cmake"><table><tr><td class="code"><pre><span class="line">yum <span class="keyword">install</span> -y amazon-efs-utils</span><br></pre></td></tr></table></figure><p>Next, you need to mount your EFS file system. All you need is the ID of your file system and the following command. Note, the <code>-o tls</code> parameter tells the <code>efs-util</code> to establish a TLS connection to EFS.</p><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="attribute">mount</span> -t efs -o tls fs-222121ea:/ efs</span><br></pre></td></tr></table></figure><p>Unfortunately, the command will fail when your EC2 instance is not running in a subnet with a route to an Internet gateway or NAT gateway.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">mount.nfs4: <span class="keyword">Connection</span> <span class="keyword">reset</span> <span class="keyword">by</span> peer</span><br><span class="line">Failed <span class="keyword">to</span> initialize TLS tunnel <span class="keyword">for</span> fs<span class="number">-222121</span>ea</span><br></pre></td></tr></table></figure><p>Why is that? The <code>efs-util</code> uses <code>stunnel</code> to establish a TLS connection between your EC2 instance and EFS. By default, <code>stunnel</code> uses OCSP (Online Certificate Status Protocol ) to validate the certificate used by EFS. Doing so requires an Internet connection. Even worse, <code>stunnel</code> does not support an HTTP&#x2F;HTTPS proxy for OCSP.</p><blockquote><p>As of <a href="https://aws.amazon.com/about-aws/whats-new/2019/07/configuration-update-for-amazon-efs-encryption-data-in-transit/" target="_blank" rel="noopener">Jul 23, 2019</a>, Amazon no longer enables OCSP by default. </p></blockquote><p>But there is a workaround for the problem: disabling OCSP. To do so, open the file <code>/etc/amazon/efs/efs-utils.conf</code> with the editor of your choice.</p><figure class="highlight awk"><table><tr><td class="code"><pre><span class="line">vi <span class="regexp">/etc/</span>amazon<span class="regexp">/efs/</span>efs-utils.conf</span><br></pre></td></tr></table></figure><p>Search for the configuration parameter <code>stunnel_check_cert_validity</code> and set the value from <code>true</code> to <code>false</code>.</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># Copyright 2017-2018 Amazon.com, Inc. and its affiliates. All Rights Reserved.</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># Licensed under the MIT License. See the LICENSE accompanying this file</span></span><br><span class="line"><span class="comment"># for the specific language governing permissions and limitations under</span></span><br><span class="line"><span class="comment"># the License.</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"></span><br><span class="line"><span class="section">[DEFAULT]</span></span><br><span class="line"><span class="attr">logging_level</span> = INFO</span><br><span class="line"><span class="attr">logging_max_bytes</span> = <span class="number">1048576</span></span><br><span class="line"><span class="attr">logging_file_count</span> = <span class="number">10</span></span><br><span class="line"><span class="comment"># mode for /var/run/efs in octal</span></span><br><span class="line"><span class="attr">state_file_dir_mode</span> = <span class="number">750</span></span><br><span class="line"></span><br><span class="line"><span class="section">[mount]</span></span><br><span class="line"><span class="attr">dns_name_format</span> = &#123;fs_id&#125;.efs.&#123;region&#125;.amazonaws.com</span><br><span class="line"><span class="attr">stunnel_debug_enabled</span> = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Validate the certificate hostname  on mount. This option is not supported by certain stunnel versions.</span></span><br><span class="line"><span class="attr">stunnel_check_cert_hostname</span> = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Use OCSP to check certificate validity. This option is not supported by certain stunnel versions.</span></span><br><span class="line"><span class="attr">stunnel_check_cert_validity</span> = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Define the port range that the TLS tunnel will choose from</span></span><br><span class="line"><span class="attr">port_range_lower_bound</span> = <span class="number">20049</span></span><br><span class="line"><span class="attr">port_range_upper_bound</span> = <span class="number">20449</span></span><br><span class="line"></span><br><span class="line"><span class="section">[mount-watchdog]</span></span><br><span class="line"><span class="attr">enabled</span> = <span class="literal">true</span></span><br><span class="line"><span class="attr">poll_interval_sec</span> = <span class="number">1</span></span><br><span class="line"><span class="attr">unmount_grace_period_sec</span> = <span class="number">30</span></span><br></pre></td></tr></table></figure><p>Afterward, try to mount the EFS again.</p><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="attribute">mount</span> -t efs -o tls fs-222121ea:/ efs</span><br></pre></td></tr></table></figure><p>Works now!</p><p>In my opinion, it is fine to disable the certification validation as the risk of a man-in-the-middle attack is very low inside your VPC. The authenticity provided by TLS is not needed, because it is already provided by the VPC service.</p><p>AWS is aware of the problem. You might want to check out the <a href="https://github.com/aws/efs-utils/issues/30" target="_blank" rel="noopener">GitHub issue</a> discussing the <code>stunnel</code> limitation.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS SSM is a trojan horse: fix it now!</title>
      <link>https://cloudonaut.io/aws-ssm-is-a-trojan-horse-fix-it-now/</link>
      <description>
        <![CDATA[<p>Recently, I held a security workshop together with a team of engineers. At some point, the team demonstrated how they use <a href="https:]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <category domain="https://cloudonaut.io/tag/ssm/">ssm</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-ssm-is-a-trojan-horse-fix-it-now/</guid>
      <pubDate>Tue, 11 Jun 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Recently, I held a security workshop together with a team of engineers. At some point, the team demonstrated how they use <a href="https://aws.amazon.com/systems-manager/" target="_blank" rel="noopener">AWS Systems Manager (SSM)</a> to run commands on a machine. What the team didn’t know: they enabled a backdoor that allows everyone with access to the AWS account to run commands on every EC2 instance as root. On top of it, they granted full read and write permissions to every S3 bucket and full write access to CloudWatch Logs. One team member nailed it: we installed a trojan horse! Read on to understand how the backdoor works and how you can close it.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/06/trojan-horse@730w.webp 730w, /images/2019/06/trojan-horse@730w2x.webp 1460w, /images/2019/06/trojan-horse@610w.webp 610w, /images/2019/06/trojan-horse@610w2x.webp 1220w, /images/2019/06/trojan-horse@450w.webp 450w, /images/2019/06/trojan-horse@450w2x.webp 900w, /images/2019/06/trojan-horse@330w.webp 330w, /images/2019/06/trojan-horse@330w2x.webp 660w, /images/2019/06/trojan-horse@545w.webp 545w, /images/2019/06/trojan-horse@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/06/trojan-horse@730w.jpg 730w, /images/2019/06/trojan-horse@730w2x.jpg 1460w, /images/2019/06/trojan-horse@610w.jpg 610w, /images/2019/06/trojan-horse@610w2x.jpg 1220w, /images/2019/06/trojan-horse@450w.jpg 450w, /images/2019/06/trojan-horse@450w2x.jpg 900w, /images/2019/06/trojan-horse@330w.jpg 330w, /images/2019/06/trojan-horse@330w2x.jpg 660w, /images/2019/06/trojan-horse@545w.jpg 545w, /images/2019/06/trojan-horse@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/06/trojan-horse.jpg" alt="AWS SSM is a trojan horse" title="AWS SSM is a trojan horse"></picture></p><h2 id="How-the-trojan-horse-works"><a href="#How-the-trojan-horse-works" class="headerlink" title="How the trojan horse works"></a>How the trojan horse works</h2><p>The three steps to enable the trojan horse:</p><ol><li>SSM is a handy service to <a href="/goodbye-ssh-use-aws-session-manager-instead/">replace SSH</a>, patch your OS, and much more. To use SSM, you have to install the <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html" target="_blank" rel="noopener">SSM agent</a> on your EC2 instances. Amazon Linux 2 comes with the SSM agent pre-installed and pre-started. The SSM agent runs with root privileges.</li><li>You also have to grant your EC2 instances permissions to talk to the SSM API. The docs suggest that you attach the <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/setup-instance-profile.html" target="_blank" rel="noopener">managed policy AmazonEC2RoleforSSM</a> to your IAM instance (using an IAM instance profile with an IAM role).</li><li>To use SSM, your IAM user or role also needs permissions. Very likely, you have those permissions thanks to managed policies like <code>AdministratorAccess</code>, <code>PowerUserAccess</code>, or <code>AmazonSSMFullAccess</code>.</li></ol><p>You can now use SSM Run Commands or Session Manager to execute any command on any EC2 instance as root. Even better, all commands that are executed use the permissions of the EC2 instance where it runs on. Thanks to <code>AmazonEC2RoleforSSM</code> you can now read all the data from S3, or do you prefer to override some data stored on S3, or you might want to inject some nonsense logs? No problem.</p><h2 id="Protect-yourself-limit-access-to-SSM"><a href="#Protect-yourself-limit-access-to-SSM" class="headerlink" title="Protect yourself: limit access to SSM"></a>Protect yourself: limit access to SSM</h2><p>You have to be very careful about the following permissions which can be used to execute a command on an EC2 instance via the SSM agent:</p><ul><li><code>ssm:CreateAssociation</code></li><li><code>ssm:CreateAssociationBatch</code></li><li><code>ssm:RegisterTargetWithMaintenanceWindow</code></li><li><code>ssm:RegisterTaskWithMaintenanceWindow</code></li><li><code>ssm:SendCommand</code></li><li><code>ssm:StartAutomationExecution</code></li><li><code>ssm:StartSession</code></li><li><code>ssm:UpdateMaintenanceWindowTarget</code></li><li><code>ssm:UpdateMaintenanceWindowTask</code></li></ul><p>Fine, so we need to restrict access to these actions to certain EC2 instances only. Unfortunately, the documentation of the <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/list_awssystemsmanager.html" target="_blank" rel="noopener">resource-level permissions and conditions</a> is incomplete. It is unclear whether it is possible to restrict all of the actions to certain EC2 instances. AWS support could not help as well. Unacceptable!</p><blockquote><p>If you still want to use SSM, I recommend that you allow <code>ssm:SendCommand</code> and <code>ssm:StartSession</code> only for specific tags using permissions. You also have to ensure that tagging is restricted! Otherwise, the tag-based restriction is pointless.</p></blockquote><p>Assuming that multiple workloads run in the same account and you want to restrict access to only some engineers I highly recommend to put each workload in a separate AWS account. It’s unlikely that you can separate the permission properly in the same AWS account.</p><h2 id="Protect-yourself-limit-permissions-of-the-SSM-agent"><a href="#Protect-yourself-limit-permissions-of-the-SSM-agent" class="headerlink" title="Protect yourself: limit permissions of the SSM agent"></a>Protect yourself: limit permissions of the SSM agent</h2><p>Let’s look at the <code>AmazonEC2RoleforSSM</code> policy statements in more details. Remember, you attached the managed policy to your EC2 instance to allow the SSM agent to talk to the AWS API.</p><p>The first statement allows your EC2 instance to report data to SSM. But it is not restricted to a specific instance. You (or an attacker on the machine) can send inventory and compliance data not just in its name. You can do this for every instance id. I don’t see how this can help me to ensure compliance, sorry.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;ssm:PutInventory&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;ssm:PutComplianceItems&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;ssm:PutConfigurePackageResult&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;ssm:UpdateAssociationStatus&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;ssm:UpdateInstanceAssociationStatus&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;ssm:UpdateInstanceInformation&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>The next statement grants you access to write logs to and CloudWatch log group!</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;logs:CreateLogGroup&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;logs:CreateLogStream&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;logs:DescribeLogGroups&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;logs:DescribeLogStreams&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;logs:PutLogEvents&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>And finally, you can read and write from all S3 buckets.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;s3:GetBucketLocation&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;s3:PutObject&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;s3:GetObject&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;s3:GetEncryptionConfiguration&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;s3:AbortMultipartUpload&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;s3:ListMultipartUploadParts&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;s3:ListBucket&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;s3:ListBucketMultipartUploads&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>I suggest that you do not use the <code>AmazonEC2RoleforSSM</code>. Instead, to make Run Commands and Session Manager work, the following policy grants enough permissions:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;ssmmessages:*&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;ssm:UpdateInstanceInformation&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;ec2messages:*&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Depending on the SSM documents you run, you might need to add additional permissions. I created an SSM example for the <a href="https://github.com/cfn-modules/docs/blob/master/examples/ec2-ssm/example.yml#L35" target="_blank" rel="noopener">cfn-modules</a> project.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Be careful when using any defaults. Especially when it comes to managed IAM policies provided by AWS. To restrict access to SSM and your EC2 instance, consider the following steps:</p><ol><li>If you can, use multiple AWS accounts to separate workloads. As soon as they run in a single AWS account, it’s unlikely that you can separate SSM permissions properly.</li><li>Are you not using SSM? Stop and remove the pre-installed SSM agent from your EC2 instances.</li><li>Do not attach the managed policy <code>AmazonEC2RoleforSSM</code> to any of your EC2 instances.</li><li>If you use SSM, restrict your engineer’s permissions to SSM. Never grant access to <code>ssm:*</code>. And make sure to replace the managed policy <code>AmazonEC2RoleforSSM</code> with a policy customized to your needs.</li></ol><hr><p>Are you interested in an AWS security workshop or review? Get in touch: <a href="mailto:&#104;&#101;&#x6c;&#x6c;&#111;&#x40;&#x63;&#x6c;&#x6f;&#117;&#x64;&#111;&#x6e;&#97;&#x75;&#116;&#46;&#105;&#x6f;">hello@cloudonaut.io</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>More than 25 SSL certificates with ECS</title>
      <link>https://cloudonaut.io/more-than-25-ssl-certificates-with-ecs/</link>
      <description>
        <![CDATA[<p>Both the Application Load Balancer (ALB) and the Network Load Balancer (NLB) provide TLS&#x2F;HTTPS listeners allowing you to encrypt the]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <guid isPermaLink="true">https://cloudonaut.io/more-than-25-ssl-certificates-with-ecs/</guid>
      <pubDate>Wed, 05 Jun 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Both the Application Load Balancer (ALB) and the Network Load Balancer (NLB) provide TLS&#x2F;HTTPS listeners allowing you to encrypt the data in transit from the clients to your cloud infrastructure. Together, with the Amazon Certificate Manager (ACM) which provides public SSL certificates for free with little operational effort, AWS is doing a great job to make the Internet a safer place.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/06/encryption@730w.webp 730w, /images/2019/06/encryption@730w2x.webp 1460w, /images/2019/06/encryption@610w.webp 610w, /images/2019/06/encryption@610w2x.webp 1220w, /images/2019/06/encryption@450w.webp 450w, /images/2019/06/encryption@450w2x.webp 900w, /images/2019/06/encryption@330w.webp 330w, /images/2019/06/encryption@330w2x.webp 660w, /images/2019/06/encryption@545w.webp 545w, /images/2019/06/encryption@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/06/encryption@730w.jpg 730w, /images/2019/06/encryption@730w2x.jpg 1460w, /images/2019/06/encryption@610w.jpg 610w, /images/2019/06/encryption@610w2x.jpg 1220w, /images/2019/06/encryption@450w.jpg 450w, /images/2019/06/encryption@450w2x.jpg 900w, /images/2019/06/encryption@330w.jpg 330w, /images/2019/06/encryption@330w2x.jpg 660w, /images/2019/06/encryption@545w.jpg 545w, /images/2019/06/encryption@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/06/encryption.jpg" alt="Encryption of data-in-transig" title="Encryption of data-in-transig"></picture></p><p>However, there is an annoying limitation: the combination of the Amazon Elastic Container Service (ECS) and ALB&#x2F;NLB does not support more than 25 certificates per ECS service. Which typically limits the maximum number of public domain names per ECS service to 25 as well.</p><p>Why is that? Each load balancer supports no more than 25 certificates. On top of that, the Amazon Elastic Container Service (ECS) does only allow registering a container (task) at a single load balancer.</p><blockquote><p>A side note: I’ve asked the AWS support to increase the limit of 25 certificates per load balancer. But AWS rejected my request, and I don’t understand why. I cannot think of a technical limitation. Please let me know if you know or can think of the reason for the hard limit of 25 certificates per load balancer.</p></blockquote><p>So what to do if you need more than 25 certificates to encrypt the data in transit to your ECS service?</p><p>Of course, you can decide not to use the ALB&#x2F;NLB and operate your own load balancer instead. You might want to check out <a href="https://github.com/GUI/lua-resty-auto-ssl" target="_blank" rel="noopener">lua-resty-auto-ssl</a>, a plugin for NGINX. Or Traefik which integrates Let’s Encrypt &amp; Docker.</p><p>But, operating a customized load balancer infrastructure is a challenge. So, I came up with the workaround - illustrated in the following figure - using AWS building blocks only.</p><ul><li><code>1..n</code> ALB with up to 25 certificates each</li><li><code>1</code> NLB</li><li><code>1</code> ECS service which registers its task at the NLB</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/06/ssl-alb-nlb@730w.webp 730w, /images/2019/06/ssl-alb-nlb@730w2x.webp 1460w, /images/2019/06/ssl-alb-nlb@610w.webp 610w, /images/2019/06/ssl-alb-nlb@610w2x.webp 1220w, /images/2019/06/ssl-alb-nlb@450w.webp 450w, /images/2019/06/ssl-alb-nlb@450w2x.webp 900w, /images/2019/06/ssl-alb-nlb@330w.webp 330w, /images/2019/06/ssl-alb-nlb@330w2x.webp 660w, /images/2019/06/ssl-alb-nlb@545w.webp 545w, /images/2019/06/ssl-alb-nlb@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/06/ssl-alb-nlb@730w.png 730w, /images/2019/06/ssl-alb-nlb@730w2x.png 1460w, /images/2019/06/ssl-alb-nlb@610w.png 610w, /images/2019/06/ssl-alb-nlb@610w2x.png 1220w, /images/2019/06/ssl-alb-nlb@450w.png 450w, /images/2019/06/ssl-alb-nlb@450w2x.png 900w, /images/2019/06/ssl-alb-nlb@330w.png 330w, /images/2019/06/ssl-alb-nlb@330w2x.png 660w, /images/2019/06/ssl-alb-nlb@545w.png 545w, /images/2019/06/ssl-alb-nlb@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/06/ssl-alb-nlb.png" alt="SSL for ECS with ALB and NLB" title="SSL for ECS with ALB and NLB"></picture></p><p>Each ALB supports only 25 certificates. But you can add as many ALBs as needed to the cloud infrastructure. The static private IP addresses of the NLB are registered as the targets of each ALB. The NLB checks the health of the ECS tasks by sending HTTP requests.</p><p>To be fair, this workaround has some downsides as well.</p><ul><li>Additional costs for the NLB (operating hours, processed traffic, …).</li><li>The NLB health checks are a lot slower when checking the health of new tasks which increases the waiting time during a deployment (up to 5 minutes).</li><li>The ALB tries to keep connections to your ECS service alive between multiple requests. ECS drains containers from the NLB, but doing so does not close open connections from the ALB. You need to make sure that the container is gracefully shutting down all open connections.</li><li>The security group of the ECS instance or task allows incoming traffic from the whole IP address range of the ALBs. Referencing the security group of the ALBs is currently not supported.</li></ul><p>Besides these downsides, the solution with multiple ALBs pointing to a single NLB works fine as a workaround for the 25 certificates per load balancer limit.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Cronjob at the edge with AWS IoT</title>
      <link>https://cloudonaut.io/cronjob-at-the-edge-with-aws-iot/</link>
      <description>
        <![CDATA[<p>I’m working on a project where I have to manage a cronjob that runs on a small computer with an unreliable Internet connection. I want to]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/iot/">iot</category>
      <guid isPermaLink="true">https://cloudonaut.io/cronjob-at-the-edge-with-aws-iot/</guid>
      <pubDate>Wed, 29 May 2019 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>I’m working on a project where I have to manage a cronjob that runs on a small computer with an unreliable Internet connection. I want to configure the cronjob schedule expression (e.g., <code>0 10 * * *</code>) remotely but the cronjob should not be interrupted if the Internet connection fails. If an Internet connection is available, I would also like to report if the cronjob execution was successful.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/05/edge-clock@730w.webp 730w, /images/2019/05/edge-clock@730w2x.webp 1460w, /images/2019/05/edge-clock@610w.webp 610w, /images/2019/05/edge-clock@610w2x.webp 1220w, /images/2019/05/edge-clock@450w.webp 450w, /images/2019/05/edge-clock@450w2x.webp 900w, /images/2019/05/edge-clock@330w.webp 330w, /images/2019/05/edge-clock@330w2x.webp 660w, /images/2019/05/edge-clock@545w.webp 545w, /images/2019/05/edge-clock@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/05/edge-clock@730w.jpg 730w, /images/2019/05/edge-clock@730w2x.jpg 1460w, /images/2019/05/edge-clock@610w.jpg 610w, /images/2019/05/edge-clock@610w2x.jpg 1220w, /images/2019/05/edge-clock@450w.jpg 450w, /images/2019/05/edge-clock@450w2x.jpg 900w, /images/2019/05/edge-clock@330w.jpg 330w, /images/2019/05/edge-clock@330w2x.jpg 660w, /images/2019/05/edge-clock@545w.jpg 545w, /images/2019/05/edge-clock@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/05/edge-clock.jpg" alt="Cronjob at the edge" title="Cronjob at the edge"></picture></p><p>An excellent fit to manage the state of a device with an unreliable Internet connection is the <a href="https://docs.aws.amazon.com/iot/latest/developerguide/iot-device-shadows.html" target="_blank" rel="noopener">Device Shadow Service for AWS IoT</a>. A device shadow has a desired and reported state. Let’s assume the following state.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;desired&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;cronexpression&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0 10 * * *&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;reported&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>As you can see, the <code>desired</code> state contains the <code>cronexpression</code> attribute which is not present on the <code>reported</code> state. The device has not yet reported anything to the device shadow.</p><p>If the device comes online, it receives the new desired state, applies the changes, and reports the new state on the device to AWS IoT. The state will look like this:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;desired&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;cronexpression&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0 10 * * *&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;reported&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;cronexpression&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0 10 * * *&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>The device is now in sync with the device shadow. You can change the desired state of the device shadow at any time by making a call to AWS IoT.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;desired&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;cronexpression&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0 11 * * *&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;reported&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;cronexpression&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0 10 * * *&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Again, if the device is online it will receive the new desrired state and apply changes.</p><h2 id="Implementation"><a href="#Implementation" class="headerlink" title="Implementation"></a>Implementation</h2><p>First, you need to create an IoT Thing.</p><ol><li>Visit the <a href="https://console.aws.amazon.com/iot/" target="_blank" rel="noopener">AWS IoT Core Management Console</a></li><li>Click on Secure &gt; Policies on the left</li><li>Click the <strong>Create</strong> button</li><li>Set <strong>Name</strong> to <code>cloudonaut</code></li><li>Click on Advanced mode</li><li>Enter the following policy, replace <code>REGION</code> with your AWS region and <code>ACCOUNT_ID</code> with your AWS account id, and click the <strong>Create</strong> button</li></ol><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2012-10-17&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Statement&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="string">&quot;iot:Subscribe&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:iot:REGION:ACCOUNT_ID:topicfilter/$aws/things/cloudonaut/shadow/*&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;iot:Publish&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;iot:Receive&quot;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:iot:REGION1:ACCOUNT_ID:topic/$aws/things/cloudonaut/shadow/*&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="string">&quot;iot:Connect&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:iot:REGION:ACCOUNT_ID:client/cloudonaut-*&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="string">&quot;iot:GetThingShadow&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="string">&quot;iot:UpdateThingShadow&quot;</span></span><br><span class="line">      <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:iot:REGION:ACCOUNT_ID:thing/cloudonaut&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><ol start="7"><li>Click on Manage &gt; Things on the left</li><li>Click the <strong>Create</strong> button</li><li>Click the <strong>Create a single thing</strong> button</li><li>Set the <strong>Name</strong> to <code>cloudonaut</code></li><li>Click the <strong>Next</strong> button</li><li>Click <strong>Create certificate</strong> </li><li>Download the certificate, private key, public key, and <a href="https://www.amazontrust.com/repository/AmazonRootCA1.pem" target="_blank" rel="noopener">root CA</a></li><li>Click the <strong>Activate</strong> button</li><li>Click the <strong>Attach a policy</strong> button</li><li>Select the <code>cloudonaut</code></li><li>Click the <strong>Register Thing</strong> button</li><li>Click on Settings on the left</li><li>Take a note of the endpoint</li></ol><p>The AWS IoT service is now configured. Let’s implement the device logic in Node.js using the <a href="http://npmjs.com/package/aws-iot-device-sdk" target="_blank" rel="noopener">aws-iot-device-sdk</a> package. To install the package, run <code>npm i aws-iot-device-sdk</code>.</p><p>First, the device needs to connect to AWS IoT using MQTT.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> awsIot = <span class="built_in">require</span>(<span class="string">&#x27;aws-iot-device-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> thingName = <span class="string">&#x27;cloudonaut&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> thingShadow = awsIot.<span class="title function_">thingShadow</span>(&#123;</span><br><span class="line">  <span class="attr">keyPath</span>: <span class="string">&#x27;***-private.pem.key&#x27;</span>, <span class="comment">// replace with downloaded file name</span></span><br><span class="line">  <span class="attr">certPath</span>: <span class="string">&#x27;***-certificate.pem.crt&#x27;</span>, <span class="comment">// replace with downloaded file name</span></span><br><span class="line">  <span class="attr">caPath</span>: <span class="string">&#x27;AmazonRootCA1.pem&#x27;</span>, <span class="comment">// replace with downloaded file name</span></span><br><span class="line">  <span class="attr">clientId</span>: <span class="string">`<span class="subst">$&#123;thingName&#125;</span>-node`</span>,</span><br><span class="line">  <span class="attr">host</span>: <span class="string">&#x27;***.iot.***.amazonaws.com&#x27;</span> <span class="comment">// replace with endpoint from settings</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>To connect to its shadow the device has to call the <code>register</code> method using a name that identifies the IoT thing. As soon the device has registered, it asks for the latest state of its shadow.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">thingShadow.<span class="title function_">register</span>(thingName, &#123;&#125;, <span class="keyword">function</span>(<span class="params">err</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) &#123;</span><br><span class="line">    <span class="keyword">throw</span> err;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    thingShadow.<span class="title function_">get</span>(thingName);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>Finally, the device receives the initial status and delta updates of its shadow.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">thingShadow.<span class="title function_">on</span>(<span class="string">&#x27;status&#x27;</span>,  <span class="keyword">function</span>(<span class="params">thingName, stat, clientToken, stateObject</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="string">&#x27;desired&#x27;</span> <span class="keyword">in</span> stateObject.<span class="property">state</span>) &#123;</span><br><span class="line">    <span class="title function_">createOrUpdateCronjob</span>(stateObject.<span class="property">state</span>.<span class="property">desired</span>.<span class="property">cronexpression</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line">thingShadow.<span class="title function_">on</span>(<span class="string">&#x27;delta&#x27;</span>,  <span class="keyword">function</span>(<span class="params">thingName, stateObject</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="string">&#x27;cronexpression&#x27;</span> <span class="keyword">in</span> stateObject.<span class="property">state</span>) &#123;</span><br><span class="line">    <span class="title function_">createOrUpdateCronjob</span>(stateObject.<span class="property">state</span>.<span class="property">cronexpression</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>The only missing piece is the implementation of <code>createOrUpdateCronjob</code> that also reports the <code>cronexpression</code> back AWS IoT. The device also reports the last time the cronjob executed via the <code>cronexecution</code> attribute. If the device has no Internet connection, the cronjob still executes. To install the package, run <code>npm i node-cron</code>.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> cron = <span class="built_in">require</span>(<span class="string">&#x27;node-cron&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> cronjob;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">createOrUpdateCronjob</span>(<span class="params">cronexpression</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (cronjob) &#123;</span><br><span class="line">    cronjob.<span class="title function_">destroy</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  cronjob = cron.<span class="title function_">schedule</span>(cronexpression, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;cronjob executed&#x27;</span>);</span><br><span class="line">    thingShadow.<span class="title function_">update</span>(thingName, &#123;</span><br><span class="line">      <span class="attr">state</span>: &#123;</span><br><span class="line">        <span class="attr">reported</span>: &#123;</span><br><span class="line">          <span class="attr">cronexecution</span>: <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">toISOString</span>()</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">  thingShadow.<span class="title function_">update</span>(thingName, &#123;</span><br><span class="line">    <span class="attr">state</span>: &#123;</span><br><span class="line">      <span class="attr">reported</span>: &#123;</span><br><span class="line">        cronexpression</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>A device shadow is an excellent choice to deploy configuration information to devices that are temporarily online. You can manage the desired configuration of your device at any time by updating the device shadow. Once the device comes online, the desired state is applied and reported back to the shadow.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Download YouTube videos with AWS Lambda and store them on S3</title>
      <link>https://cloudonaut.io/download-youtube-videos-with-aws-lambda-and-store-them-on-s3/</link>
      <description>
        <![CDATA[<p>Recently, I was faced with the challenge to download videos from YouTube and store them on S3. </p>
<p><picture class="img-fluid"><source]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/download-youtube-videos-with-aws-lambda-and-store-them-on-s3/</guid>
      <pubDate>Fri, 17 May 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Recently, I was faced with the challenge to download videos from YouTube and store them on S3. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/05/youtube@730w.webp 730w, /images/2019/05/youtube@730w2x.webp 1460w, /images/2019/05/youtube@610w.webp 610w, /images/2019/05/youtube@610w2x.webp 1220w, /images/2019/05/youtube@450w.webp 450w, /images/2019/05/youtube@450w2x.webp 900w, /images/2019/05/youtube@330w.webp 330w, /images/2019/05/youtube@330w2x.webp 660w, /images/2019/05/youtube@545w.webp 545w, /images/2019/05/youtube@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/05/youtube@730w.jpg 730w, /images/2019/05/youtube@730w2x.jpg 1460w, /images/2019/05/youtube@610w.jpg 610w, /images/2019/05/youtube@610w2x.jpg 1220w, /images/2019/05/youtube@450w.jpg 450w, /images/2019/05/youtube@450w2x.jpg 900w, /images/2019/05/youtube@330w.jpg 330w, /images/2019/05/youtube@330w2x.jpg 660w, /images/2019/05/youtube@545w.jpg 545w, /images/2019/05/youtube@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/05/youtube.jpg" alt="Download YouTube videos with AWS Lambda" title="Download YouTube videos with AWS Lambda"></picture></p><p>Sounds easy? Remember than Lambda comes with a few limitations:</p><ol><li>512 MB of disk space available at <code>/tmp</code></li><li>3008 MB of memory</li><li>15 minutes maximum execution time</li></ol><p>While working on a solution, I encountered multiple problems:</p><ol><li>Download the video from YouTube to <code>/tmp</code> and then upload it to S3: Does not work with videos larger than 512 MB.</li><li>Download the video from YouTube into memory and then upload it to S3: Does not work with videos larger than ~3 GB.</li><li>Download the video from Youtube and stream it to S3 while downloading: Works for all videos that can be processed within 15 minutes. I have not found a video that took longer than a few minutes to process.</li></ol><p>Let’s look at how I finally solved the problem with a streaming approach in Node.js. I use the <a href="https://www.npmjs.com/package/youtube-dl" target="_blank" rel="noopener">youtube-dl</a> library to get easy access to YouTube videos.</p><p>First, we create a <a href="https://nodejs.org/api/stream.html#stream_class_stream_passthrough" target="_blank" rel="noopener">PassThrough</a> stream in Node.js. A pass-through stream is a duplex stream where you can write on one side and read on the other side.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> stream = <span class="built_in">require</span>(<span class="string">&#x27;stream&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> passtrough = <span class="keyword">new</span> stream.<span class="title class_">PassThrough</span>();</span><br></pre></td></tr></table></figure><p>Next, we need to write data to the stream. This is done by the <code>youtube-dl</code> library.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> youtubedl = <span class="built_in">require</span>(<span class="string">&#x27;youtube-dl&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> dl = <span class="title function_">youtubedl</span>(event.<span class="property">videoUrl</span>, [<span class="string">&#x27;--format=best[ext=mp4]&#x27;</span>], &#123;<span class="attr">maxBuffer</span>: <span class="title class_">Infinity</span>&#125;);</span><br><span class="line">dl.<span class="title function_">pipe</span>(passtrough); <span class="comment">// write video to the pass-through stream</span></span><br></pre></td></tr></table></figure><p>And finally, we need to upload the stream to S3. We make use of the <a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/mpuoverview.html" target="_blank" rel="noopener">Multipart Upload feature of S3</a> which allows us to upload a big file in smaller chunks. This way, we only have to buffer the small junk (64 MB in this case) in memory and not the whole file.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> upload = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="property">S3</span>.<span class="title class_">ManagedUpload</span>(&#123;</span><br><span class="line">  <span class="attr">params</span>: &#123;</span><br><span class="line">    <span class="title class_">Bucket</span>: process.<span class="property">env</span>.<span class="property">BUCKET_NAME</span>,</span><br><span class="line">    <span class="title class_">Key</span>: <span class="string">&#x27;video.mp4&#x27;</span>,</span><br><span class="line">    <span class="title class_">Body</span>: passtrough</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">partSize</span>: <span class="number">1024</span> * <span class="number">1024</span> * <span class="number">64</span> <span class="comment">// 64 MB in bytes</span></span><br><span class="line">&#125;);</span><br><span class="line">upload.<span class="title function_">send</span>(<span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (err) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;error&#x27;</span>, err);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;done&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>That’s it. Now you can download YouTube videos of any size with Lambda and upload them to S3. I recommend running the code in a “big” Lambda function with 3008 MB of memory for better network performance.</p><p>You can find the <a href="https://github.com/widdix/aws-lambda-youtube-dl" target="_blank" rel="noopener">full source code on GitHub</a> including a SAM template to provision the AWS resources. Have fun!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>6 new ways to reduce your AWS bill with little effort</title>
      <link>https://cloudonaut.io/6-new-ways-to-reduce-your-aws-bill-with-little-effort/</link>
      <description>
        <![CDATA[<p>The last time we wrote about how to <a href="/3-simple-ways-of-saving-up-to-90-of-ec2-costs/">save AWS costs</a> was at the end of 2015.]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/costs/">costs</category>
      <guid isPermaLink="true">https://cloudonaut.io/6-new-ways-to-reduce-your-aws-bill-with-little-effort/</guid>
      <pubDate>Tue, 07 May 2019 17:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The last time we wrote about how to <a href="/3-simple-ways-of-saving-up-to-90-of-ec2-costs/">save AWS costs</a> was at the end of 2015. AWS has changed a lot since then. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/05/cost-savings@730w.webp 730w, /images/2019/05/cost-savings@730w2x.webp 1460w, /images/2019/05/cost-savings@610w.webp 610w, /images/2019/05/cost-savings@610w2x.webp 1220w, /images/2019/05/cost-savings@450w.webp 450w, /images/2019/05/cost-savings@450w2x.webp 900w, /images/2019/05/cost-savings@330w.webp 330w, /images/2019/05/cost-savings@330w2x.webp 660w, /images/2019/05/cost-savings@545w.webp 545w, /images/2019/05/cost-savings@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/05/cost-savings@730w.jpg 730w, /images/2019/05/cost-savings@730w2x.jpg 1460w, /images/2019/05/cost-savings@610w.jpg 610w, /images/2019/05/cost-savings@610w2x.jpg 1220w, /images/2019/05/cost-savings@450w.jpg 450w, /images/2019/05/cost-savings@450w2x.jpg 900w, /images/2019/05/cost-savings@330w.jpg 330w, /images/2019/05/cost-savings@330w2x.jpg 660w, /images/2019/05/cost-savings@545w.jpg 545w, /images/2019/05/cost-savings@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/05/cost-savings.jpg" alt="Cost Savings" title="Cost Savings"></picture></p><p>It’s time for an update with six new tips to save AWS costs with little effort.</p><h2 id="EC2-AMD-Instances"><a href="#EC2-AMD-Instances" class="headerlink" title="EC2 AMD Instances"></a>EC2 AMD Instances</h2><p>AWS introduced AMD-powered EC2 instances that are 10% cheaper compared to the Intel-powered Instances. They provide the same resources (CPU, memory, network bandwidth) and run the same AMIs. The following table shows a mapping from Intel to AMD instance families.</p><table class="table table-striped table-responsive"><thead><tr><th>Purpose</th><th>Intel family</th><th>AMD family</th></tr></thead><tbody><tr><td>Burstable</td><td>t3</td><td>t3a</td></tr><tr><td>General</td><td>m5</td><td>m5a</td></tr><tr><td>Memory-optimized</td><td>r5</td><td>r5a</td></tr></tbody></table><p>You can switch to an AMD family by stopping your EC2 instance, changing the instance type, and starting the instance again. Those steps will pay off quickly.</p><p><strong>Saving potential: 10% over Intel-based instance types</strong></p><blockquote><p>AWS is also working on <a href="https://aws.amazon.com/blogs/aws/new-ec2-instances-a1-powered-by-arm-based-aws-graviton-processors/" target="_blank" rel="noopener">ARM-based EC2 instances</a>. They are even cheaper (~40%), but the architecture is different and cannot run your Intel&#x2F;AMD AMIs.</p></blockquote><h2 id="Use-VPC-endpoints-instead-of-NAT-gateways"><a href="#Use-VPC-endpoints-instead-of-NAT-gateways" class="headerlink" title="Use VPC endpoints instead of NAT gateways"></a>Use VPC endpoints instead of NAT gateways</h2><p>Many VPC architectures make use of private subnets (a subnet without a route to the Internet via an IGW). You can even run public websites in such a setup if your load balancer runs in public subnets as shown in the following figure.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@730w.webp 730w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@730w2x.webp 1460w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@610w.webp 610w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@610w2x.webp 1220w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@450w.webp 450w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@450w2x.webp 900w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@330w.webp 330w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@330w2x.webp 660w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@545w.webp 545w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@730w.png 730w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@730w2x.png 1460w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@610w.png 610w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@610w2x.png 1220w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@450w.png 450w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@450w2x.png 900w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@330w.png 330w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@330w2x.png 660w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@545w.png 545w, /images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/05/aws-architecture-private-public-subnets-with-nat-gateways.png" alt="AWS architecture with private and public subnets using NAT gateways" title="AWS architecture with private and public subnets using NAT gateways"></picture></p><p><em>The diagram was created with <a href="https://bit.ly/2PpMc2M" target="_blank" rel="noopener">Cloudcraft - Visualize your cloud architecture like a pro</a>.</em></p><p>But we also see many EC2 based architectures to make use of AWS services such as SQS, S3, DynamoDB, and so on. To use those services, we have to make calls to the AWS API over Internet. In private subnets, this was often done using NAT gateways (or more dated NAT instances) which increase your traffic costs.</p><p>There are three ways to eliminate or at least reduce the extra traffic costs:</p><ol><li>For S3 and DynamoDB, you can create a Gateway VPC Endpoint which is free and lets you communicate to S3 and DynamoDB from private subnets without natting.</li><li>For <a href="https://docs.aws.amazon.com/vpc/latest/userguide/vpce-interface.html" target="_blank" rel="noopener">some AWS services</a>, you can create an Interface VPC Endpoint which is cheaper than a NAT gateway.</li><li><a href="/private-subnets-are-broken-on-aws/">Run your workloads in public subnets and protect them with security groups</a>.</li></ol><p>The following figure shows how to use VPC endpoints to access AWS services without natting while still running in private subnets.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@730w.webp 730w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@730w2x.webp 1460w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@610w.webp 610w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@610w2x.webp 1220w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@450w.webp 450w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@450w2x.webp 900w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@330w.webp 330w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@330w2x.webp 660w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@545w.webp 545w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@730w.png 730w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@730w2x.png 1460w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@610w.png 610w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@610w2x.png 1220w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@450w.png 450w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@450w2x.png 900w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@330w.png 330w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@330w2x.png 660w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@545w.png 545w, /images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/05/aws-architecture-private-public-subnets-with-vpc-endpoint.png" alt="AWS architecture with private and public subnets using VPC endpoints" title="AWS architecture with private and public subnets using VPC endpoints"></picture></p><p>The following table shows the options you have if you decide to stay in private subnets.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th>Gateway VPC Endpoint</th><th>Interface VPC Endpoint</th><th>NAT Gateway</th></tr></thead><tbody><tr><td>Supported AWS services</td><td>S3, DynamoDB</td><td><a href="https://docs.aws.amazon.com/vpc/latest/userguide/vpce-interface.html">some</a></td><td>all</td></tr><tr><td>Price per hour<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup></td><td>free</td><td>$0.01</td><td>$0.045<sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup></td></tr><tr><td>Price per GB<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup></td><td>free</td><td>$0.01</td><td>$0.045</td></tr></tbody></table><p>As you can see, using Gateway VPC Endpoints is cheaper than using Interface VPC Endpoint which is cheaper than using NAT Gateways</p><p><strong>Saving potential: Depends on your traffic</strong></p><h2 id="Convertible-Reserved-EC2-Instances"><a href="#Convertible-Reserved-EC2-Instances" class="headerlink" title="Convertible Reserved EC2 Instances"></a>Convertible Reserved EC2 Instances</h2><p>Reserved EC2 instances are all yours for one or three years. You pay for them no matter if you use them. Reserved instances are cheaper than on-demand instances that are billed in seconds or minutes. Since AWS launched instance reservations, they become much more flexible. With Convertible Reserved EC2 Instances, you can modify the instance family of a reservation. This is very handy if you have 3-year reservations and AWS launches a new generation of the instance family that you use. E.g., going from <code>m4</code> to <code>m5</code>. Convert all your <code>m4</code> reservations to <code>m5</code> reservations and modernize your EC2 instances fleet accordingly without losing money because of old reservations.</p><p><strong>Saving potential: Additional 25% over On-Demand (assuming you can now go from 1-year terms to 3-year terms)</strong></p><h2 id="EC2-Spot-Instances"><a href="#EC2-Spot-Instances" class="headerlink" title="EC2 Spot Instances"></a>EC2 Spot Instances</h2><p>The AWS spot market has become <a href="https://aws.amazon.com/blogs/compute/new-amazon-ec2-spot-pricing/" target="_blank" rel="noopener">very stable</a>. In combination with the support of <a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/asg-purchase-options.html" target="_blank" rel="noopener">Multiple Instance Types and Purchase Options</a> in Auto Scaling Groups, you can now make use of spot instances in production environments without increasing the engineering overhead.</p><p><strong>Saving potential: 70-90% over On-Demand</strong></p><h2 id="DynamoDB-on-demand"><a href="#DynamoDB-on-demand" class="headerlink" title="DynamoDB on-demand"></a>DynamoDB on-demand</h2><p><a href="https://aws.amazon.com/blogs/aws/amazon-dynamodb-on-demand-no-capacity-planning-and-pay-per-request-pricing/" target="_blank" rel="noopener">DynamoDB On-Demand</a> is a new feature to reduce the costs of most DynamoDB tables. With DynamoDB On-Demand, you can use DynamoDB without provisioning and paying for capacity. Instead, you pay per request. Sounds amazing? I was excited and re-configured all DynamoDB tables of our SaaS product marbot: <a href="https://marbot.io/" target="_blank" rel="noopener">cloud-native alerting for CloudWatch via Slack</a>. <a href="/cost-savings-with-dynamodb-on-demand-essons-learned/">The DynamoDB costs went down by 90%</a>.</p><p><strong>Saving potential: Depends on your workload (for small, spiky workloads up to 90% over DynamoDB with provisioned capacity)</strong></p><h2 id="S3-Intelligent-Tiering"><a href="#S3-Intelligent-Tiering" class="headerlink" title="S3 Intelligent-Tiering"></a>S3 Intelligent-Tiering</h2><p>S3 Intelligent-Tiering moves objects (&gt;&#x3D; 128KB) from Standard Storage ($0.023 per GB&#x2F;month) to Infrequent Access Storage ($0.0125 per GB&#x2F;month, minus 45%) if the objects are not accessed within 30 days. Additionally, you pay $0.0000025 per object&#x2F;month for the management of the storage classes. Because of the per-object fee, the pricing model works better for larger objects than for smaller objects.</p><p><strong>Saving potential: Depends on your workload</strong></p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>AWS is changing fast. Every day, new capabilities are released. Some of them can help you to reduce your AWS bill. </p><table class="table table-striped table-responsive"><thead><tr><th>Saving potential</th><th>Action item</th></tr></thead><tbody><tr><td>10% over Intel-based instance types</td><td>Switch to AMD-based instances</td></tr><tr><td>Depends on your traffic</td><td>Reduce NAT traffic with VPC endpoints</td></tr><tr><td>25%-50% over On-Demand</td><td>Reserve instances without losing instance family flexibility</td></tr><tr><td>70-90% over On-Demand</td><td>Using Spot Instances in Auto Scaling Groups</td></tr><tr><td>Depends on your workload</td><td>Switch to DynamoDB on-demand</td></tr><tr><td>Depends on your workload</td><td>Enable S3 Intelligent-Tiering</td></tr></tbody></table><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. All prices apply to the <code>us-east-1</code> region <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. For high availability, you need one NAT Gateway per AZ <a href="#fnref:2" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Monitoring EC2 Network Utilization</title>
      <link>https://cloudonaut.io/monitoring-ec2-network-utilization/</link>
      <description>
        <![CDATA[<blockquote>
<p>This post was originally published on the <a href="https://marbot.io/blog/analyze-cloudwatch-logs-like-a-pro.html" target="_]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <guid isPermaLink="true">https://cloudonaut.io/monitoring-ec2-network-utilization/</guid>
      <pubDate>Thu, 02 May 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<blockquote><p>This post was originally published on the <a href="https://marbot.io/blog/analyze-cloudwatch-logs-like-a-pro.html" target="_blank" rel="noopener">marbot blog</a>.</p></blockquote><p>Are you monitoring the network utilization of your EC2 instances? Why not? The network is one of the rare resources that will limit your workload’s maximum throughput:</p><ol><li>CPU</li><li>Memory</li><li>Network</li><li>Disk</li><li>GPU</li></ol><p>I’ve debugged performance problems in a lot of infrastructures during the last 12 months. In most of the scenarios, the network capabilities of EC2 or RDS instances was the bottleneck causing troubles. That is why I want to share with you how to monitor the network utilization of EC2 instances.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/05/network@730w.webp 730w, /images/2019/05/network@730w2x.webp 1460w, /images/2019/05/network@610w.webp 610w, /images/2019/05/network@610w2x.webp 1220w, /images/2019/05/network@450w.webp 450w, /images/2019/05/network@450w2x.webp 900w, /images/2019/05/network@330w.webp 330w, /images/2019/05/network@330w2x.webp 660w, /images/2019/05/network@545w.webp 545w, /images/2019/05/network@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/05/network@730w.jpg 730w, /images/2019/05/network@730w2x.jpg 1460w, /images/2019/05/network@610w.jpg 610w, /images/2019/05/network@610w2x.jpg 1220w, /images/2019/05/network@450w.jpg 450w, /images/2019/05/network@450w2x.jpg 900w, /images/2019/05/network@330w.jpg 330w, /images/2019/05/network@330w2x.jpg 660w, /images/2019/05/network@545w.jpg 545w, /images/2019/05/network@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/05/network.jpg" alt="Monitoring the Network Utilization of EC2" title="Monitoring the Network Utilization of EC2"></picture></p><p>To monitor the networking utilization of an EC2 instance, we need to solve two challenges.</p><h2 id="Challenge-1-What’s-the-network-performance-of-my-EC2-instance"><a href="#Challenge-1-What’s-the-network-performance-of-my-EC2-instance" class="headerlink" title="Challenge #1: What’s the network performance of my EC2 instance?"></a>Challenge #1: What’s the network performance of my EC2 instance?</h2><p>To be able to monitor the network utilization of your EC2 instance, you need to be able to answer the following question. What are the baseline and maximum network throughput of your EC2 instance? Unfortunately, AWS does not provide accurate information about the network performance for most instance types. For example, AWS promises <code>Moderate</code> network performance for a <code>t2.xlarge</code> instance or <code>Up to 10 Gbps</code> for a <code>m5.large</code> instance.</p><p>This provided information is not satisfactory. That is why I ran a network performance benchmark and published the results at <a href="/ec2-network-performance-cheat-sheet/">EC2 Network Performance Cheat Sheet</a>. The results are astonishing.</p><p>An <code>m5.large</code> instance provides 10.04 Gbit&#x2F;s for a few minutes only. Afterward, the baseline network performance for an m5.large instance is around 0.74  Gbit&#x2F;s. The results for other instance types look similar.</p><p>The <a href="/ec2-network-performance-cheat-sheet/">EC2 Network Performance Cheat Sheet</a> gives you an estimation for the baseline and maximum network throughput of your EC2 instance which allows you to define a threshold for monitoring.</p><p>Fine, we have solved the challenge #1.</p><h2 id="Challenge-2-How-to-combine-multiple-CloudWatch-metrics"><a href="#Challenge-2-How-to-combine-multiple-CloudWatch-metrics" class="headerlink" title="Challenge #2: How to combine multiple CloudWatch metrics?"></a>Challenge #2: How to combine multiple CloudWatch metrics?</h2><p>Each EC2 instance reports various metrics to CloudWatch. The metrics <code>NetworkIn</code> and <code>NetworkOut</code> collect the number of bytes received on all network interfaces by the instance. However, to calculate the network utilization of your EC2 instance, you need to add up both metrics.</p><p>Pick one of the following options to create a CloudWatch alarm monitoring the total network utilization of your EC2 instance:</p><ol><li>Use the AWS Management Console to create the CloudWatch alarm manually.</li><li>Use CloudFormation to create the CloudWatch alarm with Infrastructure as Code.</li><li>Use marbot’s Jump Start to create the CloudWatch alarm.</li></ol><h3 id="AWS-Management-Console"><a href="#AWS-Management-Console" class="headerlink" title="AWS Management Console"></a>AWS Management Console</h3><p>Log into the AWS Management Console and go to <a href="https://console.aws.amazon.com/cloudwatch/" target="_blank" rel="noopener">CloudWatch</a>. Select <code>Alarms</code> from the sub-navigation and click the <code>Create Alarm</code> button. The wizard shown in the following screenshot appears. Click the <code>Select metric</code> button.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/05/network-utilization-01@730w.webp 730w, /images/2019/05/network-utilization-01@730w2x.webp 1460w, /images/2019/05/network-utilization-01@610w.webp 610w, /images/2019/05/network-utilization-01@610w2x.webp 1220w, /images/2019/05/network-utilization-01@450w.webp 450w, /images/2019/05/network-utilization-01@450w2x.webp 900w, /images/2019/05/network-utilization-01@330w.webp 330w, /images/2019/05/network-utilization-01@330w2x.webp 660w, /images/2019/05/network-utilization-01@545w.webp 545w, /images/2019/05/network-utilization-01@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/05/network-utilization-01@730w.png 730w, /images/2019/05/network-utilization-01@730w2x.png 1460w, /images/2019/05/network-utilization-01@610w.png 610w, /images/2019/05/network-utilization-01@610w2x.png 1220w, /images/2019/05/network-utilization-01@450w.png 450w, /images/2019/05/network-utilization-01@450w2x.png 900w, /images/2019/05/network-utilization-01@330w.png 330w, /images/2019/05/network-utilization-01@330w2x.png 660w, /images/2019/05/network-utilization-01@545w.png 545w, /images/2019/05/network-utilization-01@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/05/network-utilization-01.png" alt="Step 1: Creating CloudWatch Alarm monitoring Network Utilization" title="Step 1: Creating CloudWatch Alarm monitoring Network Utilization"></picture></p><p>Search for the <code>NetworkIn</code> and <code>NetworkOut</code> metrics of your EC2 instance and select them both. After doing so, select the <code>Graphed metrics</code> tab.</p><ol><li>Click <code>Add a math expression</code>.</li><li>Type in id <code>out</code> for the <code>NetworkOut</code> metric and <code>in</code> for the <code>NetworkIn</code> metric.</li><li>Type in the expression <code>(in+out)/300/1000/1000/1000*8</code>.</li></ol><p>Let me quickly explain the math expression <code>(in+out)/300/1000/1000/1000*8</code>:</p><ul><li>Add up <code>in</code> and <code>out</code>.</li><li>Divide by <code>300</code> to convert from 5 minutes to 1 second.</li><li>Divide by <code>1000/1000/1000*8</code> to convert Byte in Gbit.</li></ul><p>Make sure you have only selected the math expression before you click the <code>Select metric</code> button.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/05/network-utilization-02@730w.webp 730w, /images/2019/05/network-utilization-02@730w2x.webp 1460w, /images/2019/05/network-utilization-02@610w.webp 610w, /images/2019/05/network-utilization-02@610w2x.webp 1220w, /images/2019/05/network-utilization-02@450w.webp 450w, /images/2019/05/network-utilization-02@450w2x.webp 900w, /images/2019/05/network-utilization-02@330w.webp 330w, /images/2019/05/network-utilization-02@330w2x.webp 660w, /images/2019/05/network-utilization-02@545w.webp 545w, /images/2019/05/network-utilization-02@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/05/network-utilization-02@730w.png 730w, /images/2019/05/network-utilization-02@730w2x.png 1460w, /images/2019/05/network-utilization-02@610w.png 610w, /images/2019/05/network-utilization-02@610w2x.png 1220w, /images/2019/05/network-utilization-02@450w.png 450w, /images/2019/05/network-utilization-02@450w2x.png 900w, /images/2019/05/network-utilization-02@330w.png 330w, /images/2019/05/network-utilization-02@330w2x.png 660w, /images/2019/05/network-utilization-02@545w.png 545w, /images/2019/05/network-utilization-02@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/05/network-utilization-02.png" alt="Step 2: Creating CloudWatch Alarm monitoring Network Utilization" title="Step 2: Creating CloudWatch Alarm monitoring Network Utilization"></picture></p><p>Finally, set up the alarm.</p><ol><li>Type in a name and description.</li><li>Define the threshold. For example, 80% of the baseline network performance listed in the <a href="/ec2-network-performance-cheat-sheet/">EC2 Network Performance Cheat Sheet</a>.</li><li>To avoid alarms from short network utilization spikes configure <code>8 out of 12 datapoints</code>. Which translates to 45 minutes within an hour.</li></ol><p>Click the <code>Create Alarm</code> button.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/05/network-utilization-03@730w.webp 730w, /images/2019/05/network-utilization-03@730w2x.webp 1460w, /images/2019/05/network-utilization-03@610w.webp 610w, /images/2019/05/network-utilization-03@610w2x.webp 1220w, /images/2019/05/network-utilization-03@450w.webp 450w, /images/2019/05/network-utilization-03@450w2x.webp 900w, /images/2019/05/network-utilization-03@330w.webp 330w, /images/2019/05/network-utilization-03@330w2x.webp 660w, /images/2019/05/network-utilization-03@545w.webp 545w, /images/2019/05/network-utilization-03@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/05/network-utilization-03@730w.png 730w, /images/2019/05/network-utilization-03@730w2x.png 1460w, /images/2019/05/network-utilization-03@610w.png 610w, /images/2019/05/network-utilization-03@610w2x.png 1220w, /images/2019/05/network-utilization-03@450w.png 450w, /images/2019/05/network-utilization-03@450w2x.png 900w, /images/2019/05/network-utilization-03@330w.png 330w, /images/2019/05/network-utilization-03@330w2x.png 660w, /images/2019/05/network-utilization-03@545w.png 545w, /images/2019/05/network-utilization-03@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/05/network-utilization-03.png" alt="Step 3: Creating CloudWatch Alarm monitoring Network Utilization" title="Step 3: Creating CloudWatch Alarm monitoring Network Utilization"></picture></p><p>Fine, you have set up a CloudWatch alarm monitoring the network utilization of your EC2 instance.</p><p>Instead of going through this process manually, you could create CloudWatch alarms in an automated way with the help of CloudFormation as well.</p><h3 id="CloudFormation"><a href="#CloudFormation" class="headerlink" title="CloudFormation"></a>CloudFormation</h3><p>The following snippet shows a CloudFormation template setting up a CloudWatch alarm monitoring the network utilization of an EC2 instance.</p><p>You need to modify the <code>Threshold</code>. I suggest 80% of the network baseline performance as listed in the <a href="/ec2-network-performance-cheat-sheet/">EC2 Network Performance Cheat Sheet</a>.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">Topic:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">  <span class="attr">InstanceId:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">NetworkUtilizationTooHighAlarm:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AlarmDescription:</span> <span class="string">&#x27;EC2 High Network Utilization&#x27;</span></span><br><span class="line">      <span class="attr">Metrics:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Id:</span> <span class="string">in</span></span><br><span class="line">        <span class="attr">Label:</span> <span class="string">NetworkIn</span></span><br><span class="line">        <span class="attr">MetricStat:</span></span><br><span class="line">          <span class="attr">Metric:</span></span><br><span class="line">            <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/EC2&#x27;</span></span><br><span class="line">            <span class="attr">MetricName:</span> <span class="string">NetworkIn</span></span><br><span class="line">            <span class="attr">Dimensions:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">InstanceId</span></span><br><span class="line">              <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">InstanceId</span></span><br><span class="line">          <span class="attr">Period:</span> <span class="number">300</span></span><br><span class="line">          <span class="attr">Stat:</span> <span class="string">Sum</span></span><br><span class="line">          <span class="attr">Unit:</span> <span class="string">Bytes</span></span><br><span class="line">        <span class="attr">ReturnData:</span> <span class="literal">false</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Id:</span> <span class="string">out</span></span><br><span class="line">        <span class="attr">Label:</span> <span class="string">NetworkOut</span></span><br><span class="line">        <span class="attr">MetricStat:</span></span><br><span class="line">          <span class="attr">Metric:</span></span><br><span class="line">            <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/EC2&#x27;</span></span><br><span class="line">            <span class="attr">MetricName:</span> <span class="string">NetworkOut</span></span><br><span class="line">            <span class="attr">Dimensions:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">InstanceId</span></span><br><span class="line">              <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">InstanceId</span></span><br><span class="line">          <span class="attr">Period:</span> <span class="number">300</span></span><br><span class="line">          <span class="attr">Stat:</span> <span class="string">Sum</span></span><br><span class="line">          <span class="attr">Unit:</span> <span class="string">Bytes</span></span><br><span class="line">        <span class="attr">ReturnData:</span> <span class="literal">false</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Id:</span> <span class="string">total</span></span><br><span class="line">        <span class="attr">Label:</span> <span class="string">&#x27;NetworkTotal&#x27;</span></span><br><span class="line">        <span class="attr">Expression:</span> <span class="string">&#x27;(in+out)/300/1000/1000/1000*8&#x27;</span> <span class="comment"># Gbit/s</span></span><br><span class="line">        <span class="attr">ReturnData:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br><span class="line">      <span class="attr">EvaluationPeriods:</span> <span class="number">12</span></span><br><span class="line">      <span class="attr">DatapointsToAlarm:</span> <span class="number">8</span></span><br><span class="line">      <span class="attr">Threshold:</span> <span class="string">&#x27;0.048&#x27;</span> <span class="comment"># Gbit/s</span></span><br><span class="line">      <span class="attr">AlarmActions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Topic</span></span><br><span class="line">      <span class="attr">OKActions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Topic</span></span><br><span class="line">      <span class="attr">TreatMissingData:</span> <span class="string">notBreaching</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Are you looking for an, even more, simpler way to monitor the network utilization of your EC2 instance?</p><h3 id="marbot-Jump-Start"><a href="#marbot-Jump-Start" class="headerlink" title="marbot Jump Start"></a>marbot Jump Start</h3><p>Our chatbot <em>marbot</em> escalates alarms among the members of your DevOps team. Luckily, <em>marbot</em> provides built-in <em>Jump Starts</em> which simplify creating CloudWatch alarms for your cloud resources. The <em>Jump Start</em> for EC2 instances sets up monitoring for network utilization as well.</p><ol><li>Add <a href="https://marbot.io/" target="_blank" rel="noopener">marbot</a> to your Slack workspace.</li><li>Invite <em>marbot</em> to a channel.</li><li>Follow the installation instructions.</li><li>Ask marbot for help monitoring your EC2 instance: <code>@marbot Help me to monitor my EC2 instance.</code></li><li>Select <code>EC2 instance</code> or <code>EC2 instances</code> as monitoring goal and follow the <em>Jump Start</em> wizard as shown in the following screenshot.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/05/marbot-jump-start@730w.webp 730w, /images/2019/05/marbot-jump-start@730w2x.webp 1460w, /images/2019/05/marbot-jump-start@610w.webp 610w, /images/2019/05/marbot-jump-start@610w2x.webp 1220w, /images/2019/05/marbot-jump-start@450w.webp 450w, /images/2019/05/marbot-jump-start@450w2x.webp 900w, /images/2019/05/marbot-jump-start@330w.webp 330w, /images/2019/05/marbot-jump-start@330w2x.webp 660w, /images/2019/05/marbot-jump-start@545w.webp 545w, /images/2019/05/marbot-jump-start@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/05/marbot-jump-start@730w.png 730w, /images/2019/05/marbot-jump-start@730w2x.png 1460w, /images/2019/05/marbot-jump-start@610w.png 610w, /images/2019/05/marbot-jump-start@610w2x.png 1220w, /images/2019/05/marbot-jump-start@450w.png 450w, /images/2019/05/marbot-jump-start@450w2x.png 900w, /images/2019/05/marbot-jump-start@330w.png 330w, /images/2019/05/marbot-jump-start@330w2x.png 660w, /images/2019/05/marbot-jump-start@545w.png 545w, /images/2019/05/marbot-jump-start@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/05/marbot-jump-start.png" alt="marbot Jump Start" title="marbot Jump Start"></picture></p><p>It couldn’t be easier!</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Monitoring the network utilization of your EC2 instance is essential, as the network is a limited resource. The instance type affects maximum and baseline performance. Your EC2 instance might not be able to provide the maximum network performance for more than 5 to 30 minutes. Therefore, use the baseline performance to define the alarm threshold. Use <a href="/ec2-network-performance-cheat-sheet/">EC2 Network Performance Cheat Sheet</a> to get an estimation of the network performance of your EC2 instance.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Own your analytics data: Replacing Google Analytics with Amazon QuickSight</title>
      <link>https://cloudonaut.io/own-your-analytics-data-replacing-google-analytics-with-amazon-quicksight/</link>
      <description>
        <![CDATA[<p>Making decisions based on data is compelling. Especially, when you wrestle with the most straightforward decisions like me. Google Analyt]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/athena/">athena</category>
      <category domain="https://cloudonaut.io/tag/quicksight/">quicksight</category>
      <guid isPermaLink="true">https://cloudonaut.io/own-your-analytics-data-replacing-google-analytics-with-amazon-quicksight/</guid>
      <pubDate>Mon, 22 Apr 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Making decisions based on data is compelling. Especially, when you wrestle with the most straightforward decisions like me. Google Analytics is the de-facto standard for tracking your customers while clicking through your websites and applications. I’ve made data-driven decisions to improve the user experience of our blog and applications with the help of Google Analytics for years.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/04/analytics@730w.webp 730w, /images/2019/04/analytics@730w2x.webp 1460w, /images/2019/04/analytics@610w.webp 610w, /images/2019/04/analytics@610w2x.webp 1220w, /images/2019/04/analytics@450w.webp 450w, /images/2019/04/analytics@450w2x.webp 900w, /images/2019/04/analytics@330w.webp 330w, /images/2019/04/analytics@330w2x.webp 660w, /images/2019/04/analytics@545w.webp 545w, /images/2019/04/analytics@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/04/analytics@730w.jpg 730w, /images/2019/04/analytics@730w2x.jpg 1460w, /images/2019/04/analytics@610w.jpg 610w, /images/2019/04/analytics@610w2x.jpg 1220w, /images/2019/04/analytics@450w.jpg 450w, /images/2019/04/analytics@450w2x.jpg 900w, /images/2019/04/analytics@330w.jpg 330w, /images/2019/04/analytics@330w2x.jpg 660w, /images/2019/04/analytics@545w.jpg 545w, /images/2019/04/analytics@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/04/analytics.jpg" alt="Own your analytics data" title="Own your analytics data"></picture></p><p>However, I decided to replace Google Analytics for the following reasons:</p><ul><li>I want to have <strong>more flexibility</strong> in how I collect and analyze data.</li><li>I think <strong>data minimization</strong> is key for data privacy. And I don’t agree with how Google collects and combines data.</li><li>I want to <strong>transfer</strong> the <strong>ownership</strong> of our tracking data.</li></ul><p>So, I looked into my toolbox full of AWS services and replaced Google Analytics with the following tools:</p><ul><li><strong>Amazon CloudFront</strong> for collecting access logs. </li><li><strong>Amazon Simple Storage Service (S3)</strong> as my data lake to store access logs and other kinds of tracking data in the future.</li><li>The ad-hoc analytics service <strong>Amazon Athena</strong> to query data.</li><li>The business intelligence (BI) solution <strong>Amazon QuickSight</strong> to visualize the tracking data.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/04/athena-quicksight@730w.webp 730w, /images/2019/04/athena-quicksight@730w2x.webp 1460w, /images/2019/04/athena-quicksight@610w.webp 610w, /images/2019/04/athena-quicksight@610w2x.webp 1220w, /images/2019/04/athena-quicksight@450w.webp 450w, /images/2019/04/athena-quicksight@450w2x.webp 900w, /images/2019/04/athena-quicksight@330w.webp 330w, /images/2019/04/athena-quicksight@330w2x.webp 660w, /images/2019/04/athena-quicksight@545w.webp 545w, /images/2019/04/athena-quicksight@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/04/athena-quicksight@730w.png 730w, /images/2019/04/athena-quicksight@730w2x.png 1460w, /images/2019/04/athena-quicksight@610w.png 610w, /images/2019/04/athena-quicksight@610w2x.png 1220w, /images/2019/04/athena-quicksight@450w.png 450w, /images/2019/04/athena-quicksight@450w2x.png 900w, /images/2019/04/athena-quicksight@330w.png 330w, /images/2019/04/athena-quicksight@330w2x.png 660w, /images/2019/04/athena-quicksight@545w.png 545w, /images/2019/04/athena-quicksight@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/04/athena-quicksight.png" alt="Overview: CloudFront, S3, Athena, QuickSight" title="Overview: CloudFront, S3, Athena, QuickSight"></picture></p><p>Next, I will describe how you can set up your analytics platform based on S3, Athena, and QuickSight resulting in a dashboard as shown in the following screenshot.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/04/quicksight-dashboard@730w.webp 730w, /images/2019/04/quicksight-dashboard@730w2x.webp 1460w, /images/2019/04/quicksight-dashboard@610w.webp 610w, /images/2019/04/quicksight-dashboard@610w2x.webp 1220w, /images/2019/04/quicksight-dashboard@450w.webp 450w, /images/2019/04/quicksight-dashboard@450w2x.webp 900w, /images/2019/04/quicksight-dashboard@330w.webp 330w, /images/2019/04/quicksight-dashboard@330w2x.webp 660w, /images/2019/04/quicksight-dashboard@545w.webp 545w, /images/2019/04/quicksight-dashboard@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/04/quicksight-dashboard@730w.jpg 730w, /images/2019/04/quicksight-dashboard@730w2x.jpg 1460w, /images/2019/04/quicksight-dashboard@610w.jpg 610w, /images/2019/04/quicksight-dashboard@610w2x.jpg 1220w, /images/2019/04/quicksight-dashboard@450w.jpg 450w, /images/2019/04/quicksight-dashboard@450w2x.jpg 900w, /images/2019/04/quicksight-dashboard@330w.jpg 330w, /images/2019/04/quicksight-dashboard@330w2x.jpg 660w, /images/2019/04/quicksight-dashboard@545w.jpg 545w, /images/2019/04/quicksight-dashboard@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/04/quicksight-dashboard.jpg" alt="QuickSight Dashboard" title="QuickSight Dashboard"></picture></p><h2 id="Collecting-access-logs"><a href="#Collecting-access-logs" class="headerlink" title="Collecting access logs"></a>Collecting access logs</h2><p>First, you need to collect data. CloudFront - as well as the Application Load Balancer - publishes access logs and ships them to S3 periodically. As shown in the following screenshot all you need to do is configuring your CloudFront distribution to upload the access logs to a specific S3 bucket <code>(2)</code> with a specific prefix <code>(1)</code> (aka. folder).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/04/cloudfront-access-logs@730w.webp 730w, /images/2019/04/cloudfront-access-logs@730w2x.webp 1460w, /images/2019/04/cloudfront-access-logs@610w.webp 610w, /images/2019/04/cloudfront-access-logs@610w2x.webp 1220w, /images/2019/04/cloudfront-access-logs@450w.webp 450w, /images/2019/04/cloudfront-access-logs@450w2x.webp 900w, /images/2019/04/cloudfront-access-logs@330w.webp 330w, /images/2019/04/cloudfront-access-logs@330w2x.webp 660w, /images/2019/04/cloudfront-access-logs@545w.webp 545w, /images/2019/04/cloudfront-access-logs@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/04/cloudfront-access-logs@730w.jpg 730w, /images/2019/04/cloudfront-access-logs@730w2x.jpg 1460w, /images/2019/04/cloudfront-access-logs@610w.jpg 610w, /images/2019/04/cloudfront-access-logs@610w2x.jpg 1220w, /images/2019/04/cloudfront-access-logs@450w.jpg 450w, /images/2019/04/cloudfront-access-logs@450w2x.jpg 900w, /images/2019/04/cloudfront-access-logs@330w.jpg 330w, /images/2019/04/cloudfront-access-logs@330w2x.jpg 660w, /images/2019/04/cloudfront-access-logs@545w.jpg 545w, /images/2019/04/cloudfront-access-logs@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/04/cloudfront-access-logs.jpg" alt="Collecting access logs from CloudFront" title="Collecting access logs from CloudFront"></picture></p><p>It might take some time, but in the end access logs should show up in your S3 bucket.</p><h2 id="Querying-access-logs"><a href="#Querying-access-logs" class="headerlink" title="Querying access logs"></a>Querying access logs</h2><p>Next, you need a way to query and analyze the access logs stored on S3. Amazon Athena enables you to run ad-hoc SQL queries on data stored on S3. You only pay for the data processed — no need to spin up any servers.</p><p>The following figure shows how to use SQL to query data with Athena.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/04/athena-query@730w.webp 730w, /images/2019/04/athena-query@730w2x.webp 1460w, /images/2019/04/athena-query@610w.webp 610w, /images/2019/04/athena-query@610w2x.webp 1220w, /images/2019/04/athena-query@450w.webp 450w, /images/2019/04/athena-query@450w2x.webp 900w, /images/2019/04/athena-query@330w.webp 330w, /images/2019/04/athena-query@330w2x.webp 660w, /images/2019/04/athena-query@545w.webp 545w, /images/2019/04/athena-query@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/04/athena-query@730w.jpg 730w, /images/2019/04/athena-query@730w2x.jpg 1460w, /images/2019/04/athena-query@610w.jpg 610w, /images/2019/04/athena-query@610w2x.jpg 1220w, /images/2019/04/athena-query@450w.jpg 450w, /images/2019/04/athena-query@450w2x.jpg 900w, /images/2019/04/athena-query@330w.jpg 330w, /images/2019/04/athena-query@330w2x.jpg 660w, /images/2019/04/athena-query@545w.jpg 545w, /images/2019/04/athena-query@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/04/athena-query.jpg" alt="Querying data with Athena" title="Querying data with Athena"></picture></p><p>Before you can query the access logs from CloudFront, you need to create a schema (aka. table). The following query creates a table named <code>cloudfront_logs</code>. Make sure you are replacing <code>&lt;BUCKET_NAME&gt;</code>and <code>&lt;PREFIX&gt;</code> with the destination of your CloudFormation access logs.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">EXTERNAL</span> <span class="keyword">TABLE</span> <span class="keyword">IF</span> <span class="keyword">NOT</span> <span class="keyword">EXISTS</span> cloudfront_logs (</span><br><span class="line">  `<span class="type">date</span>` <span class="type">DATE</span>,</span><br><span class="line">  <span class="type">time</span> STRING,</span><br><span class="line">  <span class="keyword">location</span> STRING,</span><br><span class="line">  bytes <span class="type">BIGINT</span>,</span><br><span class="line">  requestip STRING,</span><br><span class="line">  <span class="keyword">method</span> STRING,</span><br><span class="line">  host STRING,</span><br><span class="line">  uri STRING,</span><br><span class="line">  status <span class="type">INT</span>,</span><br><span class="line">  referrer STRING,</span><br><span class="line">  useragent STRING,</span><br><span class="line">  querystring STRING,</span><br><span class="line">  cookie STRING,</span><br><span class="line">  resulttype STRING,</span><br><span class="line">  requestid STRING,</span><br><span class="line">  hostheader STRING,</span><br><span class="line">  requestprotocol STRING,</span><br><span class="line">  requestbytes <span class="type">BIGINT</span>,</span><br><span class="line">  timetaken <span class="type">FLOAT</span>,</span><br><span class="line">  xforwardedfor STRING,</span><br><span class="line">  sslprotocol STRING,</span><br><span class="line">  sslcipher STRING,</span><br><span class="line">  responseresulttype STRING,</span><br><span class="line">  httpversion STRING,</span><br><span class="line">  filestatus STRING,</span><br><span class="line">  encryptedfields <span class="type">INT</span></span><br><span class="line">)</span><br><span class="line"><span class="keyword">ROW</span> <span class="keyword">FORMAT</span> DELIMITED </span><br><span class="line">FIELDS TERMINATED <span class="keyword">BY</span> <span class="string">&#x27;\t&#x27;</span></span><br><span class="line"><span class="keyword">LOCATION</span> <span class="string">&#x27;s3://&lt;BUCKET_NAME&gt;/&lt;PREFIX&gt;/&#x27;</span></span><br><span class="line">TBLPROPERTIES ( <span class="string">&#x27;skip.header.line.count&#x27;</span>=<span class="string">&#x27;2&#x27;</span> )</span><br></pre></td></tr></table></figure><p>To simplify queries, you should create two views on the <code>cloudfront_logs</code> table.</p><p>The following query creates a view which filters access from all kind of bots (e.g., the Google bot crawling your website). You might need to filter additional bots when analyzing access logs for your website.</p><figure class="highlight n1ql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">OR</span> <span class="built_in">REPLACE</span> <span class="keyword">VIEW</span> user_logs <span class="keyword">AS</span> <span class="keyword">SELECT</span> * <span class="keyword">FROM</span> cloudfront_logs </span><br><span class="line"><span class="keyword">WHERE</span> useragent <span class="keyword">NOT</span> <span class="keyword">LIKE</span> <span class="string">&#x27;%Googlebot%&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> useragent <span class="keyword">NOT</span> <span class="keyword">LIKE</span> <span class="string">&#x27;%bingbot%&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> useragent <span class="keyword">NOT</span> <span class="keyword">LIKE</span> <span class="string">&#x27;Twitterbot%&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> useragent <span class="keyword">NOT</span> <span class="keyword">LIKE</span> <span class="string">&#x27;%DuckDuckBot%&#x27;</span>;</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>Another view parses Urchin Tracking Module (UTM) parameters from the HTTP request. Doing so allows you to track traffic from campaigns (e.g., Google AdWords). Execute the following query to create a view which extracts the query parameters <code>utm_source</code>, <code>utm_medium</code>, <code>utm_campaign</code>, <code>utm_term</code>, and <code>utm_content</code>.</p><figure class="highlight n1ql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">OR</span> <span class="built_in">REPLACE</span> <span class="keyword">VIEW</span> utm_logs <span class="keyword">AS</span> </span><br><span class="line"><span class="keyword">SELECT</span> *, url_extract_parameter(<span class="string">&#x27;?&#x27;</span> || url_decode(querystring), <span class="string">&#x27;utm_source&#x27;</span>) <span class="keyword">AS</span> utm_source, </span><br><span class="line">url_extract_parameter(<span class="string">&#x27;?&#x27;</span> || url_decode(querystring), <span class="string">&#x27;utm_medium&#x27;</span>) <span class="keyword">AS</span> utm_medium, </span><br><span class="line">url_extract_parameter(<span class="string">&#x27;?&#x27;</span> || url_decode(querystring), <span class="string">&#x27;utm_campaign&#x27;</span>) <span class="keyword">AS</span> utm_campaign, </span><br><span class="line">url_extract_parameter(<span class="string">&#x27;?&#x27;</span> || url_decode(querystring), <span class="string">&#x27;utm_term&#x27;</span>) <span class="keyword">AS</span> utm_term, </span><br><span class="line">url_extract_parameter(<span class="string">&#x27;?&#x27;</span> || url_decode(querystring), <span class="string">&#x27;utm_content&#x27;</span>) <span class="keyword">AS</span> utm_content</span><br><span class="line"><span class="keyword">FROM</span> user_logs <span class="keyword">WHERE</span> querystring <span class="keyword">LIKE</span> <span class="string">&#x27;%utm_source%&#x27;</span>;</span><br></pre></td></tr></table></figure><p>After you have been creating the table and two views, run queries to analyze the traffic of your website.</p><p>The following query lists the most viewed pages. Keep in mind that our pages are represented as folders, therfore we only include URIs that end with an <code>/</code>.</p><figure class="highlight n1ql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> uri, <span class="built_in">COUNT</span>(*) hits <span class="keyword">FROM</span> user_logs</span><br><span class="line"><span class="keyword">WHERE</span> status = <span class="number">200</span> <span class="keyword">AND</span> uri <span class="keyword">LIKE</span> <span class="string">&#x27;%/&#x27;</span></span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> uri <span class="keyword">ORDER</span> <span class="keyword">BY</span> hits <span class="keyword">DESC</span></span><br><span class="line"><span class="keyword">LIMIT</span> <span class="number">100</span>;</span><br></pre></td></tr></table></figure><p>Another query returns the number of site hits per date.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="type">date</span>, COUNT(*) hits</span><br><span class="line"><span class="keyword">FROM</span> user_logs</span><br><span class="line"><span class="keyword">WHERE</span> status = <span class="number">200</span> <span class="keyword">AND</span> uri <span class="keyword">LIKE</span> <span class="string">&#x27;%/&#x27;</span></span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> <span class="type">date</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> <span class="type">date</span></span><br><span class="line"><span class="keyword">LIMIT</span> <span class="number">100</span>;</span><br></pre></td></tr></table></figure><p>Or you can query for site hits per Urchin Tracking Module (UTM) parameters.</p><figure class="highlight n1ql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> utm_source, utm_medium, utm_campaign, utm_term, utm_content, <span class="built_in">COUNT</span>(*) <span class="keyword">As</span> hits</span><br><span class="line"><span class="keyword">FROM</span> utm_logs</span><br><span class="line"><span class="keyword">WHERE</span> status = <span class="number">200</span> <span class="keyword">AND</span> uri <span class="keyword">LIKE</span> <span class="string">&#x27;%/&#x27;</span></span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> utm_source, utm_medium, utm_campaign, utm_term, utm_content <span class="keyword">HAVING</span> <span class="built_in">COUNT</span>(*) &gt; <span class="number">1</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> hits <span class="keyword">DESC</span></span><br><span class="line"><span class="keyword">LIMIT</span> <span class="number">100</span>;</span><br></pre></td></tr></table></figure><p>Feel free to bring in your SQL expertise to write your queries to analyze the access logs.</p><h2 id="Visualizing-access-logs"><a href="#Visualizing-access-logs" class="headerlink" title="Visualizing access logs"></a>Visualizing access logs</h2><p>The final step is to visualize the data you have analyzed with the help of Athena. QuickSight is a simple-to-use BI tool focusing on visualizing data and sharing insights.</p><p>As shown in the following screenshot you need to create a data set first. QuickSight integrates with a bunch of data sources. Make sure you are choosing <code>Athena</code> <code>(1)</code> when creating a new data source.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/04/quicksight-dataset-01@730w.webp 730w, /images/2019/04/quicksight-dataset-01@730w2x.webp 1460w, /images/2019/04/quicksight-dataset-01@610w.webp 610w, /images/2019/04/quicksight-dataset-01@610w2x.webp 1220w, /images/2019/04/quicksight-dataset-01@450w.webp 450w, /images/2019/04/quicksight-dataset-01@450w2x.webp 900w, /images/2019/04/quicksight-dataset-01@330w.webp 330w, /images/2019/04/quicksight-dataset-01@330w2x.webp 660w, /images/2019/04/quicksight-dataset-01@545w.webp 545w, /images/2019/04/quicksight-dataset-01@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/04/quicksight-dataset-01@730w.jpg 730w, /images/2019/04/quicksight-dataset-01@730w2x.jpg 1460w, /images/2019/04/quicksight-dataset-01@610w.jpg 610w, /images/2019/04/quicksight-dataset-01@610w2x.jpg 1220w, /images/2019/04/quicksight-dataset-01@450w.jpg 450w, /images/2019/04/quicksight-dataset-01@450w2x.jpg 900w, /images/2019/04/quicksight-dataset-01@330w.jpg 330w, /images/2019/04/quicksight-dataset-01@330w2x.jpg 660w, /images/2019/04/quicksight-dataset-01@545w.jpg 545w, /images/2019/04/quicksight-dataset-01@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/04/quicksight-dataset-01.jpg" alt="QuickSight Setup Data Set: Step 1" title="QuickSight Setup Data Set: Step 1"></picture></p><p>Name the data source <code>(1)</code> and click <code>Create data source</code> <code>(2)</code>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/04/quicksight-dataset-02@730w.webp 730w, /images/2019/04/quicksight-dataset-02@730w2x.webp 1460w, /images/2019/04/quicksight-dataset-02@610w.webp 610w, /images/2019/04/quicksight-dataset-02@610w2x.webp 1220w, /images/2019/04/quicksight-dataset-02@450w.webp 450w, /images/2019/04/quicksight-dataset-02@450w2x.webp 900w, /images/2019/04/quicksight-dataset-02@330w.webp 330w, /images/2019/04/quicksight-dataset-02@330w2x.webp 660w, /images/2019/04/quicksight-dataset-02@545w.webp 545w, /images/2019/04/quicksight-dataset-02@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/04/quicksight-dataset-02@730w.jpg 730w, /images/2019/04/quicksight-dataset-02@730w2x.jpg 1460w, /images/2019/04/quicksight-dataset-02@610w.jpg 610w, /images/2019/04/quicksight-dataset-02@610w2x.jpg 1220w, /images/2019/04/quicksight-dataset-02@450w.jpg 450w, /images/2019/04/quicksight-dataset-02@450w2x.jpg 900w, /images/2019/04/quicksight-dataset-02@330w.jpg 330w, /images/2019/04/quicksight-dataset-02@330w2x.jpg 660w, /images/2019/04/quicksight-dataset-02@545w.jpg 545w, /images/2019/04/quicksight-dataset-02@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/04/quicksight-dataset-02.jpg" alt="QuickSight Setup Data Set: Step 2" title="QuickSight Setup Data Set: Step 2"></picture></p><p>To make use of the custom queries you came up with in the previous section select <code>Use custom SQL</code> <code>(1)</code> in the following step of the wizard.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/04/quicksight-dataset-03@730w.webp 730w, /images/2019/04/quicksight-dataset-03@730w2x.webp 1460w, /images/2019/04/quicksight-dataset-03@610w.webp 610w, /images/2019/04/quicksight-dataset-03@610w2x.webp 1220w, /images/2019/04/quicksight-dataset-03@450w.webp 450w, /images/2019/04/quicksight-dataset-03@450w2x.webp 900w, /images/2019/04/quicksight-dataset-03@330w.webp 330w, /images/2019/04/quicksight-dataset-03@330w2x.webp 660w, /images/2019/04/quicksight-dataset-03@545w.webp 545w, /images/2019/04/quicksight-dataset-03@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/04/quicksight-dataset-03@730w.jpg 730w, /images/2019/04/quicksight-dataset-03@730w2x.jpg 1460w, /images/2019/04/quicksight-dataset-03@610w.jpg 610w, /images/2019/04/quicksight-dataset-03@610w2x.jpg 1220w, /images/2019/04/quicksight-dataset-03@450w.jpg 450w, /images/2019/04/quicksight-dataset-03@450w2x.jpg 900w, /images/2019/04/quicksight-dataset-03@330w.jpg 330w, /images/2019/04/quicksight-dataset-03@330w2x.jpg 660w, /images/2019/04/quicksight-dataset-03@545w.jpg 545w, /images/2019/04/quicksight-dataset-03@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/04/quicksight-dataset-03.jpg" alt="QuickSight Setup Data Set: Step 3" title="QuickSight Setup Data Set: Step 3"></picture></p><p>Next, you need to name the query <code>(1)</code>, insert your SQL query <code>(2)</code>, and click the <code>Confirm query</code> button <code>(3)</code>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/04/quicksight-dataset-04@730w.webp 730w, /images/2019/04/quicksight-dataset-04@730w2x.webp 1460w, /images/2019/04/quicksight-dataset-04@610w.webp 610w, /images/2019/04/quicksight-dataset-04@610w2x.webp 1220w, /images/2019/04/quicksight-dataset-04@450w.webp 450w, /images/2019/04/quicksight-dataset-04@450w2x.webp 900w, /images/2019/04/quicksight-dataset-04@330w.webp 330w, /images/2019/04/quicksight-dataset-04@330w2x.webp 660w, /images/2019/04/quicksight-dataset-04@545w.webp 545w, /images/2019/04/quicksight-dataset-04@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/04/quicksight-dataset-04@730w.jpg 730w, /images/2019/04/quicksight-dataset-04@730w2x.jpg 1460w, /images/2019/04/quicksight-dataset-04@610w.jpg 610w, /images/2019/04/quicksight-dataset-04@610w2x.jpg 1220w, /images/2019/04/quicksight-dataset-04@450w.jpg 450w, /images/2019/04/quicksight-dataset-04@450w2x.jpg 900w, /images/2019/04/quicksight-dataset-04@330w.jpg 330w, /images/2019/04/quicksight-dataset-04@330w2x.jpg 660w, /images/2019/04/quicksight-dataset-04@545w.jpg 545w, /images/2019/04/quicksight-dataset-04@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/04/quicksight-dataset-04.jpg" alt="QuickSight Setup Data Set: Step 4" title="QuickSight Setup Data Set: Step 4"></picture></p><p>I recommend that you use SPICE to cache the query results from Athena. Doing so speeds up the visualizations and minimizes Athena costs. Continue with visualizing the data <code>(1)</code>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/04/quicksight-dataset-05@730w.webp 730w, /images/2019/04/quicksight-dataset-05@730w2x.webp 1460w, /images/2019/04/quicksight-dataset-05@610w.webp 610w, /images/2019/04/quicksight-dataset-05@610w2x.webp 1220w, /images/2019/04/quicksight-dataset-05@450w.webp 450w, /images/2019/04/quicksight-dataset-05@450w2x.webp 900w, /images/2019/04/quicksight-dataset-05@330w.webp 330w, /images/2019/04/quicksight-dataset-05@330w2x.webp 660w, /images/2019/04/quicksight-dataset-05@545w.webp 545w, /images/2019/04/quicksight-dataset-05@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/04/quicksight-dataset-05@730w.jpg 730w, /images/2019/04/quicksight-dataset-05@730w2x.jpg 1460w, /images/2019/04/quicksight-dataset-05@610w.jpg 610w, /images/2019/04/quicksight-dataset-05@610w2x.jpg 1220w, /images/2019/04/quicksight-dataset-05@450w.jpg 450w, /images/2019/04/quicksight-dataset-05@450w2x.jpg 900w, /images/2019/04/quicksight-dataset-05@330w.jpg 330w, /images/2019/04/quicksight-dataset-05@330w2x.jpg 660w, /images/2019/04/quicksight-dataset-05@545w.jpg 545w, /images/2019/04/quicksight-dataset-05@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/04/quicksight-dataset-05.jpg" alt="QuickSight Setup Data Set: Step 5" title="QuickSight Setup Data Set: Step 5"></picture></p><p>It is time to visualize your data now. To do so, drag the fields <code>(1)</code> to the chart pane and select the visual type <code>(2)</code>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/04/quicksight-dataset-06@730w.webp 730w, /images/2019/04/quicksight-dataset-06@730w2x.webp 1460w, /images/2019/04/quicksight-dataset-06@610w.webp 610w, /images/2019/04/quicksight-dataset-06@610w2x.webp 1220w, /images/2019/04/quicksight-dataset-06@450w.webp 450w, /images/2019/04/quicksight-dataset-06@450w2x.webp 900w, /images/2019/04/quicksight-dataset-06@330w.webp 330w, /images/2019/04/quicksight-dataset-06@330w2x.webp 660w, /images/2019/04/quicksight-dataset-06@545w.webp 545w, /images/2019/04/quicksight-dataset-06@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/04/quicksight-dataset-06@730w.jpg 730w, /images/2019/04/quicksight-dataset-06@730w2x.jpg 1460w, /images/2019/04/quicksight-dataset-06@610w.jpg 610w, /images/2019/04/quicksight-dataset-06@610w2x.jpg 1220w, /images/2019/04/quicksight-dataset-06@450w.jpg 450w, /images/2019/04/quicksight-dataset-06@450w2x.jpg 900w, /images/2019/04/quicksight-dataset-06@330w.jpg 330w, /images/2019/04/quicksight-dataset-06@330w2x.jpg 660w, /images/2019/04/quicksight-dataset-06@545w.jpg 545w, /images/2019/04/quicksight-dataset-06@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/04/quicksight-dataset-06.jpg" alt="QuickSight Setup Data Set: Step 6" title="QuickSight Setup Data Set: Step 6"></picture></p><p>You have successfully created a data source as well as a visualization. You need to repeat the described process for each Athena query you want to visualize. The next steps are: creating an analysis consisting of multiple visualizations as well as sharing your analysis by creating a dashboard.</p><h2 id="Estimated-costs"><a href="#Estimated-costs" class="headerlink" title="Estimated costs"></a>Estimated costs</h2><p>The costs for the described analytics platform consist of the following items:</p><ul><li>S3: USD 0.023 per GB plus costs for read and write requests</li><li>Athena: USD 5.00 per TB of data analyzed</li><li>QuickSight: USD 9.00 per author per month for the Standard Edition (1 author is free)</li></ul><p>For example, we are paying less than USD 10.00 per month for gaining insights into the user experience of you, the readers of this blog.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>This blog is free of Google Analytics. Instead, I’m using S3, Athena and QuickSight to make data-driven decisions. For example, to decide which topics Michael and I should write about. The analytics solution does neither involve servers nor self-hosted applications. All three services are maintenance-free. But at the same time, the solution is extensible and gives me full control. Admittedly, the functionality of Google Analytics goes well beyond what I can achieve with based on the access logs. But for me, the possibilities are quite sufficient.</p><p>My plan for the future is to not only use access logs from CloudFront but to collect other kinds of events as well. I will keep you posted.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>6 unknown CloudFormation features you should know about</title>
      <link>https://cloudonaut.io/6-unknown-cloudformation-features-you-should-know-about/</link>
      <description>
        <![CDATA[<p>I was recently invited to a CloudFormation workshop with a group of early CloudFormation users. I soon realized that the group had a good]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <guid isPermaLink="true">https://cloudonaut.io/6-unknown-cloudformation-features-you-should-know-about/</guid>
      <pubDate>Mon, 15 Apr 2019 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>I was recently invited to a CloudFormation workshop with a group of early CloudFormation users. I soon realized that the group had a good understanding of the basics, so I started to introduce more advanced features.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/04/6-cloudformation-features-you-should-know-about@730w.webp 730w, /images/2019/04/6-cloudformation-features-you-should-know-about@730w2x.webp 1460w, /images/2019/04/6-cloudformation-features-you-should-know-about@610w.webp 610w, /images/2019/04/6-cloudformation-features-you-should-know-about@610w2x.webp 1220w, /images/2019/04/6-cloudformation-features-you-should-know-about@450w.webp 450w, /images/2019/04/6-cloudformation-features-you-should-know-about@450w2x.webp 900w, /images/2019/04/6-cloudformation-features-you-should-know-about@330w.webp 330w, /images/2019/04/6-cloudformation-features-you-should-know-about@330w2x.webp 660w, /images/2019/04/6-cloudformation-features-you-should-know-about@545w.webp 545w, /images/2019/04/6-cloudformation-features-you-should-know-about@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/04/6-cloudformation-features-you-should-know-about@730w.jpg 730w, /images/2019/04/6-cloudformation-features-you-should-know-about@730w2x.jpg 1460w, /images/2019/04/6-cloudformation-features-you-should-know-about@610w.jpg 610w, /images/2019/04/6-cloudformation-features-you-should-know-about@610w2x.jpg 1220w, /images/2019/04/6-cloudformation-features-you-should-know-about@450w.jpg 450w, /images/2019/04/6-cloudformation-features-you-should-know-about@450w2x.jpg 900w, /images/2019/04/6-cloudformation-features-you-should-know-about@330w.jpg 330w, /images/2019/04/6-cloudformation-features-you-should-know-about@330w2x.jpg 660w, /images/2019/04/6-cloudformation-features-you-should-know-about@545w.jpg 545w, /images/2019/04/6-cloudformation-features-you-should-know-about@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/04/6-cloudformation-features-you-should-know-about.jpg" alt="6 CloudFormation features you should know about" title="6 CloudFormation features you should know about"></picture></p><p>Today, I would like to share with you six CloudFormation features that have inspired the workshop participants most.</p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/25-cloudformation-unknown-features/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><h2 id="cfn-lint"><a href="#cfn-lint" class="headerlink" title="cfn-lint"></a>cfn-lint</h2><p>Writing CloudFormation templates is error prone. We should use every tool available to make our lives easier. I use the following tools:</p><ol><li><a href="https://github.com/adrienverge/yamllint" target="_blank" rel="noopener">yamllint</a> Locally ensures that our yaml is correct</li><li><a href="https://github.com/aws-cloudformation/cfn-python-lint" target="_blank" rel="noopener">cfn-lint</a> Locally checks if the template makes sense</li><li><a href="https://docs.aws.amazon.com/cli/latest/reference/cloudformation/validate-template.html" target="_blank" rel="noopener">aws cloudformation validate-template</a> Sends the template to AWS for validation</li></ol><p><code>yamllint</code> and <code>cfn-lint</code> are integrated into my editor, and I execute <code>aws cloudformation validate-template</code> before I <code>git commit</code>.</p><h2 id="Creation-policy"><a href="#Creation-policy" class="headerlink" title="Creation policy"></a>Creation policy</h2><p>A <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-creationpolicy.html" target="_blank" rel="noopener">CloudFormation creation policy</a> is helpful if you provision an EC2 instance, typically by using user data. By default, when CloudFormation creates and EC2 instance it will not wait for the operating system and application to be ready. With a creation policy, you can ask CloudFormation to wait for an external <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-signal.html" target="_blank" rel="noopener">signal</a>.</p><p>In the following snippet, you find a <code>CreationPolicy</code> that instructs CloudFormation to wait for a signal with a timeout of 10 minutes (<code>PT10M</code>). The <code>/opt/aws/bin/cfn-signal</code> command sends the signal to CloudFormation to indicate that the user data script finished successfully (<code>--exit-code 0</code>).</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#[...]</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">VirtualMachine:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::Instance&#x27;</span></span><br><span class="line">    <span class="attr">CreationPolicy:</span></span><br><span class="line">      <span class="attr">ResourceSignal:</span></span><br><span class="line">        <span class="attr">Timeout:</span> <span class="string">PT10M</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="comment">#[...] </span></span><br><span class="line">      <span class="attr">UserData:</span></span><br><span class="line">        <span class="attr">&#x27;Fn::Base64&#x27;:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">          #!/bin/bash -ex</span></span><br><span class="line"><span class="string">          # run some commands</span></span><br><span class="line"><span class="string">          /opt/aws/bin/cfn-signal --exit-code 0 --resource VirtualMachine --region $&#123;AWS::Region&#125; --stack $&#123;AWS::StackName&#125;</span></span><br></pre></td></tr></table></figure><p>Keep in mind that the logical id <code>VirtualMachine</code> of the resource needs to be referenced in <code>cfn-signal</code> using <code>--resource VirtualMachine</code>.</p><p>You can even improve this snippet to let CloudFormation know if the user data script failed using <code>#!/bin/bash -e</code>, <code>trap</code>, and <code>--exit-code 1</code>:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#[...]</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">VirtualMachine:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::Instance&#x27;</span></span><br><span class="line">    <span class="attr">CreationPolicy:</span></span><br><span class="line">      <span class="attr">ResourceSignal:</span></span><br><span class="line">        <span class="attr">Timeout:</span> <span class="string">PT10M</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">UserData:</span></span><br><span class="line">        <span class="attr">&#x27;Fn::Base64&#x27;:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">          #!/bin/bash -ex</span></span><br><span class="line"><span class="string">          trap &#x27;/opt/aws/bin/cfn-signal --exit-code 1 --resource VirtualMachine --region $&#123;AWS::Region&#125; --stack $&#123;AWS::StackName&#125;&#x27; ERR</span></span><br><span class="line"><span class="string">          # run some commands</span></span><br><span class="line"><span class="string">          /opt/aws/bin/cfn-signal --exit-code 0 --resource VirtualMachine --region $&#123;AWS::Region&#125; --stack $&#123;AWS::StackName&#125;</span></span><br></pre></td></tr></table></figure><p>Check out the following links if you are interested in real-world examples:</p><ul><li>EC2 instance: <a href="https://github.com/widdix/aws-cf-templates/blob/770f328bde1be6a867900d8672a0046643d17524/ec2/al2-mutable-private.yaml#L1375-L1378" target="_blank" rel="noopener">CreationPolicy</a> and <a href="https://github.com/widdix/aws-cf-templates/blob/770f328bde1be6a867900d8672a0046643d17524/ec2/al2-mutable-private.yaml#L1371" target="_blank" rel="noopener">cfn-signal</a></li><li>Auto Scaling Group: <a href="https://github.com/widdix/aws-cf-templates/blob/master/vpc/vpc-vpn-bastion.yaml#L720" target="_blank" rel="noopener">CreationPolicy</a> and <a href="https://github.com/widdix/aws-cf-templates/blob/master/vpc/vpc-vpn-bastion.yaml#L696" target="_blank" rel="noopener">cfn-signal</a> (Keep in mind that the <code>CreationPolicy</code> is attached to the auto scaling group while the <code>cfn-signal</code> is added to the user data in the launch configuration)</li></ul><h2 id="Update-policy"><a href="#Update-policy" class="headerlink" title="Update policy"></a>Update policy</h2><p>A <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html" target="_blank" rel="noopener">CloudFormation update policy</a> triggers a <a href="/rolling-update-with-aws-cloudformation/">rolling update</a> to make changes to an auto scaling group. A rolling update will execute your change in small batches (e.g., instance by instance).</p><p>The following CloudFormation snippet demonstrates a rolling update that waits for signals in the same way you learned about in the creation policy:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#[...]</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">LaunchConfiguration:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::AutoScaling::LaunchConfiguration&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="comment"># [...]</span></span><br><span class="line">  <span class="attr">AutoScalingGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::AutoScaling::AutoScalingGroup&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="comment"># [...]</span></span><br><span class="line">      <span class="attr">LaunchConfigurationName:</span> <span class="type">!Ref</span> <span class="string">LaunchConfiguration</span></span><br><span class="line">    <span class="attr">UpdatePolicy:</span></span><br><span class="line">      <span class="attr">AutoScalingRollingUpdate:</span></span><br><span class="line">        <span class="attr">PauseTime:</span> <span class="string">PT10M</span></span><br><span class="line">        <span class="attr">WaitOnResourceSignals:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>Check out the following links if you are interested in real-world examples:</p><ul><li><a href="https://github.com/widdix/aws-cf-templates/blob/master/vpc/vpc-ssh-bastion.yaml#L471" target="_blank" rel="noopener">Auto Scaling Group</a></li></ul><p>A update policy can do even <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html" target="_blank" rel="noopener">more</a>.</p><h2 id="Deletion-policy"><a href="#Deletion-policy" class="headerlink" title="Deletion policy"></a>Deletion policy</h2><p>A <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html" target="_blank" rel="noopener">CloudFormation deletion policy</a> can prevent serious data loss. If CloudFormation deletes a resource, you can instruct CloudFormation to perform a backup first. This works for the following resource types:</p><ul><li><code>AWS::RDS::DBInstance</code> (enabled by default)</li><li><code>AWS::RDS::DBCluster</code> (enabled by default)</li><li><code>AWS::EC2::Volume</code></li><li><code>AWS::ElastiCache::CacheCluster</code> (only Redis)</li><li><code>AWS::ElastiCache::ReplicationGroup</code></li><li><code>AWS::Redshift::Cluster</code></li><li><code>AWS::Neptune::DBCluster</code></li></ul><p>The following snippet shows a deletion policy:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#[...]</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Volume:</span></span><br><span class="line">    <span class="attr">DeletionPolicy:</span> <span class="string">Snapshot</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::Volume&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="comment">#[...] </span></span><br></pre></td></tr></table></figure><p>Check out the following links if you are interested in real-world examples:</p><ul><li><a href="https://github.com/cfn-modules/ebs-volume/blob/master/module.yml#L54" target="_blank" rel="noopener">EBS volume</a></li><li><a href="https://github.com/widdix/aws-cf-templates/blob/master/state/elasticache-redis.yaml#L172" target="_blank" rel="noopener">ElastiCache for Redis</a></li></ul><p>But what about other resource types? You can also instruct CloudFormation not to delete a resource at all. We use this to prevent deletions of KMS keys which can also cause data loss. Why is it dangerous to delete KMS keys? You can delete them even if they are in use. Sometimes it is now obvious that the key is still used in a snapshot. Once the key is deleted, the snapshot cannot be restored anymore.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#[...]</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Key:</span></span><br><span class="line">    <span class="attr">DeletionPolicy:</span> <span class="string">Retain</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::KMS::Key&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="comment">#[...] </span></span><br></pre></td></tr></table></figure><p>Check out the following links if you are interested in real-world examples:</p><ul><li><a href="https://github.com/widdix/aws-cf-templates/blob/master/security/kms-key.yaml#L65" target="_blank" rel="noopener">KMS key</a></li></ul><h2 id="Update-replace-policy"><a href="#Update-replace-policy" class="headerlink" title="Update replace policy"></a>Update replace policy</h2><p>The <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatereplacepolicy.html" target="_blank" rel="noopener">CloudFormation update replace policy</a> is similar to the deletion policy. You can use it to prevent data loss. If you update an existing stack, CloudFormation figures out what resources need to be updated. For some attributes, an update requires replacement of the resource. Therefore, CloudFormation creates the new resource and when successful, deletes the old one. If this resource was your database, you are in troubles.</p><p>The following resource types can be backed up before they are replaced:</p><ul><li><code>AWS::RDS::DBInstance</code></li><li><code>AWS::RDS::DBCluster</code></li><li><code>AWS::EC2::Volume</code></li><li><code>AWS::ElastiCache::CacheCluster</code> (only Redis)</li><li><code>AWS::ElastiCache::ReplicationGroup</code></li><li><code>AWS::Redshift::Cluster</code></li><li><code>AWS::Neptune::DBCluster</code></li></ul><p>The following snippet shows an update replace policy.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#[...]</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Volume:</span></span><br><span class="line">    <span class="attr">UpdateReplacePolicy:</span> <span class="string">Snapshot</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::Volume&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="comment">#[...] </span></span><br></pre></td></tr></table></figure><p>Check out the following links if you are interested in real-life examples:</p><ul><li><a href="https://github.com/widdix/aws-cf-templates/blob/master/state/rds-postgres.yaml#L163" target="_blank" rel="noopener">RDS instance</a></li><li><a href="https://github.com/widdix/aws-cf-templates/blob/master/state/rds-aurora.yaml#L211" target="_blank" rel="noopener">Aurora cluster</a></li><li><a href="https://github.com/cfn-modules/ebs-volume/blob/master/module.yml#L55" target="_blank" rel="noopener">EBS volume</a></li><li><a href="https://github.com/widdix/aws-cf-templates/blob/master/state/elasticache-redis.yaml#L173" target="_blank" rel="noopener">ElastiCache for Redis</a></li></ul><p>But what about other resource types? As with the deletion policy, you can also instruct CloudFormation to not delete a resource at all during a replacement.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#[...]</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Key:</span></span><br><span class="line">    <span class="attr">UpdateReplacePolicy:</span> <span class="string">Retain</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::KMS::Key&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="comment">#[...] </span></span><br></pre></td></tr></table></figure><h2 id="cfn-init"><a href="#cfn-init" class="headerlink" title="cfn-init"></a>cfn-init</h2><p>Writing shell scripts to configure and install en EC2 instance is error prone. Sometimes, you need a tool that is easier to use than a script but not as complex as Chef&#x2F;Puppet&#x2F;Ansible&#x2F;… </p><p><a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-init.html" target="_blank" rel="noopener">cfn-init</a> solves that problem. <code>cfn-init</code> is executed in the user data script and fetches configuration information from your CloudFormation stack. The configuration is attached as <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-init.html" target="_blank" rel="noopener">metadata</a> to the resource and supports:</p><ul><li>Execution of commands</li><li>Creation of files</li><li>Fetching of external sources (tar, tar+gzip, tar+bz2, and zip)</li><li>Creation of users and groups</li><li>Installation of packages (yum, rpm, gems)</li><li>Management of services</li></ul><p>The following CloudFormation snippet shows how you can install the package <code>awslogs</code>.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment">#[...]</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">VirtualMachine:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::Instance&#x27;</span></span><br><span class="line">    <span class="attr">Metadata:</span></span><br><span class="line">      <span class="attr">&#x27;AWS::CloudFormation::Init&#x27;:</span></span><br><span class="line">        <span class="attr">config:</span></span><br><span class="line">          <span class="attr">packages:</span></span><br><span class="line">            <span class="attr">yum:</span></span><br><span class="line">              <span class="attr">awslogs:</span> []</span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="comment">#[...] </span></span><br><span class="line">      <span class="attr">UserData:</span></span><br><span class="line">        <span class="attr">&#x27;Fn::Base64&#x27;:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">          #!/bin/bash -ex</span></span><br><span class="line"><span class="string">          /opt/aws/bin/cfn-init -v --resource VirtualMachine --region $&#123;AWS::Region&#125; --stack $&#123;AWS::StackName&#125;</span></span><br></pre></td></tr></table></figure><p>Check out the following links if you are interested in real-world examples:</p><ul><li><a href="https://github.com/widdix/aws-cf-templates/blob/master/vpc/vpc-ssh-bastion.yaml#L214" target="_blank" rel="noopener">Metadata</a> and <a href="https://github.com/widdix/aws-cf-templates/blob/master/vpc/vpc-ssh-bastion.yaml#L445" target="_blank" rel="noopener">cfn-init</a></li></ul><p>The user data script only runs once. If you update the stack and change the metadata, nothing happens. <a href="https://docs.aws.amazon.com/de_de/AWSCloudFormation/latest/UserGuide/cfn-hup.html" target="_blank" rel="noopener">cfn-hup</a> is a way ro run <code>cfn-init</code> regularly to notice and apply updates to the metadata.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>CloudFormation is a great service about which we have written more than <a href="/tag/cloudformation/">40 articles</a>. There are many features to discover such as the numerous policies that you can attach to resources: <code>CreationPolicy</code>, <code>DeletionPolicy</code>, <code>UpdatePolicy</code>, and <code>UpdateReplacePolicy</code>. Besides that, the tooling around CloudFormation has also evolved in the past years. <a href="https://github.com/aws-cloudformation/cfn-python-lint" target="_blank" rel="noopener">cfn-lint</a> saves you time by spotting failures in your templates early.</p><p>The <a href="https://www.reddit.com/r/aws/comments/bdhnj8/6_unknown_cloudformation_features_you_should_know/" target="_blank" rel="noopener">Reddit user coinclink mentioned another hidden feature</a> that I use often: <a href="https://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html" target="_blank" rel="noopener">aws cloudformation package</a> and <a href="https://docs.aws.amazon.com/cli/latest/reference/cloudformation/deploy.html" target="_blank" rel="noopener">aws cloudformation deploy</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Goodbye SSH, use AWS Session Manager instead</title>
      <link>https://cloudonaut.io/goodbye-ssh-use-aws-session-manager-instead/</link>
      <description>
        <![CDATA[<p>SSH is great. But the AWS Session Manager - whose full name is AWS Systems Manager Session Manager - matches the needs for interacting wi]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <guid isPermaLink="true">https://cloudonaut.io/goodbye-ssh-use-aws-session-manager-instead/</guid>
      <pubDate>Sun, 31 Mar 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>SSH is great. But the AWS Session Manager - whose full name is AWS Systems Manager Session Manager - matches the needs for interacting with your EC2 instances even better.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/03/terminal@730w.webp 730w, /images/2019/03/terminal@730w2x.webp 1460w, /images/2019/03/terminal@610w.webp 610w, /images/2019/03/terminal@610w2x.webp 1220w, /images/2019/03/terminal@450w.webp 450w, /images/2019/03/terminal@450w2x.webp 900w, /images/2019/03/terminal@330w.webp 330w, /images/2019/03/terminal@330w2x.webp 660w, /images/2019/03/terminal@545w.webp 545w, /images/2019/03/terminal@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/03/terminal@730w.jpg 730w, /images/2019/03/terminal@730w2x.jpg 1460w, /images/2019/03/terminal@610w.jpg 610w, /images/2019/03/terminal@610w2x.jpg 1220w, /images/2019/03/terminal@450w.jpg 450w, /images/2019/03/terminal@450w2x.jpg 900w, /images/2019/03/terminal@330w.jpg 330w, /images/2019/03/terminal@330w2x.jpg 660w, /images/2019/03/terminal@545w.jpg 545w, /images/2019/03/terminal@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/03/terminal.jpg" alt="Goodbye SSH!" title="Goodbye SSH!"></picture></p><h2 id="Benefits"><a href="#Benefits" class="headerlink" title="Benefits"></a>Benefits</h2><p>You should think about replacing SSH with the AWS Session Manager because of the following benefits.</p><h3 id="Simple-Authentication"><a href="#Simple-Authentication" class="headerlink" title="Simple Authentication"></a>Simple Authentication</h3><p>Unfortunately, AWS deploys a single key pair for authenticating via SSH to each EC2 instances. As sharing keys between engineers is a no go, you need to find a way to distribute a key pair per engineer to your EC2 instances. We have implemented this with <a href="https://github.com/widdix/aws-ec2-ssh" target="_blank" rel="noopener">widdix&#x2F;aws-ec2-ssh</a>, which admittedly is a handicraft solution.</p><p>The AWS Session Manager uses the Identity and Access Management (IAM) for authentication and authorization. Therefore, you can reuse IAM users or SSO with Azure AD, SAML, … to authenticate and authorize engineers when logging into EC2 instances as well.</p><p>Multi-factor authentication (MFA) is built into IAM by default. Therefore, it is simple to require administrators to authenticate with a second factor - e.g., an OTP app - before establishing a remote session with an EC2 instance.</p><h3 id="Built-in-Audit-Log"><a href="#Built-in-Audit-Log" class="headerlink" title="Built-in Audit Log"></a>Built-in Audit Log</h3><p>Tracking each command an administrator executes on an EC2 instance is a common security and compliance requirement. I don’t know of an easy to implement and tamper-proof way to do so with Linux onboard resources.</p><p>However, the AWS Session Manager offers audit logs by default. Each command is captured and stored at CloudWatch Logs or S3.</p><h3 id="Simple-Networking"><a href="#Simple-Networking" class="headerlink" title="Simple Networking"></a>Simple Networking</h3><p>SSH requires a network connection between the engineer’s machine and the EC2 instance. Setting up a jump server - also called bastion host - as shown in the following figure is a typical pattern to minimize the attack surface from the Internet. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/03/ssh-jump-host@730w.webp 730w, /images/2019/03/ssh-jump-host@730w2x.webp 1460w, /images/2019/03/ssh-jump-host@610w.webp 610w, /images/2019/03/ssh-jump-host@610w2x.webp 1220w, /images/2019/03/ssh-jump-host@450w.webp 450w, /images/2019/03/ssh-jump-host@450w2x.webp 900w, /images/2019/03/ssh-jump-host@330w.webp 330w, /images/2019/03/ssh-jump-host@330w2x.webp 660w, /images/2019/03/ssh-jump-host@545w.webp 545w, /images/2019/03/ssh-jump-host@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/03/ssh-jump-host@730w.png 730w, /images/2019/03/ssh-jump-host@730w2x.png 1460w, /images/2019/03/ssh-jump-host@610w.png 610w, /images/2019/03/ssh-jump-host@610w2x.png 1220w, /images/2019/03/ssh-jump-host@450w.png 450w, /images/2019/03/ssh-jump-host@450w2x.png 900w, /images/2019/03/ssh-jump-host@330w.png 330w, /images/2019/03/ssh-jump-host@330w2x.png 660w, /images/2019/03/ssh-jump-host@545w.png 545w, /images/2019/03/ssh-jump-host@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/03/ssh-jump-host.png" alt="SSH Jump Host" title="SSH Jump Host"></picture></p><p>Another option is to use client-to-site VPN connections. Both options add complexity to your networking infrastructure and require constant security patches and monitoring.</p><p>Luckily, with AWS Session Manager there is no need for a network connection between the engineer’s machine and the EC2 instance. This simplifies the networking infrastructure a lot and reduces security risk.</p><h2 id="How-it-works"><a href="#How-it-works" class="headerlink" title="How it works"></a>How it works</h2><p>The AWS Session Manager is part of the AWS Systems Manager service. The following diagram outlines how it works:</p><ol><li>The administrator authenticates against IAM (IAM user or SSO identity provider).</li><li>IAM authorizes to start a session for an EC2 instance (IAM policy).</li><li>The administrator uses the AWS Management Console or the terminal (AWS CLI and additional plugin required) to start a session via the Systems Manager.</li><li>An agent running on the EC2 instance connects to the Systems Manager’s backend and executes commands on the machine. Therefore, the EC2 instance needs access to the Internet or a VPC endpoint.</li><li>The Session Manager sends audit logs to CloudWatch Logs or S3.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/03/session-manager@730w.webp 730w, /images/2019/03/session-manager@730w2x.webp 1460w, /images/2019/03/session-manager@610w.webp 610w, /images/2019/03/session-manager@610w2x.webp 1220w, /images/2019/03/session-manager@450w.webp 450w, /images/2019/03/session-manager@450w2x.webp 900w, /images/2019/03/session-manager@330w.webp 330w, /images/2019/03/session-manager@330w2x.webp 660w, /images/2019/03/session-manager@545w.webp 545w, /images/2019/03/session-manager@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/03/session-manager@730w.png 730w, /images/2019/03/session-manager@730w2x.png 1460w, /images/2019/03/session-manager@610w.png 610w, /images/2019/03/session-manager@610w2x.png 1220w, /images/2019/03/session-manager@450w.png 450w, /images/2019/03/session-manager@450w2x.png 900w, /images/2019/03/session-manager@330w.png 330w, /images/2019/03/session-manager@330w2x.png 660w, /images/2019/03/session-manager@545w.png 545w, /images/2019/03/session-manager@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/03/session-manager.png" alt="AWS Session Manager" title="AWS Session Manager"></picture></p><p>Next, I will point you to the most important parts when configuring and securing the AWS Session Manager for your AWS infrastructure.</p><h2 id="How-to-configure"><a href="#How-to-configure" class="headerlink" title="How to configure"></a>How to configure</h2><p>The following configuration is required for AWS Systems Manager:</p><ol><li>Install the AWS Systems Manager agent on each EC2 instance (already installed on Amazon Linux).</li><li>Create an IAM role for the EC2 instance which grants access to the AWS Systems Manager.</li><li>Use IAM policies to restrict which IAM user or role can start a session with an EC2 instance.</li><li>Configure audit logs.</li><li>Use IAM policies to make sure engineers are not able to modify the audit log settings.</li></ol><h3 id="Configure-EC2-instances"><a href="#Configure-EC2-instances" class="headerlink" title="Configure EC2 instances"></a>Configure EC2 instances</h3><p>Make sure the SSM agent version 2.3.68.0 or later is installed on your EC2 instance. See <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html" target="_blank" rel="noopener">Installing and Configuring SSM Agent</a> if you need help with that.</p><p>To grant the SSM agent access to the Systems Manager’s backend you need to attach an IAM role to your EC2 instance. Make sure, that the managed IAM policy <code>AmazonEC2RoleforSSM</code> is attached to the IAM role. Be warned, the IAM policy <code>AmazonEC2RoleforSSM</code> grants excessive access to S3 and other services, write your own policy to implement the least privilege principle.</p><h3 id="Restrict-access-to-EC2-instances"><a href="#Restrict-access-to-EC2-instances" class="headerlink" title="Restrict access to EC2 instances"></a>Restrict access to EC2 instances</h3><p>After configuring your EC2 instances IAM users and roles can start a session. Probably, you don’t want all IAM users, and roles grant root access to your EC2 instances. Therefore, you need to create IAM policies to allow access for connecting to an EC2 instance to specific IAM users and roles.</p><p>The following snippet shows an IAM policy that grants access to EC2 instances with a specific tag: <code>Tag Key = team</code> and <code>Value = a</code>.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2012-10-17&quot;</span>,</span><br><span class="line">    <span class="string">&quot;Statement&quot;</span>: [</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Action&quot;</span>: [</span><br><span class="line">                <span class="string">&quot;ssm:StartSession&quot;</span></span><br><span class="line">            ],</span><br><span class="line">            <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;arn:aws:ec2:*:*:instance/*&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Condition&quot;</span>: &#123;</span><br><span class="line">                <span class="string">&quot;StringEquals&quot;</span>: &#123;</span><br><span class="line">                    <span class="string">&quot;ssm:resourceTag/team&quot;</span>: <span class="string">&quot;a&quot;</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Action&quot;</span>: [</span><br><span class="line">                <span class="string">&quot;ssm:GetConnectionStatus&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ssm:DescribeSessions&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ssm:DescribeInstanceProperties&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeInstances&quot;</span></span><br><span class="line">            ],</span><br><span class="line">            <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;*&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Please, double check that there is no IAM policy which grants access to <code>ssm:StartSession</code> or <code>ssm:ResumeSession</code> without resource restrictions or conditions attached to IAM users, groups or roles. Otherwise, an IAM user or role might be able to log into all EC2 instances as <code>root</code> or <code>Administrator</code>.</p><h3 id="Configure-audit-logs"><a href="#Configure-audit-logs" class="headerlink" title="Configure audit logs"></a>Configure audit logs</h3><p>Capturing audit logs of every administrator session on your EC2 instance is a built-in feature of the AWS Session Manager. AWS Systems Manager stores audit logs in a CloudWatch log group or an S3 bucket that you provide. However, you have to enable audit logs. The following screenshot shows the necessary steps.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/03/session-manager-audit-log@730w.webp 730w, /images/2019/03/session-manager-audit-log@730w2x.webp 1460w, /images/2019/03/session-manager-audit-log@610w.webp 610w, /images/2019/03/session-manager-audit-log@610w2x.webp 1220w, /images/2019/03/session-manager-audit-log@450w.webp 450w, /images/2019/03/session-manager-audit-log@450w2x.webp 900w, /images/2019/03/session-manager-audit-log@330w.webp 330w, /images/2019/03/session-manager-audit-log@330w2x.webp 660w, /images/2019/03/session-manager-audit-log@545w.webp 545w, /images/2019/03/session-manager-audit-log@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/03/session-manager-audit-log@730w.png 730w, /images/2019/03/session-manager-audit-log@730w2x.png 1460w, /images/2019/03/session-manager-audit-log@610w.png 610w, /images/2019/03/session-manager-audit-log@610w2x.png 1220w, /images/2019/03/session-manager-audit-log@450w.png 450w, /images/2019/03/session-manager-audit-log@450w2x.png 900w, /images/2019/03/session-manager-audit-log@330w.png 330w, /images/2019/03/session-manager-audit-log@330w2x.png 660w, /images/2019/03/session-manager-audit-log@545w.png 545w, /images/2019/03/session-manager-audit-log@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/03/session-manager-audit-log.png" alt="Configure audit logs" title="Configure audit logs"></picture></p><p>By default, the Systems Manager document <code>SSM-SessionManagerRunShell</code> is used to store your audit log preferences. Therefore, you need to make sure that engineers are not able to modify the Systems Manager document. The following snippet shows an IAM policy denying write access to the <code>SSM-SessionManagerRunShell</code> document.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2012-10-17&quot;</span>,</span><br><span class="line">    <span class="string">&quot;Statement&quot;</span>: [</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Deny&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Action&quot;</span>: [</span><br><span class="line">                <span class="string">&quot;ssm:CreateDocument&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ssm:UpdateDocument&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ssm:DeleteDocument&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ssm:UpdateDocumentDefaultVersion&quot;</span></span><br><span class="line">            ],</span><br><span class="line">            <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;arn:aws:ssm:*:*:document/SSM-SessionManagerRunShell&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Please, double check that there is no IAM policy which grants access to <code>ssm:CreateDocument</code>, <code>ssm:UpdateDocument</code>, <code>ssm:DeleteDocument</code>, and <code>ssm:UpdateDocumentDefaultVersion</code> without resource restrictions or conditions attached to IAM users, groups or roles. Otherwise, engineers can manipulate the audit log settings.</p><p>On top of that, it is advisable to extend the IAM policy from the previous section to only allow users, groups, roles to start a new session when using the document containing your audit log configuration. Replace <code>&lt;REGION&gt;</code> with the region you are operating in and <code>&lt;ACCOUNT&gt;</code> with your AWS account ID.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2012-10-17&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Statement&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Action&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;ssm:StartSession&quot;</span></span><br><span class="line">      ],</span><br><span class="line">      <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;arn:aws:ec2:*:*:instance/*&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Condition&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;StringEquals&quot;</span>: &#123;</span><br><span class="line">          <span class="string">&quot;ssm:resourceTag/team&quot;</span>: <span class="string">&quot;a&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Action&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;ssm:StartSession&quot;</span></span><br><span class="line">      ],</span><br><span class="line">      <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;arn:aws:ssm:&lt;REGION&gt;:&lt;ACCOUNT&gt;:document/SSM-SessionManagerRunShell&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Limitations"><a href="#Limitations" class="headerlink" title="Limitations"></a>Limitations</h2><p>There are a few limitations when using AWS Session Manager instead of SSH.</p><p>The most important limitation: transferring files is not possible with the AWS Session Manager. As a workaround, you could use an S3 bucket and the AWS CLI to exchange data. Doing so is of course not quite the same as using <code>scp</code>.</p><p>I’ve also struggled with canceling commands via <code>CTRL + C</code> from time to time. AWS Support promised to find a solution, and I will keep you posted.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Replacing SSH with the AWS Session Manager simplifies authentication, authorization, networking, as well as audit logs for administrator sessions on EC2 instances.</p><p>So is it already time to say goodbye to SSH? Yes, especially if you are aiming for immutable virtual machines and therefore only need remote access for debugging.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Three ways to run Docker on AWS</title>
      <link>https://cloudonaut.io/three-ways-to-run-docker-on-aws/</link>
      <description>
        <![CDATA[<p>There are a bunch of different ways to run your containerized workloads on AWS. This blog post compares the three most important ways to]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <category domain="https://cloudonaut.io/tag/kubernetes/">kubernetes</category>
      <category domain="https://cloudonaut.io/tag/eks/">eks</category>
      <guid isPermaLink="true">https://cloudonaut.io/three-ways-to-run-docker-on-aws/</guid>
      <pubDate>Tue, 19 Mar 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>There are a bunch of different ways to run your containerized workloads on AWS. This blog post compares the three most important ways to run Docker on AWS:</p><ul><li>Amazon Elastic Container Service (<strong>ECS</strong>) with AWS <strong>Fargate</strong></li><li>Amazon Elastic Container Service for Kubernetes (<strong>EKS</strong>)</li><li>AWS Elastic Beanstalk (<strong>EB</strong>) with Single Container Docker</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/03/container-jump@730w.webp 730w, /images/2019/03/container-jump@730w2x.webp 1460w, /images/2019/03/container-jump@610w.webp 610w, /images/2019/03/container-jump@610w2x.webp 1220w, /images/2019/03/container-jump@450w.webp 450w, /images/2019/03/container-jump@450w2x.webp 900w, /images/2019/03/container-jump@330w.webp 330w, /images/2019/03/container-jump@330w2x.webp 660w, /images/2019/03/container-jump@545w.webp 545w, /images/2019/03/container-jump@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/03/container-jump@730w.jpg 730w, /images/2019/03/container-jump@730w2x.jpg 1460w, /images/2019/03/container-jump@610w.jpg 610w, /images/2019/03/container-jump@610w2x.jpg 1220w, /images/2019/03/container-jump@450w.jpg 450w, /images/2019/03/container-jump@450w2x.jpg 900w, /images/2019/03/container-jump@330w.jpg 330w, /images/2019/03/container-jump@330w2x.jpg 660w, /images/2019/03/container-jump@545w.jpg 545w, /images/2019/03/container-jump@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/03/container-jump.jpg" alt="Three ways to run Docker on AWS" title="Three ways to run Docker on AWS"></picture></p><p>The following table compares the three different approaches.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th>ECS + Fargate</th><th>EKS</th><th>EB</th></tr></thead><tbody><tr><td>Complexity</td><td>High</td><td>Very High</td><td>Low</td></tr><tr><td>Flexibility</td><td>High</td><td>Very High</td><td>Medium</td></tr><tr><td>Operational effort</td><td>Very Low</td><td>Medium</td><td>Medium</td></tr><tr><td>Maturity of AWS integrations</td><td>Very High</td><td>Medium</td><td>High</td></tr><tr><td>Inter-service communication for microservices</td><td>High</td><td>High</td><td>n/a</td></tr><tr><td>Cloud agnostic?</td><td>No</td><td>Yes</td><td>No</td></tr><tr><td>Multiple applications per host?</td><td>Yes</td><td>Yes</td><td>No</td></tr></tbody></table><p>Below you will find more information about all three options.</p><h2 id="ECS-with-Fargate"><a href="#ECS-with-Fargate" class="headerlink" title="ECS with Fargate"></a>ECS with Fargate</h2><p>First, let’s have a look at ECS, a fully-managed container orchestration service. ECS is a proprietary but free of charge solution offered by AWS. It is important to mention that ECS provides a high level of integration with the AWS infrastructure. For example, containers are 1st class citizens of the VPC with their network interface (ENI) and security groups.</p><p>ECS offers service discovery via a load balancer or DNS (Cloud Map).</p><p>Aside from that ECS is the only option to run Docker containers without running EC2 instances on AWS. Fargate is the compute engine for ECS. All the heavy lifting of scaling the number of EC2 instances and containers, rolling out updates to EC2 instances without affecting containers, and many more is gone.</p><p>ECS is free of charge. Fargate is billed per second based on CPU and memory allocated for your containers. A container with 1 vCPU and 4 GB is about USD 30 per month.</p><p>Keep in mind the following limitations of Fargate:</p><ul><li>General purpose compute capacity only. Fargate does not support GPU, CPU&#x2F;memory optimized configurations at the moment.</li><li>Persistent volumes are not supported out of the box (e.g., Docker volume driver).</li><li>No discounts for reserved capacity available.</li></ul><h2 id="EKS-Kubernetes"><a href="#EKS-Kubernetes" class="headerlink" title="EKS (Kubernetes)"></a>EKS (Kubernetes)</h2><p>The 2nd option to run Docker containers on AWS is Kubernetes (K8s). In summary, K8s is an open-source container orchestration solution. AWS offers the K8s master layer as a service. The master layer is responsible for storing the state of the container cluster and deciding on which machines new containers should be placed. On top of that, you are responsible for managing a fleet of EC2 instances used to run the containers.</p><p>The main selling point for K8s: it is open-source and runs on AWS, Azure, Google Cloud, on-premises, or even on your local machine. The resulting disadvantage is that Kubernetes is not that well integrated with the AWS infrastructure.</p><p>Kubernetes is designed for microservice architectures. For example, a built-in service discovery allows containers to talk to each other easily by using a local proxy. </p><p>EKS is about USD 144 per month for the master layer. Besides, you are paying for the EC2 instances powering your containers. A <code>t3.medium</code> instance provides 2 CPUs with 4 GiB of memory and costs around USD 30 USD per month.</p><p>You should not underestimate the complexity of operating EKS and EC2. For example, the way EKS integrates with the VPC comes with a few unexpected limitations (see <a href="/eks-vs-ecs-orchestrating-containers-on-aws/">EKS vs. ECS: orchestrating containers on AWS</a> for more details).</p><h2 id="Elastic-Beanstalk"><a href="#Elastic-Beanstalk" class="headerlink" title="Elastic Beanstalk"></a>Elastic Beanstalk</h2><p>Another option to run Docker containers on AWS is Elastic Beanstalk. Some say Elastic Beanstalk is the PaaS (Platform-as-a-Service) offering from AWS. Nevertheless, Elastic Beanstalk is very easy to use. There are a bunch of environments to deploy your web application with Elastic Beanstalk. One of them is called <em>Single Container Docker</em>. This environment deploys a single Docker container to one or multiple EC2 instances.</p><p>Elastic Beanstalk is not only deploying your application; it is also creating the needed infrastructure consisting of a database, a load balancer, and EC2 instances. Important to note: Elastic Beanstalk creates EC2 instances automatically. But you are still responsible for these virtual machines they are not fully-managed by AWS.</p><p>Elastic Beanstalk is a proprietary but free of charge solution offered by AWS. You are only paying for the underlying infrastructure. For example, a <code>t3.medium</code> instance provides 2 CPUs with 4 GiB of memory and costs around USD 30 USD per month.</p><h2 id="My-recommendation"><a href="#My-recommendation" class="headerlink" title="My recommendation"></a>My recommendation</h2><p>My experience shows that managing the virtual machines for a dynamic container workload requires a lot of effort. The big difference between the three options discussed in this article is the service depth provided by AWS.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/03/three-ways-to-run-docker-on-aws@730w.webp 730w, /images/2019/03/three-ways-to-run-docker-on-aws@730w2x.webp 1460w, /images/2019/03/three-ways-to-run-docker-on-aws@610w.webp 610w, /images/2019/03/three-ways-to-run-docker-on-aws@610w2x.webp 1220w, /images/2019/03/three-ways-to-run-docker-on-aws@450w.webp 450w, /images/2019/03/three-ways-to-run-docker-on-aws@450w2x.webp 900w, /images/2019/03/three-ways-to-run-docker-on-aws@330w.webp 330w, /images/2019/03/three-ways-to-run-docker-on-aws@330w2x.webp 660w, /images/2019/03/three-ways-to-run-docker-on-aws@545w.webp 545w, /images/2019/03/three-ways-to-run-docker-on-aws@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/03/three-ways-to-run-docker-on-aws@730w.jpg 730w, /images/2019/03/three-ways-to-run-docker-on-aws@730w2x.jpg 1460w, /images/2019/03/three-ways-to-run-docker-on-aws@610w.jpg 610w, /images/2019/03/three-ways-to-run-docker-on-aws@610w2x.jpg 1220w, /images/2019/03/three-ways-to-run-docker-on-aws@450w.jpg 450w, /images/2019/03/three-ways-to-run-docker-on-aws@450w2x.jpg 900w, /images/2019/03/three-ways-to-run-docker-on-aws@330w.jpg 330w, /images/2019/03/three-ways-to-run-docker-on-aws@330w2x.jpg 660w, /images/2019/03/three-ways-to-run-docker-on-aws@545w.jpg 545w, /images/2019/03/three-ways-to-run-docker-on-aws@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/03/three-ways-to-run-docker-on-aws.jpg" alt="Three ways to run Docker on AWS" title="Three ways to run Docker on AWS"></picture></p><p>That is why I highly recommend using ECS and Fargate to run your Docker workloads on AWS. With Fargate you do not need to patch, scale, monitor virtual machines anymore. Imagine Fargate as a flexible and production-grade Platform-as-a-Service offering for Docker containers. No need to invest effort into your container platform any longer.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Fargate networking 101</title>
      <link>https://cloudonaut.io/fargate-networking-101/</link>
      <description>
        <![CDATA[<p>Fargate runs Docker containers on AWS. ECS is responsible for orchestrating the containers that Fargate runs. If you are new to Fargate,]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <guid isPermaLink="true">https://cloudonaut.io/fargate-networking-101/</guid>
      <pubDate>Tue, 12 Mar 2019 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Fargate runs Docker containers on AWS. ECS is responsible for orchestrating the containers that Fargate runs. If you are new to Fargate, I recommend you to read: <a href="/ecs-vs-fargate-whats-the-difference/">ECS vs. Fargate: What’s the difference?</a>. ECS and Fargate offer deep integration with other parts of AWS. You will learn how to master networking with Fargate while reading through this article.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/03/network@730w.webp 730w, /images/2019/03/network@730w2x.webp 1460w, /images/2019/03/network@610w.webp 610w, /images/2019/03/network@610w2x.webp 1220w, /images/2019/03/network@450w.webp 450w, /images/2019/03/network@450w2x.webp 900w, /images/2019/03/network@330w.webp 330w, /images/2019/03/network@330w2x.webp 660w, /images/2019/03/network@545w.webp 545w, /images/2019/03/network@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/03/network@730w.jpg 730w, /images/2019/03/network@730w2x.jpg 1460w, /images/2019/03/network@610w.jpg 610w, /images/2019/03/network@610w2x.jpg 1220w, /images/2019/03/network@450w.jpg 450w, /images/2019/03/network@450w2x.jpg 900w, /images/2019/03/network@330w.jpg 330w, /images/2019/03/network@330w2x.jpg 660w, /images/2019/03/network@545w.jpg 545w, /images/2019/03/network@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/03/network.jpg" alt="Network" title="Network"></picture></p><p>Before I dive into networking, I have to highlight an ECS specific concept: the task. An <a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/scheduling_tasks.html" target="_blank" rel="noopener">ECS Task</a> is a group of containers that share a volume or should communicate via a local link and are therefore placed onto the same machine. The following figure demonstrates two different tasks.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/03/ecs-task@730w.webp 730w, /images/2019/03/ecs-task@730w2x.webp 1460w, /images/2019/03/ecs-task@610w.webp 610w, /images/2019/03/ecs-task@610w2x.webp 1220w, /images/2019/03/ecs-task@450w.webp 450w, /images/2019/03/ecs-task@450w2x.webp 900w, /images/2019/03/ecs-task@330w.webp 330w, /images/2019/03/ecs-task@330w2x.webp 660w, /images/2019/03/ecs-task@545w.webp 545w, /images/2019/03/ecs-task@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/03/ecs-task@730w.png 730w, /images/2019/03/ecs-task@730w2x.png 1460w, /images/2019/03/ecs-task@610w.png 610w, /images/2019/03/ecs-task@610w2x.png 1220w, /images/2019/03/ecs-task@450w.png 450w, /images/2019/03/ecs-task@450w2x.png 900w, /images/2019/03/ecs-task@330w.png 330w, /images/2019/03/ecs-task@330w2x.png 660w, /images/2019/03/ecs-task@545w.png 545w, /images/2019/03/ecs-task@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/03/ecs-task.png" alt="The concept of an ECS Task" title="The concept of an ECS Task"></picture></p><p>A simple task consists only of a single container — for example, an Apache HTTP Server container. But there are also tasks where two containers work together. Think about an nginx container that forwards requests to a PHP-FPM container. When you interact with ECS, you can only start tasks, not containers.</p><p>Enough introduction about tasks. Let’s dive into networking.</p><h2 id="Task-networking-capabilities"><a href="#Task-networking-capabilities" class="headerlink" title="Task networking capabilities"></a>Task networking capabilities</h2><p>Each task that runs on Fargate is integrated into a VPC. More precisely, each task gets it’s own Elastic Network Interface (ENI). Therefore, a task behaves like an EC2 instance from a networking perspective. A task …</p><ul><li>runs in a single subnet.</li><li>is protected by a security group.</li><li>has a private IP address.</li><li>has an optional public IP address.</li></ul><p>The following figure shows the difference between a task running in a public and private subnet.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/03/ecs-task-networking@730w.webp 730w, /images/2019/03/ecs-task-networking@730w2x.webp 1460w, /images/2019/03/ecs-task-networking@610w.webp 610w, /images/2019/03/ecs-task-networking@610w2x.webp 1220w, /images/2019/03/ecs-task-networking@450w.webp 450w, /images/2019/03/ecs-task-networking@450w2x.webp 900w, /images/2019/03/ecs-task-networking@330w.webp 330w, /images/2019/03/ecs-task-networking@330w2x.webp 660w, /images/2019/03/ecs-task-networking@545w.webp 545w, /images/2019/03/ecs-task-networking@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/03/ecs-task-networking@730w.png 730w, /images/2019/03/ecs-task-networking@730w2x.png 1460w, /images/2019/03/ecs-task-networking@610w.png 610w, /images/2019/03/ecs-task-networking@610w2x.png 1220w, /images/2019/03/ecs-task-networking@450w.png 450w, /images/2019/03/ecs-task-networking@450w2x.png 900w, /images/2019/03/ecs-task-networking@330w.png 330w, /images/2019/03/ecs-task-networking@330w2x.png 660w, /images/2019/03/ecs-task-networking@545w.png 545w, /images/2019/03/ecs-task-networking@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/03/ecs-task-networking.png" alt="ECS Task networking" title="ECS Task networking"></picture></p><p>If a task runs in a private subnet (no route to an Internet Gateway), you can not access the task from the Internet, and you need a NAT Gateway to access the Internet.</p><blockquote><p>Keep in mind that NAT Gateways are bottlenecks (scales from 5 to 45 Gbps) and adds additional costs. To access the AWS API, you should make use of <a href="https://docs.aws.amazon.com/vpc/latest/userguide/vpc-endpoints.html" target="_blank" rel="noopener">VPC endpoints</a> instead.</p></blockquote><p>So far, I haven’t talked about how the Docker world is integrated with the AWS world. Imagine a task with two containers:</p><ul><li>nginx: runs the nginx process and listens on port 80 (TCP)</li><li>php-fpm: runs the PHP-FPM process and listens on port 9000 (TCP)</li></ul><p>When you use Docker locally, you use a command like this to run an nginx container:</p><p><code>docker run --rm -p 8080:80 nginx</code></p><p>The networking magic is configured with the <code>-p 8080:80</code> parameter. <code>-p</code> instructs Docker to make port 80 on the container available as port 8080 on the host (e.g., your local machine).</p><p>When using Fargate, you specify the ports that you want to make available in the <code>portMappings</code> section of your container definition. With Fargate, you set the <code>containerPort</code> to the port that the container listens to (e.g., 80 for nginx). Fargate will ensure that you then can access this container on the same port on the ENI.</p><blockquote><p>You can not run two containers in a task that listen to the same port!</p></blockquote><p>What does this mean for our example?</p><ul><li>From the nginx container, you can access PHP-FPM on <code>127.0.0.1:9000</code></li><li>From the php-fpm container, you can access nginx on <code>127.0.0.1:80</code></li><li>If the security groups allow inbound access on port 80, you can access nginx by making a request to the private and optional public IP address of the ENI using port 80.</li><li>If the security groups allow inbound access on port 9000, you can access PHP-FPM by making a request to the private and optional public IP address of the ENI using port 9000.</li></ul><p>So far, you learned how containers could talk to each other inside the same task over <code>127.0.0.1</code> and how you can talk to a task using the public or private IP address. Most workloads run many tasks of the same kind using auto-scaling. If a client requests your website, you want to forward this to one of the running tasks.</p><h2 id="Discovering-tasks"><a href="#Discovering-tasks" class="headerlink" title="Discovering tasks"></a>Discovering tasks</h2><p>Back to our example, imagine you have two tasks running (nginx + php-fpm) to host your web application. You place the tasks into a public subnet and enable public IP addresses. To make those public IP addresses available to your clients, you can create a DNS <code>A</code> record and map <code>www.app.io</code> to the two public IP addresses of the task ENIs as demonstrated in the following figure.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/03/dns-to-task@730w.webp 730w, /images/2019/03/dns-to-task@730w2x.webp 1460w, /images/2019/03/dns-to-task@610w.webp 610w, /images/2019/03/dns-to-task@610w2x.webp 1220w, /images/2019/03/dns-to-task@450w.webp 450w, /images/2019/03/dns-to-task@450w2x.webp 900w, /images/2019/03/dns-to-task@330w.webp 330w, /images/2019/03/dns-to-task@330w2x.webp 660w, /images/2019/03/dns-to-task@545w.webp 545w, /images/2019/03/dns-to-task@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/03/dns-to-task@730w.png 730w, /images/2019/03/dns-to-task@730w2x.png 1460w, /images/2019/03/dns-to-task@610w.png 610w, /images/2019/03/dns-to-task@610w2x.png 1220w, /images/2019/03/dns-to-task@450w.png 450w, /images/2019/03/dns-to-task@450w2x.png 900w, /images/2019/03/dns-to-task@330w.png 330w, /images/2019/03/dns-to-task@330w2x.png 660w, /images/2019/03/dns-to-task@545w.png 545w, /images/2019/03/dns-to-task@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/03/dns-to-task.png" alt="Statis DNS configuration to discover ECS tasks" title="Statis DNS configuration to discover ECS tasks"></picture></p><p>That would work. The only problem is that this configuration is static and relies heavily on DNS. Before I can start to dive into service discovery, I have to introduce another ECS specific concept: the service. An <a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs_services.html" target="_blank" rel="noopener">ECS service</a> is responsible for running a group of tasks of the same kind (using the same <a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definitions.html" target="_blank" rel="noopener">ECS Task Definition</a>). Auto Scaling Groups are a similar concept when working with EC2. An ECS service regularly checks if enough tasks are running. If not, tasks are started or stopped accordingly. Besides that, the ECS service also integrates with AWS services to enable service discovery.</p><h3 id="Application-and-Network-Load-Balancer"><a href="#Application-and-Network-Load-Balancer" class="headerlink" title="Application and Network Load Balancer"></a>Application and Network Load Balancer</h3><p>Both the Application Load Balancer (ALB) and the Network Load Balancer (NLB) provide a static endpoint for incoming requests for your clients. Incoming requests are distributed to a dynamic number of tasks as the following figure shows.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/03/service-discovery-with-load-balancer@730w.webp 730w, /images/2019/03/service-discovery-with-load-balancer@730w2x.webp 1460w, /images/2019/03/service-discovery-with-load-balancer@610w.webp 610w, /images/2019/03/service-discovery-with-load-balancer@610w2x.webp 1220w, /images/2019/03/service-discovery-with-load-balancer@450w.webp 450w, /images/2019/03/service-discovery-with-load-balancer@450w2x.webp 900w, /images/2019/03/service-discovery-with-load-balancer@330w.webp 330w, /images/2019/03/service-discovery-with-load-balancer@330w2x.webp 660w, /images/2019/03/service-discovery-with-load-balancer@545w.webp 545w, /images/2019/03/service-discovery-with-load-balancer@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/03/service-discovery-with-load-balancer@730w.png 730w, /images/2019/03/service-discovery-with-load-balancer@730w2x.png 1460w, /images/2019/03/service-discovery-with-load-balancer@610w.png 610w, /images/2019/03/service-discovery-with-load-balancer@610w2x.png 1220w, /images/2019/03/service-discovery-with-load-balancer@450w.png 450w, /images/2019/03/service-discovery-with-load-balancer@450w2x.png 900w, /images/2019/03/service-discovery-with-load-balancer@330w.png 330w, /images/2019/03/service-discovery-with-load-balancer@330w2x.png 660w, /images/2019/03/service-discovery-with-load-balancer@545w.png 545w, /images/2019/03/service-discovery-with-load-balancer@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/03/service-discovery-with-load-balancer.png" alt="Application and Network Load Balancer to discover tasks" title="Application and Network Load Balancer to discover tasks"></picture></p><p>The ECS service registers and deregisters tasks at the load balancer. The client does not need to know about the IP addresses of the tasks. Instead, the client sends the request to the load balancer. </p><p>Some additional benefits are:</p><ul><li>A load balancers provide metrics (traffic, latency, HTTP errors) and logs.</li><li>The load balancers can terminate HTTPS&#x2F;TLS for you.</li><li>The ALB can route requests based on hostname and path.</li><li>The ALB ensures that only valid HTTP requests are forwarded to your tasks.</li></ul><p>The downsides are:</p><ul><li>A load balancer adds costs.</li><li>The ALB adds latency.</li></ul><p>An ALB or NLB is more straightforward to manage. I recommend to use them as the entry point into your system from the outside world (frontend load balancing). If your services communicate heavily with each other, the additional latency and costs might be a problem. Read on for a second solution.</p><p>If you are interested in an example, check out our CloudFormation template on Github: <a href="https://github.com/widdix/aws-cf-templates/blob/master/fargate/service-dedicated-alb.yaml" target="_blank" rel="noopener">https://github.com/widdix/aws-cf-templates/blob/master/fargate/service-dedicated-alb.yaml</a></p><h3 id="ECS-service-Discovery"><a href="#ECS-service-Discovery" class="headerlink" title="ECS service Discovery"></a>ECS service Discovery</h3><p><a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-discovery.html" target="_blank" rel="noopener">ECS service discovery</a> is based on <a href="https://docs.aws.amazon.com/cloud-map/latest/dg/what-is-cloud-map.html" target="_blank" rel="noopener">AWS Cloud Map</a> and integrates with Route53 (DNS) for service discovery. The ECS service registers and deregisters tasks to keeps the DNS records up-to-date.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/03/service-discovery-with-cloud-map@730w.webp 730w, /images/2019/03/service-discovery-with-cloud-map@730w2x.webp 1460w, /images/2019/03/service-discovery-with-cloud-map@610w.webp 610w, /images/2019/03/service-discovery-with-cloud-map@610w2x.webp 1220w, /images/2019/03/service-discovery-with-cloud-map@450w.webp 450w, /images/2019/03/service-discovery-with-cloud-map@450w2x.webp 900w, /images/2019/03/service-discovery-with-cloud-map@330w.webp 330w, /images/2019/03/service-discovery-with-cloud-map@330w2x.webp 660w, /images/2019/03/service-discovery-with-cloud-map@545w.webp 545w, /images/2019/03/service-discovery-with-cloud-map@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/03/service-discovery-with-cloud-map@730w.png 730w, /images/2019/03/service-discovery-with-cloud-map@730w2x.png 1460w, /images/2019/03/service-discovery-with-cloud-map@610w.png 610w, /images/2019/03/service-discovery-with-cloud-map@610w2x.png 1220w, /images/2019/03/service-discovery-with-cloud-map@450w.png 450w, /images/2019/03/service-discovery-with-cloud-map@450w2x.png 900w, /images/2019/03/service-discovery-with-cloud-map@330w.png 330w, /images/2019/03/service-discovery-with-cloud-map@330w2x.png 660w, /images/2019/03/service-discovery-with-cloud-map@545w.png 545w, /images/2019/03/service-discovery-with-cloud-map@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/03/service-discovery-with-cloud-map.png" alt="Cloud Map to discover tasks" title="Cloud Map to discover tasks"></picture></p><p>A client resolves the DNS name and gets back an IP address pointing to one of the service’s tasks. The client connects to the task directly. I only recommend this way of service discovery for inter-service communication with retries and DNS resolvers respecting the TTL of records.</p><p>If you are interested in an example, check out our CloudFormation template on Github: <a href="https://github.com/widdix/aws-cf-templates/blob/master/fargate/service-cloudmap.yaml" target="_blank" rel="noopener">https://github.com/widdix/aws-cf-templates/blob/master/fargate/service-cloudmap.yaml</a></p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Fargate networking is very close to what you know from EC2. You can think of a task as an EC2 instance and a container as a process. If you want to distribute end user requests to multiple tasks, you can use an Application or Network Load Balancer. For your inter-service communication, you might want to check out ECS service Discovery aka Cloud Map.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Beginner-friendly mobile backend based on AWS AppSync</title>
      <link>https://cloudonaut.io/beginner-friendly-mobile-backend-based-on-aws-appsync/</link>
      <description>
        <![CDATA[<p>Are you looking for a way to build a backend for a mobile or web application on AWS? You should check out the newcomer announced by AWS l]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/appsync/">appsync</category>
      <guid isPermaLink="true">https://cloudonaut.io/beginner-friendly-mobile-backend-based-on-aws-appsync/</guid>
      <pubDate>Thu, 07 Mar 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Are you looking for a way to build a backend for a mobile or web application on AWS? You should check out the newcomer announced by AWS last year: AWS AppSync which provides a GraphQL based API endpoint. AppSync is serverless and very beginner friendly.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/03/mobile@730w.webp 730w, /images/2019/03/mobile@730w2x.webp 1460w, /images/2019/03/mobile@610w.webp 610w, /images/2019/03/mobile@610w2x.webp 1220w, /images/2019/03/mobile@450w.webp 450w, /images/2019/03/mobile@450w2x.webp 900w, /images/2019/03/mobile@330w.webp 330w, /images/2019/03/mobile@330w2x.webp 660w, /images/2019/03/mobile@545w.webp 545w, /images/2019/03/mobile@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/03/mobile@730w.jpg 730w, /images/2019/03/mobile@730w2x.jpg 1460w, /images/2019/03/mobile@610w.jpg 610w, /images/2019/03/mobile@610w2x.jpg 1220w, /images/2019/03/mobile@450w.jpg 450w, /images/2019/03/mobile@450w2x.jpg 900w, /images/2019/03/mobile@330w.jpg 330w, /images/2019/03/mobile@330w2x.jpg 660w, /images/2019/03/mobile@545w.jpg 545w, /images/2019/03/mobile@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/03/mobile.jpg" alt="Beginner-friendly mobile backend based on AWS AppSync" title="Beginner-friendly mobile backend based on AWS AppSync"></picture></p><h2 id="What-is-GraphQL-and-why-do-we-need-it"><a href="#What-is-GraphQL-and-why-do-we-need-it" class="headerlink" title="What is GraphQL and why do we need it?"></a>What is GraphQL and why do we need it?</h2><p>GraphQL is a mobile-first approach to build an API and is, therefore, an exciting alternative to RESTful APIs. Facebook is the main contributor to GraphQL. What are the main advantages of GraphQL?</p><ul><li><strong>Minimizing the data transfer</strong>: The query language allows the client to ask for exactly the data that is needed, nothing more and nothing less.</li><li><strong>Reducing the number of requests</strong>: A request cannot only access a single resource but combine multiple queries.</li><li><strong>Defining a schema</strong>: A schema is the core of each GraphQL API. The used type system makes sure your API is well-defined by default.</li><li><strong>Evolving an API</strong>: Changing an API without the need of versioned APIs is possible due to the flexible schema and clients defining their expected responses.</li><li><strong>Unifying access</strong>: A typical use case for a GraphQL is to unify the access to different backend systems (e.g., legacy applications, microservices, …).</li></ul><p>A side note, GraphQL has nothing to do with a graph database. With GraphQL you define your API and might use a SQL, NoSQL, or even more specialized database behind.</p><p>Next, we will have a look at GraphQL as a Service on AWS: <strong>AppSync</strong>.</p><h2 id="Sample-Application-with-AWS-AppSync"><a href="#Sample-Application-with-AWS-AppSync" class="headerlink" title="Sample Application with AWS AppSync"></a>Sample Application with AWS AppSync</h2><p>AppSync is very easy to use. All you need to do to create a Serverless API is:</p><ol><li>Create a GraphQL schema.</li><li>Define the data sources: DynamoDB, Lambda, Elasticsearch, generic HTTP, …</li><li>Configure a mapping between your GraphQL schema and the data sources: resolvers.</li></ol><p>The repository <a href="https://github.com/widdix/aws-cutting-edge-appsync" target="_blank" rel="noopener">widdix&#x2F;aws-cutting-edge-appsync</a> contains a sample application based on AppSync, DynamoDB, and Lambda. The sample application <em>Favorite AWS Architecture</em> lets you vote for the services you would include in your favorite AWS architecture.</p><p>The following figure shows the architecture of the <em>Favorite AWS Architecture</em> sample application which is typical for a web application based on AppSync:</p><ul><li><strong>AppSync</strong>: the GraphQL API endpoint.</li><li><strong>S3 Bucket</strong>: stores and delivers the static assets of the single page application.</li><li><strong>Lambda function</strong>: executes your business logic.</li><li><strong>DynamoDB</strong>: the NoSQL database.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/03/appsync@730w.webp 730w, /images/2019/03/appsync@730w2x.webp 1460w, /images/2019/03/appsync@610w.webp 610w, /images/2019/03/appsync@610w2x.webp 1220w, /images/2019/03/appsync@450w.webp 450w, /images/2019/03/appsync@450w2x.webp 900w, /images/2019/03/appsync@330w.webp 330w, /images/2019/03/appsync@330w2x.webp 660w, /images/2019/03/appsync@545w.webp 545w, /images/2019/03/appsync@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/03/appsync@730w.jpg 730w, /images/2019/03/appsync@730w2x.jpg 1460w, /images/2019/03/appsync@610w.jpg 610w, /images/2019/03/appsync@610w2x.jpg 1220w, /images/2019/03/appsync@450w.jpg 450w, /images/2019/03/appsync@450w2x.jpg 900w, /images/2019/03/appsync@330w.jpg 330w, /images/2019/03/appsync@330w2x.jpg 660w, /images/2019/03/appsync@545w.jpg 545w, /images/2019/03/appsync@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/03/appsync.jpg" alt="Typical architecture based on AppSync" title="Typical architecture based on AppSync"></picture></p><h3 id="Defining-a-Schema"><a href="#Defining-a-Schema" class="headerlink" title="Defining a Schema"></a>Defining a Schema</h3><p>A GraphQL schema consists of the following parts:</p><ul><li><strong>Queries</strong> define the read-only operations.</li><li><strong>Mutations</strong> specify the write operations.</li><li><strong>Subscriptions</strong> configure long-lived connections for receiving real-time data.</li></ul><p>A GraphQL schema is comparable to an Open API specification (formerly known as Swagger).</p><p>We define the following queries, mutations, and subscriptions for the <em>Favorite AWS Architecture</em> sample application.</p><figure class="highlight elm"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="type">Query</span> &#123;</span><br><span class="line">  getVotingResults(nextToken: <span class="type">String</span>): <span class="type">VotingResults</span></span><br><span class="line">  getServices(nextToken: <span class="type">String</span>): <span class="type">Services</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="type">Mutation</span> &#123;</span><br><span class="line">  vote(service: <span class="type">ServiceType</span>!): <span class="type">Boolean</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="type">Subscription</span> &#123;</span><br><span class="line">  voted: <span class="type">Boolean</span></span><br><span class="line">  @aws_subscribe(mutations: [&quot;vote&quot;])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>On top of that, The GraphQL schema contains the type definition.</p><p>The following snippet shows the type definition for our <em>Favorite AWS Architecture</em> sample application.</p><figure class="highlight elm"><table><tr><td class="code"><pre><span class="line"><span class="title">enum</span> <span class="type">ServiceType</span> &#123;</span><br><span class="line">  ec2</span><br><span class="line">  lambda</span><br><span class="line">  fargate</span><br><span class="line">  clb</span><br><span class="line">  nlb</span><br><span class="line">  alb</span><br><span class="line">  appsync</span><br><span class="line">  apigateway</span><br><span class="line">  eks</span><br><span class="line">  ecs</span><br><span class="line">  rds_aurora</span><br><span class="line">  rds_postgres</span><br><span class="line">  rds_mysql</span><br><span class="line">  rds_mariadb</span><br><span class="line">  dynamodb</span><br><span class="line">  s3</span><br><span class="line">  efs</span><br><span class="line">  ebs</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="type">Services</span> &#123;</span><br><span class="line">  items: [<span class="type">Service</span>]</span><br><span class="line">  nextToken: <span class="type">String</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="type">Service</span> &#123;</span><br><span class="line">  type: <span class="type">ServiceType</span></span><br><span class="line">  name: <span class="type">String</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="type">VotingResults</span> &#123;</span><br><span class="line">  items: [<span class="type">VotingResult</span>]</span><br><span class="line">  nextToken: <span class="type">String</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="type">VotingResult</span> &#123;</span><br><span class="line">  service: <span class="type">ServiceType</span>!</span><br><span class="line">  upvotes: <span class="type">Int</span>!</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>AppSync supports the GraphQL scalar types <code>ID</code>, <code>String</code>, <code>Int</code>, <code>Float</code>, and <code>Boolean</code>. On top of that, AppSync defines additional scalar types like <code>AWSDate</code>, <code>AWSEmail</code>, and <code>AWSURL</code>. See <a href="https://docs.aws.amazon.com/appsync/latest/devguide/scalars.html" target="_blank" rel="noopener">Scalar types in AWS AppSync</a> for more details.</p><p>Even interfaces and unions are possible with GraphQL. See <a href="https://docs.aws.amazon.com/appsync/latest/devguide/interfaces-and-unions.html" target="_blank" rel="noopener">Interfaces and Unions in GraphQL</a> for more details.</p><h3 id="Supported-data-sources"><a href="#Supported-data-sources" class="headerlink" title="Supported data sources"></a>Supported data sources</h3><p>AppSync supports the following data sources out of the box:</p><ul><li>A <strong>DynamoDB</strong> table.</li><li>A <strong>Elasticsearch</strong> domain.</li><li>A <strong>Lambda</strong> function.</li><li>A <strong>RDS Aurora</strong> database cluster.</li><li>Any <strong>HTTP endpoint</strong>.</li></ul><p>Our sample application makes use of two data sources: DynamoDB and Lambda. As usual, we are following an infrastructure as code approach based on CloudFormation. The following snippet shows how to define a DynamoDB table, a Lambda function, and the AppSync data sources.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">VoteTable:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">AWS::DynamoDB::Table</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AttributeDefinitions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">AttributeName:</span> <span class="string">service</span></span><br><span class="line">      <span class="attr">AttributeType:</span> <span class="string">S</span></span><br><span class="line">    <span class="attr">BillingMode:</span> <span class="string">PAY_PER_REQUEST</span></span><br><span class="line">    <span class="attr">KeySchema:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">AttributeName:</span> <span class="string">service</span></span><br><span class="line">      <span class="attr">KeyType:</span> <span class="string">HASH</span></span><br><span class="line"><span class="attr">VoteDataSource:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::AppSync::DataSource&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AMAZON_DYNAMODB</span></span><br><span class="line">    <span class="attr">ServiceRoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;VoteRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">ApiId:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;GraphQLApi.ApiId&#x27;</span></span><br><span class="line">    <span class="attr">Name:</span> <span class="string">vote</span></span><br><span class="line">    <span class="attr">DynamoDBConfig:</span></span><br><span class="line">      <span class="attr">TableName:</span> <span class="type">!Ref</span> <span class="string">VoteTable</span></span><br><span class="line">      <span class="attr">AwsRegion:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::Region&#x27;</span></span><br><span class="line"><span class="attr">ServicesFunction:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::Lambda::Function&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Handler:</span> <span class="string">&#x27;index.handler&#x27;</span></span><br><span class="line">    <span class="attr">Runtime:</span> <span class="string">&#x27;nodejs8.10&#x27;</span></span><br><span class="line">    <span class="attr">MemorySize:</span> <span class="number">128</span></span><br><span class="line">    <span class="attr">Timeout:</span> <span class="number">30</span></span><br><span class="line">    <span class="attr">Role:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;ServicesFunctionRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">Code:</span></span><br><span class="line">      <span class="attr">ZipFile:</span> <span class="string">|</span></span><br><span class="line"><span class="string">        &#x27;use strict&#x27;;</span></span><br><span class="line"><span class="string">        exports.handler = async (event, context) =&gt; &#123;</span></span><br><span class="line"><span class="string">          return &#123;</span></span><br><span class="line"><span class="string">            items: [</span></span><br><span class="line"><span class="string">              &#123;type: &quot;ec2&quot;, name: &quot;ec2&quot;&#125;,</span></span><br><span class="line"><span class="string">              // ...</span></span><br><span class="line"><span class="string">            ]&#125;;</span></span><br><span class="line"><span class="string">        &#125;;</span></span><br><span class="line"><span class="string"></span><span class="attr">ServicesDataSource:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::AppSync::DataSource&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS_LAMBDA</span></span><br><span class="line">    <span class="attr">ServiceRoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;ServicesRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">ApiId:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;GraphQLApi.ApiId&#x27;</span></span><br><span class="line">    <span class="attr">Name:</span> <span class="string">service</span></span><br><span class="line">    <span class="attr">LambdaConfig:</span></span><br><span class="line">      <span class="attr">LambdaFunctionArn:</span> <span class="type">!GetAtt</span> <span class="string">ServicesFunction.Arn</span></span><br></pre></td></tr></table></figure><p>Next, we need to connect the data sources to the GraphQL schema. To do so, we need to configure resolvers.</p><h3 id="Resolvers"><a href="#Resolvers" class="headerlink" title="Resolvers"></a>Resolvers</h3><p>Besides connecting a data source to a query or mutation, a resolver allows you to configure mapping templates for the data sources’ request and response. The template engine <strong>Velocity</strong> is used by AppSync.</p><p>So, first of all, you need to write a request mapping template which transforms the incoming request to a format that the downstream data source can process.</p><p>In our sample application <em>Favorite AWS Architecture</em> the following request mapping template is used to query all data from the DynamoDB table storing the voting results from <code>getVotingResults(nextToken: String): VotingResults</code>.</p><figure class="highlight leaf"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;version&quot;: &quot;2017-02-28&quot;,</span><br><span class="line">  &quot;operation&quot;: &quot;Scan&quot;,</span><br><span class="line">  &quot;nextToken&quot;: <span class="punctuation">#</span><span class="keyword">if</span><span class="params">( $<span class="variable">context</span>.<span class="variable">args</span>.<span class="variable">nextToken</span> )</span> &quot;$context.args.nextToken&quot; <span class="punctuation">#</span><span class="keyword">else</span> null <span class="punctuation">#</span><span class="keyword">end</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The response mapping template is even simpler. It just forwards the result from DynamoDB to the client in JSON format.</p><figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line"><span class="variable">$util</span><span class="selector-class">.toJson</span>(<span class="variable">$context</span>.result)</span><br></pre></td></tr></table></figure><p>Again, we are using CloudFormation to create the resolver itself.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">VotingResultsResolver:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::AppSync::Resolver&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">TypeName:</span> <span class="string">Query</span></span><br><span class="line">    <span class="attr">DataSourceName:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;VoteDataSource.Name&#x27;</span></span><br><span class="line">    <span class="attr">RequestMappingTemplateS3Location:</span> <span class="string">&#x27;./getVotingResults-request.vtl&#x27;</span></span><br><span class="line">    <span class="attr">ResponseMappingTemplateS3Location:</span> <span class="string">&#x27;./getVotingResults-response.vtl&#x27;</span></span><br><span class="line">    <span class="attr">ApiId:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;GraphQLApi.ApiId&#x27;</span></span><br><span class="line">    <span class="attr">FieldName:</span> <span class="string">getVotingResults</span></span><br></pre></td></tr></table></figure><p>AppSync allows you to chain resolvers into a so-called pipeline as well. See <a href="https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html" target="_blank" rel="noopener">Pipeline Resolvers</a> for more details.</p><h2 id="Subscriptions-and-offline-access"><a href="#Subscriptions-and-offline-access" class="headerlink" title="Subscriptions and offline access"></a>Subscriptions and offline access</h2><p>GraphQL is built for mobile applications. Network connectivity is limited on mobile devices. Therefore, GraphQL frameworks like <a href="https://www.apollographql.com/" target="_blank" rel="noopener">Apollo</a> support caching and offline data access by default.</p><p>On top of that, AppSync offers subscriptions - implemented with MQTT over WebSockets - which allows you to notify the client about changes on the API. Necessary to know, with AppSync subscriptions are invoked as a response to a mutation. There is no other way to send a message to the client.</p><p>When to use subscriptions instead of polling or manually re-fetching data?</p><ul><li>Initial data set is significant, but only small increments change over time.</li><li>Low-latency updates of the client are essential.</li></ul><p>We are using subscriptions to update the voting results for all clients after each change for our <em>Favorite AWS Architecture</em> sample application.</p><p>The following snippet shows the subscription <code>voted</code> defined on the mutation <code>vote</code>.</p><figure class="highlight elm"><table><tr><td class="code"><pre><span class="line"><span class="keyword">type</span> <span class="type">Mutation</span> &#123;</span><br><span class="line">  vote(service: <span class="type">ServiceType</span>!): <span class="type">Boolean</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="type">Subscription</span> &#123;</span><br><span class="line">  voted: <span class="type">Boolean</span></span><br><span class="line">  @aws_subscribe(mutations: [&quot;vote&quot;])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Deployment-Pipeline"><a href="#Deployment-Pipeline" class="headerlink" title="Deployment Pipeline"></a>Deployment Pipeline</h2><p>It is very convenient to deploy AppSync with CloudFormation. I built my first deployment pipeline for the <em>Favorite AWS Architecture</em> sample application based on CloudFormation, CodeBuild, and CodePipeline within two hours.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>That’s it. AppSync is an easy and powerful way to build Serverless APIs on AWS. My favorite AWS architectures right now:</p><ul><li>AppSync with Lambda and DynamoDB.</li><li>ALB with Fargate and RDS Aurora.</li></ul><p>Check out <a href="https://www.youtube.com/watch?v=CwLB0BRwIqE&t=1959s" target="_blank" rel="noopener">Ten Tips And Tricks for Improving Your GraphQL API with AWS AppSync</a> from re:Invent 2018 for practical tips as well.</p><hr><p>This blog post is based on our talk <em>Cutting-Edge Architectures Based on AppSync, Lambda, and Fargate</em>. More than 500 engineers joined us for this talk at the AWS Summit in Berlin. Want us to inspire your developers and architects as well? <a href="mailto:&#104;&#x65;&#108;&#x6c;&#x6f;&#x40;&#99;&#x6c;&#111;&#117;&#x64;&#x6f;&#110;&#97;&#x75;&#x74;&#46;&#x69;&#111;">Invite us</a> to a session at your location.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>A brief history of AWS architectures</title>
      <link>https://cloudonaut.io/a-brief-history-of-aws-architectures/</link>
      <description>
        <![CDATA[<p>The way you create architectures on AWS has evolved over the last ten years.</p>
<p><picture class="img-fluid"><source type="image/webp"]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <guid isPermaLink="true">https://cloudonaut.io/a-brief-history-of-aws-architectures/</guid>
      <pubDate>Tue, 26 Feb 2019 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The way you create architectures on AWS has evolved over the last ten years.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/02/aws-history@730w.webp 730w, /images/2019/02/aws-history@730w2x.webp 1460w, /images/2019/02/aws-history@610w.webp 610w, /images/2019/02/aws-history@610w2x.webp 1220w, /images/2019/02/aws-history@450w.webp 450w, /images/2019/02/aws-history@450w2x.webp 900w, /images/2019/02/aws-history@330w.webp 330w, /images/2019/02/aws-history@330w2x.webp 660w, /images/2019/02/aws-history@545w.webp 545w, /images/2019/02/aws-history@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/02/aws-history@730w.jpg 730w, /images/2019/02/aws-history@730w2x.jpg 1460w, /images/2019/02/aws-history@610w.jpg 610w, /images/2019/02/aws-history@610w2x.jpg 1220w, /images/2019/02/aws-history@450w.jpg 450w, /images/2019/02/aws-history@450w2x.jpg 900w, /images/2019/02/aws-history@330w.jpg 330w, /images/2019/02/aws-history@330w2x.jpg 660w, /images/2019/02/aws-history@545w.jpg 545w, /images/2019/02/aws-history@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/02/aws-history.jpg" alt="A brief history of AWS architectures" title="A brief history of AWS architectures"></picture></p><p>In this blog post, I demonstrate what is better today than ten years before.</p><h2 id="2009"><a href="#2009" class="headerlink" title="2009"></a>2009</h2><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/02/aws-architectures-2009@730w.webp 730w, /images/2019/02/aws-architectures-2009@730w2x.webp 1460w, /images/2019/02/aws-architectures-2009@610w.webp 610w, /images/2019/02/aws-architectures-2009@610w2x.webp 1220w, /images/2019/02/aws-architectures-2009@450w.webp 450w, /images/2019/02/aws-architectures-2009@450w2x.webp 900w, /images/2019/02/aws-architectures-2009@330w.webp 330w, /images/2019/02/aws-architectures-2009@330w2x.webp 660w, /images/2019/02/aws-architectures-2009@545w.webp 545w, /images/2019/02/aws-architectures-2009@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/02/aws-architectures-2009@730w.png 730w, /images/2019/02/aws-architectures-2009@730w2x.png 1460w, /images/2019/02/aws-architectures-2009@610w.png 610w, /images/2019/02/aws-architectures-2009@610w2x.png 1220w, /images/2019/02/aws-architectures-2009@450w.png 450w, /images/2019/02/aws-architectures-2009@450w2x.png 900w, /images/2019/02/aws-architectures-2009@330w.png 330w, /images/2019/02/aws-architectures-2009@330w2x.png 660w, /images/2019/02/aws-architectures-2009@545w.png 545w, /images/2019/02/aws-architectures-2009@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/02/aws-architectures-2009.png" alt="2009" title="2009"></picture></p><p>In 2009, a typical architecture on AWS used the following components:</p><ul><li>Classic Load Balancer (CLB): Distribute requests across a fleet of EC2 instances.</li><li>Auto Scaling Group (ASG) with EC2 instances: Manage a dynamic fleet of virtual machines.</li><li>RDS database: Fully managed MySQL database offering.</li></ul><h2 id="2015"><a href="#2015" class="headerlink" title="2015"></a>2015</h2><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/02/aws-architectures-2015@730w.webp 730w, /images/2019/02/aws-architectures-2015@730w2x.webp 1460w, /images/2019/02/aws-architectures-2015@610w.webp 610w, /images/2019/02/aws-architectures-2015@610w2x.webp 1220w, /images/2019/02/aws-architectures-2015@450w.webp 450w, /images/2019/02/aws-architectures-2015@450w2x.webp 900w, /images/2019/02/aws-architectures-2015@330w.webp 330w, /images/2019/02/aws-architectures-2015@330w2x.webp 660w, /images/2019/02/aws-architectures-2015@545w.webp 545w, /images/2019/02/aws-architectures-2015@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/02/aws-architectures-2015@730w.png 730w, /images/2019/02/aws-architectures-2015@730w2x.png 1460w, /images/2019/02/aws-architectures-2015@610w.png 610w, /images/2019/02/aws-architectures-2015@610w2x.png 1220w, /images/2019/02/aws-architectures-2015@450w.png 450w, /images/2019/02/aws-architectures-2015@450w2x.png 900w, /images/2019/02/aws-architectures-2015@330w.png 330w, /images/2019/02/aws-architectures-2015@330w2x.png 660w, /images/2019/02/aws-architectures-2015@545w.png 545w, /images/2019/02/aws-architectures-2015@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/02/aws-architectures-2015.png" alt="2015" title="2015"></picture></p><p>2015 was an innovative year. AWS launched the Elastic Container Service (ECS) to orchestrate Docker containers running on EC2 instances. RDS Aurora was launched to push the limits of relational databases.</p><p>Besides that, AWS launched a new category of services to build Serverless architectures:</p><ul><li>Lambda functions: Execute code</li><li>API Gateway (RESTful): Trigger Lambda functions on HTTP requests</li><li>DynamoDB (2012): NoSQL database</li></ul><h2 id="2016"><a href="#2016" class="headerlink" title="2016"></a>2016</h2><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/02/aws-architectures-2016@730w.webp 730w, /images/2019/02/aws-architectures-2016@730w2x.webp 1460w, /images/2019/02/aws-architectures-2016@610w.webp 610w, /images/2019/02/aws-architectures-2016@610w2x.webp 1220w, /images/2019/02/aws-architectures-2016@450w.webp 450w, /images/2019/02/aws-architectures-2016@450w2x.webp 900w, /images/2019/02/aws-architectures-2016@330w.webp 330w, /images/2019/02/aws-architectures-2016@330w2x.webp 660w, /images/2019/02/aws-architectures-2016@545w.webp 545w, /images/2019/02/aws-architectures-2016@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/02/aws-architectures-2016@730w.png 730w, /images/2019/02/aws-architectures-2016@730w2x.png 1460w, /images/2019/02/aws-architectures-2016@610w.png 610w, /images/2019/02/aws-architectures-2016@610w2x.png 1220w, /images/2019/02/aws-architectures-2016@450w.png 450w, /images/2019/02/aws-architectures-2016@450w2x.png 900w, /images/2019/02/aws-architectures-2016@330w.png 330w, /images/2019/02/aws-architectures-2016@330w2x.png 660w, /images/2019/02/aws-architectures-2016@545w.png 545w, /images/2019/02/aws-architectures-2016@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/02/aws-architectures-2016.png" alt="2016" title="2016"></picture></p><p>The Application Load Balancer (ALB) was released in 2016. The ALB is a layer 7 HTTP(S) load balancer with more capabilities than the CLB. Sounds like a minor innovation? It was a big deal for container workloads.</p><h2 id="2017"><a href="#2017" class="headerlink" title="2017"></a>2017</h2><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/02/aws-architectures-2017@730w.webp 730w, /images/2019/02/aws-architectures-2017@730w2x.webp 1460w, /images/2019/02/aws-architectures-2017@610w.webp 610w, /images/2019/02/aws-architectures-2017@610w2x.webp 1220w, /images/2019/02/aws-architectures-2017@450w.webp 450w, /images/2019/02/aws-architectures-2017@450w2x.webp 900w, /images/2019/02/aws-architectures-2017@330w.webp 330w, /images/2019/02/aws-architectures-2017@330w2x.webp 660w, /images/2019/02/aws-architectures-2017@545w.webp 545w, /images/2019/02/aws-architectures-2017@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/02/aws-architectures-2017@730w.png 730w, /images/2019/02/aws-architectures-2017@730w2x.png 1460w, /images/2019/02/aws-architectures-2017@610w.png 610w, /images/2019/02/aws-architectures-2017@610w2x.png 1220w, /images/2019/02/aws-architectures-2017@450w.png 450w, /images/2019/02/aws-architectures-2017@450w2x.png 900w, /images/2019/02/aws-architectures-2017@330w.png 330w, /images/2019/02/aws-architectures-2017@330w2x.png 660w, /images/2019/02/aws-architectures-2017@545w.png 545w, /images/2019/02/aws-architectures-2017@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/02/aws-architectures-2017.png" alt="2017" title="2017"></picture></p><p>In 2017, the Network Load Balancer was launched. The NLB is a layer 4 TCP load balancer for high throughput (millions of requests per second) and low latency workloads. It uses the same API constructs than the ALB.</p><p>The big launch in 2017 was Fargate. Fargate runs your Docker containers orchestrated by ECS. You no longer have to operate the EC2 instances to host the containers.</p><h2 id="2018"><a href="#2018" class="headerlink" title="2018"></a>2018</h2><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/02/aws-arcvhitectures-2018@730w.webp 730w, /images/2019/02/aws-arcvhitectures-2018@730w2x.webp 1460w, /images/2019/02/aws-arcvhitectures-2018@610w.webp 610w, /images/2019/02/aws-arcvhitectures-2018@610w2x.webp 1220w, /images/2019/02/aws-arcvhitectures-2018@450w.webp 450w, /images/2019/02/aws-arcvhitectures-2018@450w2x.webp 900w, /images/2019/02/aws-arcvhitectures-2018@330w.webp 330w, /images/2019/02/aws-arcvhitectures-2018@330w2x.webp 660w, /images/2019/02/aws-arcvhitectures-2018@545w.webp 545w, /images/2019/02/aws-arcvhitectures-2018@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/02/aws-arcvhitectures-2018@730w.png 730w, /images/2019/02/aws-arcvhitectures-2018@730w2x.png 1460w, /images/2019/02/aws-arcvhitectures-2018@610w.png 610w, /images/2019/02/aws-arcvhitectures-2018@610w2x.png 1220w, /images/2019/02/aws-arcvhitectures-2018@450w.png 450w, /images/2019/02/aws-arcvhitectures-2018@450w2x.png 900w, /images/2019/02/aws-arcvhitectures-2018@330w.png 330w, /images/2019/02/aws-arcvhitectures-2018@330w2x.png 660w, /images/2019/02/aws-arcvhitectures-2018@545w.png 545w, /images/2019/02/aws-arcvhitectures-2018@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/02/aws-arcvhitectures-2018.png" alt="2018" title="2018"></picture></p><p>In 2018, the relational database scalability issue was finally solved for most workloads with RDS Aurora Serverless. RDS Aurora Serverless provides an auto-scaled relational database for you.</p><p>Furthermore, App Sync was launched. App Sync can be seen as a replacement for API Gateway. App Sync is based on GraphQL and integrates natively with Lambda, DynamoDB, and other Serverless services.</p><h2 id="2019"><a href="#2019" class="headerlink" title="2019"></a>2019</h2><p>I’m curious what we will hear from AWS this year.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>ECS vs. Fargate: What's the difference?</title>
      <link>https://cloudonaut.io/ecs-vs-fargate-whats-the-difference/</link>
      <description>
        <![CDATA[<p>When discussing options to run Docker on AWS, I’m often asked about the differences between ECS and <a href="/eks-vs-ecs-orchestrating-co]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <guid isPermaLink="true">https://cloudonaut.io/ecs-vs-fargate-whats-the-difference/</guid>
      <pubDate>Thu, 21 Feb 2019 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>When discussing options to run Docker on AWS, I’m often asked about the differences between ECS and <a href="/eks-vs-ecs-orchestrating-containers-on-aws/">EKS</a> or <a href="/ecs-vs-kubernetes/">Kubernets</a>. However, lately, a new question arises: What’s the difference between ECS and <a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html" target="_blank" rel="noopener">AWS Fargate</a>? In this blog post, you get the answer. You also learn about the advantages and disadvantages of both options.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/02/difference@730w.webp 730w, /images/2019/02/difference@730w2x.webp 1460w, /images/2019/02/difference@610w.webp 610w, /images/2019/02/difference@610w2x.webp 1220w, /images/2019/02/difference@450w.webp 450w, /images/2019/02/difference@450w2x.webp 900w, /images/2019/02/difference@330w.webp 330w, /images/2019/02/difference@330w2x.webp 660w, /images/2019/02/difference@545w.webp 545w, /images/2019/02/difference@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/02/difference@730w.jpg 730w, /images/2019/02/difference@730w2x.jpg 1460w, /images/2019/02/difference@610w.jpg 610w, /images/2019/02/difference@610w2x.jpg 1220w, /images/2019/02/difference@450w.jpg 450w, /images/2019/02/difference@450w2x.jpg 900w, /images/2019/02/difference@330w.jpg 330w, /images/2019/02/difference@330w2x.jpg 660w, /images/2019/02/difference@545w.jpg 545w, /images/2019/02/difference@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/02/difference.jpg" alt="ECS vs. Fargate: What's the difference?" title="ECS vs. Fargate: What's the difference?"></picture></p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/33-ecs-vs-fargate-what-is-the-difference/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>To understand the difference, let’s divide the ECS service into two responsibilities:</p><ol><li>Managing the lifecycle and placement of tasks</li><li>Running containers</li></ol><h2 id="Managing-the-lifecycle-and-placement-of-tasks"><a href="#Managing-the-lifecycle-and-placement-of-tasks" class="headerlink" title="Managing the lifecycle and placement of tasks"></a>Managing the lifecycle and placement of tasks</h2><p>First, ECS is responsible for managing the lifecycle and placement of tasks. A task is usually made of one or two containers that work together, e.g., an <a href="https://hub.docker.com/_/nginx" target="_blank" rel="noopener">nginx</a> container with a <a href="https://hub.docker.com/_/php/" target="_blank" rel="noopener">php-fpm</a> container. You can ask ECS to start or stop a task, and it stores your intent. However, ECS does not run or execute your container. ECS only provides the control plane to manage tasks. So, who runs the containers?</p><h2 id="Running-containers"><a href="#Running-containers" class="headerlink" title="Running containers"></a>Running containers</h2><p>To run containers, you have two options. You can use ECS container instances, or you can use Fargate. Both options work together with ECS. The following figure demonstrates the difference.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/02/ecs-container-instance-fargate@730w.webp 730w, /images/2019/02/ecs-container-instance-fargate@730w2x.webp 1460w, /images/2019/02/ecs-container-instance-fargate@610w.webp 610w, /images/2019/02/ecs-container-instance-fargate@610w2x.webp 1220w, /images/2019/02/ecs-container-instance-fargate@450w.webp 450w, /images/2019/02/ecs-container-instance-fargate@450w2x.webp 900w, /images/2019/02/ecs-container-instance-fargate@330w.webp 330w, /images/2019/02/ecs-container-instance-fargate@330w2x.webp 660w, /images/2019/02/ecs-container-instance-fargate@545w.webp 545w, /images/2019/02/ecs-container-instance-fargate@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/02/ecs-container-instance-fargate@730w.png 730w, /images/2019/02/ecs-container-instance-fargate@730w2x.png 1460w, /images/2019/02/ecs-container-instance-fargate@610w.png 610w, /images/2019/02/ecs-container-instance-fargate@610w2x.png 1220w, /images/2019/02/ecs-container-instance-fargate@450w.png 450w, /images/2019/02/ecs-container-instance-fargate@450w2x.png 900w, /images/2019/02/ecs-container-instance-fargate@330w.png 330w, /images/2019/02/ecs-container-instance-fargate@330w2x.png 660w, /images/2019/02/ecs-container-instance-fargate@545w.png 545w, /images/2019/02/ecs-container-instance-fargate@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/02/ecs-container-instance-fargate.png" alt="ECS: container instance versus Fargate" title="ECS: container instance versus Fargate"></picture></p><h3 id="ECS-container-instance"><a href="#ECS-container-instance" class="headerlink" title="ECS container instance"></a>ECS container instance</h3><p>An ECS container instance is nothing more than an EC2 instance that runs the <a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ECS_agent.html" target="_blank" rel="noopener">ECS Container Agent</a>. The EC2 instance is owned and managed by you. The instance appears in the list of EC2 instances like any other EC2 instance. The ECS Container Agent regularly polls the ECS API if new containers need to be started or stopped. Usually, you run a cluster of container instances in an auto-scaling group. ECS is free of charge. You only pay for the EC2 instances. The downside is that you have to scale, monitor, patch, and secure the EC2 instances yourself. Especially the scaling is not easy because:</p><ul><li>There is no obvious metric to scale the cluster and no integration to scale when the task placement fails because of insufficient capacity.</li><li>The auto-scaling group and ECS are not aware of each other, making task deployments very hard during cluster scale in or rolling updates via CloudFormation (Capacity Providers address this issue but are not ready for prime time yet).</li><li>You have to <a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/container-instance-draining.html" target="_blank" rel="noopener">scale down without killing running tasks</a>, which is an even more significant challenge for long-lived tasks.</li></ul><p>Even the <a href="https://github.com/aws-samples/ecs-refarch-cloudformation" target="_blank" rel="noopener">AWS reference architecture</a> does not include auto-scaling for the cluster. Check out our <a href="https://templates.cloudonaut.io/en/stable/ecs/" target="_blank" rel="noopener">reference architecture with auto-scaling</a> if you are interested.</p><p>An ECS container instance can run on Linux or Windows. Unused CPU shares can be used by other containers if available.</p><h3 id="Fargate"><a href="#Fargate" class="headerlink" title="Fargate"></a>Fargate</h3><p>AWS Fargate manages the task execution. No EC2 instances to manage anymore. You pay for running tasks. That’s it. As easy as it sounds.</p><p>Each task that runs in Fargate comes with a dedicated Elastic Network Interface (ENI) with a private IP address. All containers of the same task can communicate with each other via localhost. Inbound and outbound task communication goes through the ENI. A public IP address can be enabled as well.</p><h3 id="Comparison"><a href="#Comparison" class="headerlink" title="Comparison"></a>Comparison</h3><table class="table table-striped"><thead><tr><th></th><th>ECS container instance</th><th>Fargate</th></tr></thead><tbody><tr><td>Host OS</td><td>Linux, Windows</td><td>Linux</td></tr><tr><td>Max vCPU</td><td>448</td><td>4</td></tr><tr><td>Max Memory</td><td>26 TB</td><td>30 GB</td></tr><tr><td>CPU bursting</td><td>Linux: yes; Windows: no</td><td>no</td></tr><tr><td>Pricing</td><td>per running EC2 instance</td><td>per running task</td></tr><tr><td>Discounts</td><td>Reserved Instances, Savings Plans, Spot</td><td>Compute Savings Plans, Spot</td></tr><tr><td>Operational effort</td><td>high</td><td>low</td></tr><tr><td>EFS integration</td><td>yes</td><td>yes</td></tr><tr><td>EBS integration</td><td>hacky but possible <sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup></td><td>no</td></tr><tr><td>Networking options</td><td>multiple <sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup></td><td>ENI per task</td></tr></tbody></table><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>ECS or Fargate is not the right question to ask. The question is whether to use container instances or Fargate. What ECS calls a container instance is known as a worker node in Kubernetes&#x2F;EKS.</p><p>We learned it the hard way. Scaling container instances is a challenge. That’s why we recommend using Fargate. Fargate is much easier to operate. Use it if possible (see networking, vCPU, memory, host OS, and EBS limitations).</p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. https://aws.amazon.com/blogs/compute/amazon-ecs-and-docker-volume-drivers-amazon-ebs/ <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#network_mode <a href="#fnref:2" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>Serverless WebSocket API: API Gateway, Kinesis, Lambda</title>
      <link>https://cloudonaut.io/serverless-websocket-api-api-gateway-kinesis-lambda/</link>
      <description>
        <![CDATA[<p>Nowadays, it is a common approach to use a RESTful API following the synchronous request&#x2F;response model. But what about asynchronous]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/apigateway/">apigateway</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/serverless-websocket-api-api-gateway-kinesis-lambda/</guid>
      <pubDate>Wed, 13 Feb 2019 10:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Nowadays, it is a common approach to use a RESTful API following the synchronous request&#x2F;response model. But what about asynchronous communication? Or communication that is synchronous but could be modeled asynchronous as well? A WebSocket API based on API Gateway, Kinesis, and Lambda is the perfect tool for that job.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/02/fast@730w.webp 730w, /images/2019/02/fast@730w2x.webp 1460w, /images/2019/02/fast@610w.webp 610w, /images/2019/02/fast@610w2x.webp 1220w, /images/2019/02/fast@450w.webp 450w, /images/2019/02/fast@450w2x.webp 900w, /images/2019/02/fast@330w.webp 330w, /images/2019/02/fast@330w2x.webp 660w, /images/2019/02/fast@545w.webp 545w, /images/2019/02/fast@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/02/fast@730w.jpeg 730w, /images/2019/02/fast@730w2x.jpeg 1460w, /images/2019/02/fast@610w.jpeg 610w, /images/2019/02/fast@610w2x.jpeg 1220w, /images/2019/02/fast@450w.jpeg 450w, /images/2019/02/fast@450w2x.jpeg 900w, /images/2019/02/fast@330w.jpeg 330w, /images/2019/02/fast@330w2x.jpeg 660w, /images/2019/02/fast@545w.jpeg 545w, /images/2019/02/fast@545w2x.jpeg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/02/fast.jpeg" alt="Real-time and event-driven API" title="Real-time and event-driven API"></picture></p><p>AWS announced WebSocket support for the API Gateway in December 2018. Recently CloudFormation added support for the new resources as well. Therefore, it is about time to discover how to build an event-driven API based on the following building blocks:</p><ul><li>An <strong>API Gateway</strong> providing the WebSocket API</li><li>A <strong>Kinesis Stream</strong> to buffer and distribute events</li><li>Several <strong>Lambda Functions</strong> implementing event-driven microservices</li></ul><p>The following figure illustrated the architecture.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/02/serverless-websocket-api@730w.webp 730w, /images/2019/02/serverless-websocket-api@730w2x.webp 1460w, /images/2019/02/serverless-websocket-api@610w.webp 610w, /images/2019/02/serverless-websocket-api@610w2x.webp 1220w, /images/2019/02/serverless-websocket-api@450w.webp 450w, /images/2019/02/serverless-websocket-api@450w2x.webp 900w, /images/2019/02/serverless-websocket-api@330w.webp 330w, /images/2019/02/serverless-websocket-api@330w2x.webp 660w, /images/2019/02/serverless-websocket-api@545w.webp 545w, /images/2019/02/serverless-websocket-api@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/02/serverless-websocket-api@730w.png 730w, /images/2019/02/serverless-websocket-api@730w2x.png 1460w, /images/2019/02/serverless-websocket-api@610w.png 610w, /images/2019/02/serverless-websocket-api@610w2x.png 1220w, /images/2019/02/serverless-websocket-api@450w.png 450w, /images/2019/02/serverless-websocket-api@450w2x.png 900w, /images/2019/02/serverless-websocket-api@330w.png 330w, /images/2019/02/serverless-websocket-api@330w2x.png 660w, /images/2019/02/serverless-websocket-api@545w.png 545w, /images/2019/02/serverless-websocket-api@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/02/serverless-websocket-api.png" alt="Serverless WebSocket API: API Gateway, Kinesis, Lambda" title="Serverless WebSocket API: API Gateway, Kinesis, Lambda"></picture></p><p>What are the unique features of this architecture?</p><ul><li><strong>Low latency</strong>: The API Gateway forwards incoming events to the Kinesis Stream. No Lambda Function needed, which decreases costs and latency.</li><li><strong>High Decoupling</strong>: Each microservice subscribes to the Kinesis Stream to process events from the client. A microservice is able to send events to a client by posting a message to the WebSocket via the API Gateway.</li><li><strong>Fault Tolerant</strong>: The Kinesis Stream acts as a buffer for incoming events. In case of a failure within one of the micro-services events are not lost.</li><li><strong>Asynchronous But Real-Time</strong>: Events are processed asynchronously but with minimal latency. The bidirectional model offered by the WebSocket allows sending response events to the client easily.</li><li><strong>Ordered</strong>: The order of events is guaranteed from the client to the microservice.</li></ul><p>In the following, you will learn how to set up the described architecture with the help of CloudFormation.</p><p>First of all, create the API as well as the default route.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Api:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::ApiGatewayV2::Api&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Name:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line">    <span class="attr">ProtocolType:</span> <span class="string">WEBSOCKET</span></span><br><span class="line">    <span class="attr">RouteSelectionExpression:</span> <span class="string">&#x27;\$default&#x27;</span></span><br><span class="line"><span class="attr">DefaultRoute:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::ApiGatewayV2::Route&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">ApiId:</span> <span class="type">!Ref</span> <span class="string">Api</span></span><br><span class="line">    <span class="attr">AuthorizationType:</span> <span class="string">NONE</span></span><br><span class="line">    <span class="attr">RouteKey:</span> <span class="string">&#x27;$default&#x27;</span></span><br><span class="line">    <span class="attr">Target:</span> <span class="type">!Sub</span> <span class="string">&#x27;integrations/$&#123;KinesisIntegration&#125;&#x27;</span></span><br></pre></td></tr></table></figure><p>In theory, the <code>RouteSelectionExpression</code> in combination with an <code>AWS::ApiGatewayV2::Route</code> allows you to route incoming events to different targets. For example, you could split events into different Kinesis Streams. Doing so is not necessary, and therefore all events are routed to the <code>DefaultRoute</code> route in this example.</p><p>The default route sends all events to the <code>KinesisIntegration</code>. The integration calls the <code>PutRecord</code> action on the Kinesis Stream for each incoming event. The <code>default</code> template defined in <code>RequestTemplates</code> assembles the needed request. Note, the <code>connectionId</code> - which can be used to send an event back to the client - is added to each event.</p><figure class="highlight leaf"><table><tr><td class="code"><pre><span class="line">KinesisIntegration:</span><br><span class="line">  Type: &#x27;AWS::ApiGatewayV2::Integration&#x27;</span><br><span class="line">  Properties:</span><br><span class="line">    ApiId: !Ref Api</span><br><span class="line">    CredentialsArn: !GetAtt IntegrationRole.Arn</span><br><span class="line">    IntegrationMethod: &#x27;POST&#x27;</span><br><span class="line">    IntegrationType: &#x27;AWS&#x27;</span><br><span class="line">    IntegrationUri: !Sub &#x27;arn:aws:apigateway:$&#123;AWS::Region&#125;:kinesis:action/PutRecord&#x27;</span><br><span class="line">    RequestTemplates:</span><br><span class="line">      default: !Sub |</span><br><span class="line">        <span class="punctuation">#</span><span class="keyword">set</span><span class="params">($<span class="variable">payload</span> <span class="operator">=</span> $<span class="keyword">in</span><span class="variable">put</span>.<span class="keyword">json</span><span class="params">(&#x27;$&#x27;)</span>)</span></span><br><span class="line">        <span class="punctuation">#</span><span class="keyword">set</span><span class="params">($<span class="variable">data</span> <span class="operator">=</span> <span class="string">&quot;&#123;&quot;</span><span class="string">&quot;payload&quot;</span><span class="string">&quot;: $payload, &quot;</span><span class="string">&quot;connectionId&quot;</span><span class="string">&quot;: &quot;</span><span class="string">&quot;$context.connectionId&quot;</span><span class="string">&quot;&#125;&quot;</span>)</span></span><br><span class="line">        &#123;</span><br><span class="line">            &quot;Data&quot;: &quot;$util.base64Encode($data)&quot;,</span><br><span class="line">            &quot;PartitionKey&quot;: &quot;$context.connectionId&quot;,</span><br><span class="line">            &quot;StreamName&quot;: &quot;$&#123;EventStream&#125;&quot;</span><br><span class="line">        &#125;</span><br><span class="line">    TemplateSelectionExpression: default</span><br></pre></td></tr></table></figure><p>What else is needed to set up the API Gateway? You need to define a stage and a deployment.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Stage:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::ApiGatewayV2::Stage&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">ApiId:</span> <span class="type">!Ref</span> <span class="string">Api</span></span><br><span class="line">    <span class="attr">DeploymentId:</span> <span class="type">!Ref</span> <span class="string">Deployment</span></span><br><span class="line">    <span class="attr">StageName:</span> <span class="string">&#x27;v1&#x27;</span></span><br><span class="line">    <span class="attr">DefaultRouteSettings:</span></span><br><span class="line">      <span class="attr">LoggingLevel:</span> <span class="string">INFO</span></span><br><span class="line">      <span class="attr">DataTraceEnabled:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">Deployment:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::ApiGatewayV2::Deployment&#x27;</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">DefaultRoute</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">ApiId:</span> <span class="type">!Ref</span> <span class="string">Api</span></span><br></pre></td></tr></table></figure><p>Next, create a Kinesis Stream to buffer events.</p><figure class="highlight nestedtext"><table><tr><td class="code"><pre><span class="line"><span class="attribute">EventStream</span><span class="punctuation">:</span></span><br><span class="line">    <span class="attribute">Type</span><span class="punctuation">:</span> <span class="string">&#x27;AWS::Kinesis::Stream&#x27;</span></span><br><span class="line">    <span class="attribute">Properties</span><span class="punctuation">: </span></span><br><span class="line">      <span class="attribute">ShardCount</span><span class="punctuation">:</span> <span class="string">1</span></span><br></pre></td></tr></table></figure><p>The WebSocket API, as well as the Kinesis Stream, are ready to receive events.</p><p>In order to process the events, a microservice needs to be attached to the Kinesis Stream. The following snippet shows how to create the necessary resources:</p><ul><li><code>EventStreamConsumer</code> registers an HTTP&#x2F;2 based consumer at the Kinesis Stream.</li><li><code>EventSourceMapping</code> the source mapping connecting the consumer with the Lambda Function.</li><li><code>StreamFunction</code> the Lambda Function implementing a microservice processing events from the stream.</li></ul><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">EventStreamConsumer:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::Kinesis::StreamConsumer&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">ConsumerName:</span> <span class="string">lambda</span></span><br><span class="line">    <span class="attr">StreamARN:</span> <span class="type">!GetAtt</span> <span class="string">EventStream.Arn</span></span><br><span class="line"><span class="attr">EventSourceMapping:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::Lambda::EventSourceMapping&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">BatchSize:</span> <span class="number">16</span></span><br><span class="line">    <span class="attr">Enabled:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">EventSourceArn:</span> <span class="type">!Ref</span> <span class="string">EventStreamConsumer</span></span><br><span class="line">    <span class="attr">FunctionName:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;StreamFunction.Arn&#x27;</span></span><br><span class="line">    <span class="attr">StartingPosition:</span> <span class="string">LATEST</span></span><br><span class="line"><span class="attr">StreamFunction:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::Lambda::Function&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Handler:</span> <span class="string">&#x27;index.handler&#x27;</span></span><br><span class="line">    <span class="attr">Runtime:</span> <span class="string">&#x27;nodejs8.10&#x27;</span></span><br><span class="line">    <span class="attr">Layers:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:lambda:$&#123;AWS::Region&#125;:853553028582:layer:monitoring-jump-start:1&#x27;</span></span><br><span class="line">    <span class="attr">MemorySize:</span> <span class="number">128</span></span><br><span class="line">    <span class="attr">Timeout:</span> <span class="number">30</span></span><br><span class="line">    <span class="attr">Role:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;StreamRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">Code:</span> <span class="string">&#x27;// ...&#x27;</span></span><br></pre></td></tr></table></figure><p>The following implementation of the <code>StreamFunction</code> sends back each incoming event to its sender.</p><figure class="highlight csharp"><table><tr><td class="code"><pre><span class="line">StreamFunction:</span><br><span class="line">  Type: <span class="string">&#x27;AWS::Lambda::Function&#x27;</span></span><br><span class="line">  Properties:</span><br><span class="line">    <span class="meta"># ...</span></span><br><span class="line">    Code:</span><br><span class="line">      ZipFile: !Sub |</span><br><span class="line">        <span class="string">&#x27;use strict&#x27;</span>;</span><br><span class="line">        <span class="keyword">const</span> AWS = require(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line">        <span class="keyword">const</span> api = <span class="keyword">new</span> AWS.ApiGatewayManagementApi(&#123;</span><br><span class="line">          apiVersion: <span class="string">&#x27;2018-11-29&#x27;</span>,</span><br><span class="line">          endpoint: <span class="string">&#x27;$&#123;Api&#125;.execute-api.$&#123;AWS::Region&#125;.amazonaws.com/$&#123;Stage&#125;&#x27;</span></span><br><span class="line">        &#125;);</span><br><span class="line">        exports.handler = <span class="keyword">async</span> (<span class="keyword">event</span>) =&gt; &#123;</span><br><span class="line">          console.log(JSON.stringify(<span class="keyword">event</span>));</span><br><span class="line">          <span class="keyword">for</span> (<span class="keyword">let</span> r <span class="keyword">in</span> <span class="keyword">event</span>.Records) &#123;</span><br><span class="line">              <span class="keyword">const</span> data = JSON.parse(<span class="keyword">new</span> Buffer(<span class="keyword">event</span>.Records[r].kinesis.data, <span class="string">&#x27;base64&#x27;</span>).toString());</span><br><span class="line">              <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">await</span> api.postToConnection(&#123;</span><br><span class="line">                  ConnectionId: data.connectionId,</span><br><span class="line">                  Data: JSON.stringify(data.payload)</span><br><span class="line">                &#125;).promise();</span><br><span class="line">              &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">                <span class="keyword">if</span> (e.statusCode === <span class="number">410</span>) &#123;</span><br><span class="line">                  <span class="comment">// do nothing, client disconnected</span></span><br><span class="line">                  console.log(<span class="string">&#x27;client disconnected&#x27;</span>);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                  <span class="keyword">throw</span> e;</span><br><span class="line">                &#125;</span><br><span class="line">              &#125;    </span><br><span class="line">          &#125;</span><br><span class="line">          <span class="keyword">return</span> <span class="string">&quot;OK&quot;</span>;</span><br><span class="line">        &#125;;</span><br></pre></td></tr></table></figure><p>I’ve skipped a few resources, mainly IAM roles. You can find the whole CloudFormation template at <a href="https://gist.github.com/andreaswittig/bea2818d83cc2e681c6edaf9bf178e4d" target="_blank" rel="noopener">api-gateway-websocket.yaml</a>. Use the following command to create a CloudFormation stack based on the template:</p><figure class="highlight jboss-cli"><table><tr><td class="code"><pre><span class="line">aws cloudformation <span class="keyword">deploy</span> <span class="params">--template-file</span> apigateway-websocket.yaml <span class="params">--stack-name</span> apigateway-websocket <span class="params">--capabilities</span> CAPABILITY_IAM</span><br></pre></td></tr></table></figure><p>Get the WebSocket URI from the CloudFormation stack.</p><figure class="highlight jboss-cli"><table><tr><td class="code"><pre><span class="line">aws cloudformation describe-stacks <span class="params">--stack-name</span> apigateway-websocket <span class="params">--query</span> &#x27;Stacks[0]<span class="string">.Outputs</span>[?OutputKey==`WebSocketURI`]<span class="string">.OutputValue</span>&#x27; <span class="params">--output</span> text</span><br></pre></td></tr></table></figure><p>Use <a href="https://github.com/websockets/wscat" target="_blank" rel="noopener">wscat</a> to send events to your WebSocket API. The API will return the event immediately.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">wscat -c wss://f124m4srw3.execute-api.eu-west-1.amazonaws.com/v1</span><br><span class="line">&gt; &#123;<span class="string">&quot;msg&quot;</span>:<span class="string">&quot;Hello World!&quot;</span>&#125;</span><br><span class="line">&lt; &#123;<span class="string">&quot;msg&quot;</span>:<span class="string">&quot;Hello World!&quot;</span>&#125;</span><br></pre></td></tr></table></figure><p>That’s it. Building a WebSocket API with an API Gateway, a Kinesis Stream and Lambda Functions is a great approach when developing a real-time and event-driven API.</p><hr><p>Do you want to learn more about cutting-edge architectures on AWS? Join Michael and me for our session <em>Cutting-Edge Architectures Based on AppSync, Lambda, and Fargate</em> on February 26th at the <a href="https://aws.amazon.com/events/summits/berlin/" target="_blank" rel="noopener">AWS Summit in Berlin</a>. Also, if you stumble upon Michael or me during the summit, please say “Hi!”.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Cost savings with DynamoDB On-Demand: Lessons learned</title>
      <link>https://cloudonaut.io/cost-savings-with-dynamodb-on-demand-essons-learned/</link>
      <description>
        <![CDATA[<p>One of my favorite features announced during re:Invent 2018 is <a href="https://aws.amazon.com/blogs/aws/amazon-dynamodb-on-demand-no-cap]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/dynamodb/">dynamodb</category>
      <guid isPermaLink="true">https://cloudonaut.io/cost-savings-with-dynamodb-on-demand-essons-learned/</guid>
      <pubDate>Thu, 07 Feb 2019 15:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>One of my favorite features announced during re:Invent 2018 is <a href="https://aws.amazon.com/blogs/aws/amazon-dynamodb-on-demand-no-capacity-planning-and-pay-per-request-pricing/" target="_blank" rel="noopener">DynamoDB On-Demand</a>. With DynamoDB On-Demand, we can use DynamoDB without provisioning capacity. Instead, we pay per request. Sounds amazing? I was excited and re-configured all DynamoDB tables of our SaaS product marbot: <a href="https://marbot.io/" target="_blank" rel="noopener">cloud-native alerting for CloudWatch via Slack</a>. The result is stunning but misleading.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/02/cost-savings@730w.webp 730w, /images/2019/02/cost-savings@730w2x.webp 1460w, /images/2019/02/cost-savings@610w.webp 610w, /images/2019/02/cost-savings@610w2x.webp 1220w, /images/2019/02/cost-savings@450w.webp 450w, /images/2019/02/cost-savings@450w2x.webp 900w, /images/2019/02/cost-savings@330w.webp 330w, /images/2019/02/cost-savings@330w2x.webp 660w, /images/2019/02/cost-savings@545w.webp 545w, /images/2019/02/cost-savings@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/02/cost-savings@730w.jpg 730w, /images/2019/02/cost-savings@730w2x.jpg 1460w, /images/2019/02/cost-savings@610w.jpg 610w, /images/2019/02/cost-savings@610w2x.jpg 1220w, /images/2019/02/cost-savings@450w.jpg 450w, /images/2019/02/cost-savings@450w2x.jpg 900w, /images/2019/02/cost-savings@330w.jpg 330w, /images/2019/02/cost-savings@330w2x.jpg 660w, /images/2019/02/cost-savings@545w.jpg 545w, /images/2019/02/cost-savings@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/02/cost-savings.jpg" alt="Cost Savings" title="Cost Savings"></picture></p><p>Today, I add what we learned in the following weeks.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/02/dynamodb-on-demand-cost-savings@730w.webp 730w, /images/2019/02/dynamodb-on-demand-cost-savings@730w2x.webp 1460w, /images/2019/02/dynamodb-on-demand-cost-savings@610w.webp 610w, /images/2019/02/dynamodb-on-demand-cost-savings@610w2x.webp 1220w, /images/2019/02/dynamodb-on-demand-cost-savings@450w.webp 450w, /images/2019/02/dynamodb-on-demand-cost-savings@450w2x.webp 900w, /images/2019/02/dynamodb-on-demand-cost-savings@330w.webp 330w, /images/2019/02/dynamodb-on-demand-cost-savings@330w2x.webp 660w, /images/2019/02/dynamodb-on-demand-cost-savings@545w.webp 545w, /images/2019/02/dynamodb-on-demand-cost-savings@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/02/dynamodb-on-demand-cost-savings@730w.png 730w, /images/2019/02/dynamodb-on-demand-cost-savings@730w2x.png 1460w, /images/2019/02/dynamodb-on-demand-cost-savings@610w.png 610w, /images/2019/02/dynamodb-on-demand-cost-savings@610w2x.png 1220w, /images/2019/02/dynamodb-on-demand-cost-savings@450w.png 450w, /images/2019/02/dynamodb-on-demand-cost-savings@450w2x.png 900w, /images/2019/02/dynamodb-on-demand-cost-savings@330w.png 330w, /images/2019/02/dynamodb-on-demand-cost-savings@330w2x.png 660w, /images/2019/02/dynamodb-on-demand-cost-savings@545w.png 545w, /images/2019/02/dynamodb-on-demand-cost-savings@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/02/dynamodb-on-demand-cost-savings.png" alt="Cost savings with DynamoDB On-Demand" title="Cost savings with DynamoDB On-Demand"></picture></p><h2 id="Behind-the-marketing-term"><a href="#Behind-the-marketing-term" class="headerlink" title="Behind the marketing term"></a>Behind the marketing term</h2><p>What does DynamoDB On-Demand mean? When compared with the provisioned DynamoDB model, on-demand is:</p><ul><li>A table that scales automatically.</li><li>A new cost model where you pay per request.</li></ul><h2 id="Scaling-takes-time-if-you-hit-a-new-peak"><a href="#Scaling-takes-time-if-you-hit-a-new-peak" class="headerlink" title="Scaling takes time if you hit a new peak"></a>Scaling takes time if you hit a new peak</h2><p>DynamoDB On-Demand provisions capacity to handle two times the past peak traffic. Let’s say your previous peak in January 2019 was 10,000 requests&#x2F;sec. After that new peak, you can go from zero to 20,000 requests&#x2F;sec at any time without being throttled. However, if all of a sudden you send 30,000 requests&#x2F;second it takes around 30 minutes for DynamoDB On-Demand to scale up. You see throttles in the meantime! <strong>The best way to avoid throttling is to prescale an on-demand table by simulating a new traffic peak before you go live.</strong></p><h2 id="Economics"><a href="#Economics" class="headerlink" title="Economics"></a>Economics</h2><p>There are cases where on-demand is significantly more expensive compared to provisioned with Auto Scaling. My rule of thumb: The spikier your workload, the higher the savings with on-demand. Workloads with zero requests also benefit. The reason why we saw such significant savings with marbot is our workload that goes down to almost zero requests&#x2F;second for half of the day in production and is mostly zero for 24&#x2F;7 in our test environment.  My suggestion is to switch to on-demand for one day and compare your costs with the day before.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS SLA: Are you able to keep your availability promise?</title>
      <link>https://cloudonaut.io/aws-sla-are-you-able-to-keep-your-availability-promise/</link>
      <description>
        <![CDATA[<p>Are you offering availability of 99.99% or more to your clients? Bad news, you might not be able to keep your promise!</p>
<p>Recently AW]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/rds/">rds</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/route53/">route53</category>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-sla-are-you-able-to-keep-your-availability-promise/</guid>
      <pubDate>Thu, 31 Jan 2019 20:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Are you offering availability of 99.99% or more to your clients? Bad news, you might not be able to keep your promise!</p><p>Recently AWS announced a bunch of new Service Level Agreements (SLA). Therefore, it is now possible to calculate the expected availability of most of the architectures on AWS.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/01/calculate@730w.webp 730w, /images/2019/01/calculate@730w2x.webp 1460w, /images/2019/01/calculate@610w.webp 610w, /images/2019/01/calculate@610w2x.webp 1220w, /images/2019/01/calculate@450w.webp 450w, /images/2019/01/calculate@450w2x.webp 900w, /images/2019/01/calculate@330w.webp 330w, /images/2019/01/calculate@330w2x.webp 660w, /images/2019/01/calculate@545w.webp 545w, /images/2019/01/calculate@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/01/calculate@730w.jpg 730w, /images/2019/01/calculate@730w2x.jpg 1460w, /images/2019/01/calculate@610w.jpg 610w, /images/2019/01/calculate@610w2x.jpg 1220w, /images/2019/01/calculate@450w.jpg 450w, /images/2019/01/calculate@450w2x.jpg 900w, /images/2019/01/calculate@330w.jpg 330w, /images/2019/01/calculate@330w2x.jpg 660w, /images/2019/01/calculate@545w.jpg 545w, /images/2019/01/calculate@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/01/calculate.jpg" alt="Calculate" title="Calculate"></picture></p><p>Typically, each SLA contains:</p><ol><li><strong>Service Commitment</strong> defines the availability objective (e.g., monthly uptime of at least 99.99%)</li><li><strong>Definitions</strong> specifies the used terms. Most importantly defines how to measure the availability of a service.</li><li><strong>Service Credits</strong> states how AWS compensates customers affected by missed availability objectives (e.g., 30% service credit).</li><li><strong>Exclusions</strong> defines which circumstances are not covered by the service commitment.</li></ol><p>Also, it is essential to distinguish between two different availability definitions:</p><ul><li><strong>per period</strong> used by EC2, ELB, and RDS.</li><li><strong>per request</strong> used by Route 53, S3, Lambda, and DynamoDB.</li></ul><p>Generally, each SLA covers a service deployed within multiple Availability Zones within a region.</p><p>The following table lists the SLA published by AWS (see <code>*</code> for details).</p><table class="table">  <thead>    <tr>      <th>Service</th>      <th class="text-right">SLA</th>      <th>Type</th>    </tr>  </thead>  <tbody>    <tr>      <td>Route 53</td>      <td class="text-right">100.0% <a href="https://aws.amazon.com/route53/sla/">*</a></td>      <td>request</td>    </tr>    <tr>      <td>ELB/ALB/NLB</td>      <td class="text-right">99.99% <a href="https://aws.amazon.com/elasticloadbalancing/sla/">*</a></td>      <td>period of time</td>    </tr>    <tr>      <td>EC2</td>      <td class="text-right">99.99% <a href="https://aws.amazon.com/compute/sla/">*</a></td>      <td>period of time</td>    </tr>    <tr>      <td>EBS</td>      <td class="text-right">99.99% <a href="https://aws.amazon.com/compute/sla/">*</a></td>      <td>period of time</td>    </tr>    <tr>      <td>EFS</td>      <td class="text-right">99.9% <a href="https://aws.amazon.com/efs/sla/">*</a></td>      <td>period of time</td>    </tr>    <tr>      <td>ECS</td>      <td class="text-right">99.99% <a href="https://aws.amazon.com/compute/sla/">*</a></td>      <td>period of time</td>    </tr>    <tr>      <td>Fargate</td>      <td class="text-right">99.99% <a href="https://aws.amazon.com/compute/sla/">*</a></td>      <td>period of time</td>    </tr>    <tr>      <td>RDS</td>      <td class="text-right">99.95% <a href="https://aws.amazon.com/rds/sla/">*</a></td>      <td>period of time</td>    </tr>    <tr>      <td>API Gateway</td>      <td class="text-right">99.95% <a href="https://aws.amazon.com/api-gateway/sla/">*</a></td>      <td>request</td>    </tr>    <tr>      <td>Lambda</td>      <td class="text-right">99.95% <a href="https://aws.amazon.com/lambda/sla/">*</a></td>      <td>request</td>    </tr>    <tr>      <td>DynamoDB</td>      <td class="text-right">99.99% <a href="https://aws.amazon.com/dynamodb/sla/">*</a></td>      <td>request</td>    </tr>    <tr>      <td>S3</td>      <td class="text-right">99.9% <a href="https://aws.amazon.com/s3/sla/">*</a></td>      <td>request</td>    </tr>    <tr>      <td>CloudFront</td>      <td class="text-right">99.9% <a href="https://aws.amazon.com/cloudfront/sla/">*</a></td>      <td>request</td>    </tr>    <tr>      <td>Step Functions</td>      <td class="text-right">99.9% <a href="https://aws.amazon.com/step-functions/sla/">*</a></td>      <td>request</td>    </tr>    <tr>      <td>Cognito</td>      <td class="text-right">99.9% <a href="https://aws.amazon.com/cognito/sla/">*</a></td>      <td>request</td>    </tr>    <tr>      <td>Amazon MQ</td>      <td class="text-right">99.9% <a href="https://aws.amazon.com/amazon-mq/sla/">*</a></td>      <td>period of time</td>    </tr>    <tr>      <td>Secrets Manager</td>      <td class="text-right">99.9% <a href="https://aws.amazon.com/secrets-manager/sla/">*</a></td>      <td>request</td>    </tr>    <tr>      <td>ECR</td>      <td class="text-right">99.9% <a href="https://aws.amazon.com/ecr/sla/">*</a></td>      <td>request</td>    </tr>    <tr>      <td>EKS</td>      <td class="text-right">99.9% <a href="https://aws.amazon.com/eks/sla/">*</a></td>      <td>period of time</td>    </tr>    <tr>      <td>Kinesis Video Streams</td>      <td class="text-right">99.9% <a href="https://aws.amazon.com/kinesis/video-streams/sla/">*</a></td>      <td>request</td>    </tr>    <tr>      <td>Kinesis Data Firehose</td>      <td class="text-right">99.9% <a href="https://aws.amazon.com/kinesis/data-firehose/sla/">*</a></td>      <td>request</td>    </tr>    <tr>      <td>Kinesis Data Streams</td>      <td class="text-right">99.9% <a href="https://aws.amazon.com/kinesis/data-streams/sla/">*</a></td>      <td>request</td>    </tr>    <tr>      <td>EMR</td>      <td class="text-right">99.9% <a href="https://aws.amazon.com/emr/sla/">*</a></td>      <td>request</td>    </tr>  </tbody></table><p>So, how to calculate the expected availability of your AWS architecture? To do so, we make two assumptions:</p><ol><li>Whenever one of the services fails, it affects the client.</li><li>There is no dependency between the services. They all fail independently from each other.</li></ol><p>In that case, we need to multiply the availability objective of each service. The following figure shows an example of a typical web application running on EC2.</p><p align="center"><img src="/images/2019/01/aws-sla-ec2.png" alt="Combined SLA for EC2 Architecture" style="max-width: 400px"></p><p>You can use the same approach to calculate the availability for your serverless application as illustrated in the following figure.</p><p align="center"><img src="/images/2019/01/aws-sla-serverless.png" alt="Combined SLA for Serverless Architecture" style="max-width: 400px"></p><p>The shown examples result in an expected availability of 99.80% to 99.92% depending on the involved services. </p><p>Next, calculate the expected availability for your architecture. Please note, that our expected availability calculated for your architecture is pessimistic because our assumptions cover the worst case.</p><p>However, keep in mind that the expected availability does only cover failures within your cloud infrastructure. It does not include an error budget for your software or failed deployments, for example.</p><p>Are you able to keep your promise?</p><p>Are you looking for a way to increase the expected availability of your architecture? Deploy your workload to multiple regions. But be warned, doing so comes with additional complexity caused by the need to synchronize your data between multiple regions.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Fargate is ready for prime time, and we share our CloudFormation templates</title>
      <link>https://cloudonaut.io/fargate-is-ready-for-prime-time-and-we-share-our-cloudformation-templates/</link>
      <description>
        <![CDATA[<p>The recent <a href="https://aws.amazon.com/blogs/compute/aws-fargate-price-reduction-up-to-50/" target="_blank" rel="noopener">AWS Fargat]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <category domain="https://cloudonaut.io/tag/fargate/">fargate</category>
      <guid isPermaLink="true">https://cloudonaut.io/fargate-is-ready-for-prime-time-and-we-share-our-cloudformation-templates/</guid>
      <pubDate>Tue, 15 Jan 2019 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The recent <a href="https://aws.amazon.com/blogs/compute/aws-fargate-price-reduction-up-to-50/" target="_blank" rel="noopener">AWS Fargate Price Reduction</a> (up to 50%) is the last piece in the puzzle to call Fargate a reasonable choice for running Docker workloads on AWS.</p><p>The CIO perspective is as simple as this: you provide the Docker image and scaling rules, Fargate deploys and runs your Docker containers for you.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/01/container@730w.webp 730w, /images/2019/01/container@730w2x.webp 1460w, /images/2019/01/container@610w.webp 610w, /images/2019/01/container@610w2x.webp 1220w, /images/2019/01/container@450w.webp 450w, /images/2019/01/container@450w2x.webp 900w, /images/2019/01/container@330w.webp 330w, /images/2019/01/container@330w2x.webp 660w, /images/2019/01/container@545w.webp 545w, /images/2019/01/container@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/01/container@730w.jpg 730w, /images/2019/01/container@730w2x.jpg 1460w, /images/2019/01/container@610w.jpg 610w, /images/2019/01/container@610w2x.jpg 1220w, /images/2019/01/container@450w.jpg 450w, /images/2019/01/container@450w2x.jpg 900w, /images/2019/01/container@330w.jpg 330w, /images/2019/01/container@330w2x.jpg 660w, /images/2019/01/container@545w.jpg 545w, /images/2019/01/container@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/01/container.jpg" alt="Container" title="Container"></picture></p><p>However, the reality is more complicated. AWS is a provider of building blocks. It’s up to you to compose them to systems that survive reality. It turns out that composing the building blocks is not as easy. Many pitfalls are waiting for you such as invalid parameter combinations and you need deep understanding of the components. We worked on a couple of consulting projects where we used Fargate to run the workloads of our clients. We codified our experience into CloudFormation templates that we want to share with you today. </p><p>We maintain two open source projects where we share our CloudFormation templates. <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a> are composable and very opinionated but easy to use and CloudFormation beginner friendly. <a href="https://github.com/cfn-modules" target="_blank" rel="noopener">cfn-modules</a> are highly modularized and flexible but require more CloudFormation experience.</p><p>In this post, I demonstrate to deploy the same application using both CloudFormation open source projects. The application consists of two Docker images:</p><ul><li>Varnish cache: Docker image <code>eeacms/varnish:4.1-6.2</code></li><li>Nginx with a simple “Hello World” app: Docker image <code>widdix/hello:v1</code></li></ul><p>We make use of the <a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador" target="_blank" rel="noopener">ambassador pattern</a> where traffic arrives on one container (ambassador container) that forwards to the other container (app container). The following diagram illustrates the high-level architecture.</p><p><img class="img-fluid" src="/images/2019/01/ambassador-pattern.svg" alt="Ambassador pattern with ALB" title="Ambassador pattern with ALB"></p><p>Let’s see how you can deploy this.</p><h2 id="Free-Templates-for-AWS-CloudFormation"><a href="#Free-Templates-for-AWS-CloudFormation" class="headerlink" title="Free Templates for AWS CloudFormation"></a>Free Templates for AWS CloudFormation</h2><p><a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a> are composable. Common aspects are split into templates to allow sharing of resources such as a VPC.</p><h3 id="VPC"><a href="#VPC" class="headerlink" title="VPC"></a>VPC</h3><p>Let’s start by creating the VPC (do not modify the parameters): <a href="https://console.aws.amazon.com/cloudformation/home#/stacks/create/review?templateURL=https://s3-eu-west-1.amazonaws.com/widdix-aws-cf-templates-releases-eu-west-1/v8.3.1/vpc/vpc-3azs.yaml&stackName=vpc"><img src="/images/2016/01/cloudformation-launch-stack.png" style="max-width: 144px" alt="Launch Stack"></a></p><p>Wait until the status changes to <code>CREATE_COMPLETE</code>.</p><h3 id="ESC-Cluster-for-Fargate"><a href="#ESC-Cluster-for-Fargate" class="headerlink" title="ESC Cluster for Fargate"></a>ESC Cluster for Fargate</h3><p>Next, you create an ESC Cluster for Fargate.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/01/create-ecs-cluster-for-fargate-stack@730w.webp 730w, /images/2019/01/create-ecs-cluster-for-fargate-stack@730w2x.webp 1460w, /images/2019/01/create-ecs-cluster-for-fargate-stack@610w.webp 610w, /images/2019/01/create-ecs-cluster-for-fargate-stack@610w2x.webp 1220w, /images/2019/01/create-ecs-cluster-for-fargate-stack@450w.webp 450w, /images/2019/01/create-ecs-cluster-for-fargate-stack@450w2x.webp 900w, /images/2019/01/create-ecs-cluster-for-fargate-stack@330w.webp 330w, /images/2019/01/create-ecs-cluster-for-fargate-stack@330w2x.webp 660w, /images/2019/01/create-ecs-cluster-for-fargate-stack@545w.webp 545w, /images/2019/01/create-ecs-cluster-for-fargate-stack@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/01/create-ecs-cluster-for-fargate-stack@730w.png 730w, /images/2019/01/create-ecs-cluster-for-fargate-stack@730w2x.png 1460w, /images/2019/01/create-ecs-cluster-for-fargate-stack@610w.png 610w, /images/2019/01/create-ecs-cluster-for-fargate-stack@610w2x.png 1220w, /images/2019/01/create-ecs-cluster-for-fargate-stack@450w.png 450w, /images/2019/01/create-ecs-cluster-for-fargate-stack@450w2x.png 900w, /images/2019/01/create-ecs-cluster-for-fargate-stack@330w.png 330w, /images/2019/01/create-ecs-cluster-for-fargate-stack@330w2x.png 660w, /images/2019/01/create-ecs-cluster-for-fargate-stack@545w.png 545w, /images/2019/01/create-ecs-cluster-for-fargate-stack@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/01/create-ecs-cluster-for-fargate-stack.png" alt="Create ECS cluster for Fargate stack" title="Create ECS cluster for Fargate stack"></picture></p><p>The figure shows how the previously created <code>vpc</code> stack is passed in as the <code>ParentVPCStack</code> parameter (do not modify the parameters, acknowledge that AWS CloudFormation might create IAM resources): <a href="https://console.aws.amazon.com/cloudformation/home#/stacks/create/review?templateURL=https://s3-eu-west-1.amazonaws.com/widdix-aws-cf-templates-releases-eu-west-1/v8.3.1/fargate/cluster.yaml&stackName=cluster&param_ParentVPCStack=vpc"><img src="/images/2016/01/cloudformation-launch-stack.png" style="max-width: 144px" alt="Launch Stack"></a></p><p>Wait until the status changes to <code>CREATE_COMPLETE</code>. </p><p>Expand the <strong>Outputs</strong> section and open the <strong>URL</strong> output value in your browser. You receive a 503 error for now.</p><h3 id="Fargate-service"><a href="#Fargate-service" class="headerlink" title="Fargate service"></a>Fargate service</h3><p>Finally, you create the Fargate service that runs the Docker images.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2019/01/create-fargate-service-stack@730w.webp 730w, /images/2019/01/create-fargate-service-stack@730w2x.webp 1460w, /images/2019/01/create-fargate-service-stack@610w.webp 610w, /images/2019/01/create-fargate-service-stack@610w2x.webp 1220w, /images/2019/01/create-fargate-service-stack@450w.webp 450w, /images/2019/01/create-fargate-service-stack@450w2x.webp 900w, /images/2019/01/create-fargate-service-stack@330w.webp 330w, /images/2019/01/create-fargate-service-stack@330w2x.webp 660w, /images/2019/01/create-fargate-service-stack@545w.webp 545w, /images/2019/01/create-fargate-service-stack@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2019/01/create-fargate-service-stack@730w.png 730w, /images/2019/01/create-fargate-service-stack@730w2x.png 1460w, /images/2019/01/create-fargate-service-stack@610w.png 610w, /images/2019/01/create-fargate-service-stack@610w2x.png 1220w, /images/2019/01/create-fargate-service-stack@450w.png 450w, /images/2019/01/create-fargate-service-stack@450w2x.png 900w, /images/2019/01/create-fargate-service-stack@330w.png 330w, /images/2019/01/create-fargate-service-stack@330w2x.png 660w, /images/2019/01/create-fargate-service-stack@545w.png 545w, /images/2019/01/create-fargate-service-stack@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2019/01/create-fargate-service-stack.png" alt="Create Fargate service stack" title="Create Fargate service stack"></picture></p><p>The figure shows how the Docker images are configured using parameters (do not modify the parameters): <a href="https://console.aws.amazon.com/cloudformation/home#/stacks/create/review?templateURL=https://s3-eu-west-1.amazonaws.com/widdix-aws-cf-templates-releases-eu-west-1/v8.3.1/fargate/service-cluster-alb.yaml&stackName=service&param_ParentVPCStack=vpc&param_ParentClusterStack=cluster&param_AmbassadorImage=eeacms/varnish:4.1-6.2&param_AmbassadorPort=6081&param_AmbassadorEnvironment1Key=BACKENDS&param_AmbassadorEnvironment1Value=localhost&param_AmbassadorEnvironment2Key=BACKENDS_PORT&param_AmbassadorEnvironment2Value=80&param_AppImage=widdix/hello:v1&param_AppPort=80"><img src="/images/2016/01/cloudformation-launch-stack.png" style="max-width: 144px" alt="Launch Stack"></a></p><p>Wait until the status changes to <code>CREATE_COMPLETE</code> and reload the URL in your web browser. Rember that you can find the URL in the <strong>URL</strong> output value. Now <em>it works</em>.</p><p>Don’t forget to delete all three stack in reverse order once you are done with the demo.</p><p>Let’s see how you can deploy this with <code>cfn-modules</code>.</p><h2 id="cfn-modules"><a href="#cfn-modules" class="headerlink" title="cfn-modules"></a>cfn-modules</h2><p><code>cfn-modules</code> are installed and updated with the package manager <a href="https://www.npmjs.com/" target="_blank" rel="noopener">npm</a>.</p><blockquote><p><a href="https://nodejs.org/en" target="_blank" rel="noopener">Install Node.js 8.x</a> if <code>npm</code> is not installed on your system </p></blockquote><p>Create a fresh demo directory and change into the newly created directory. </p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> cfn-modules-fargate-alb-ambassador-pattern-example</span><br><span class="line"><span class="built_in">cd</span> cfn-modules-fargate-alb-ambassador-pattern-example/</span><br></pre></td></tr></table></figure><p>Install the modules using npm:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm i @cfn-modules/alerting@1.0.0</span><br><span class="line">npm i @cfn-modules/vpc@1.1.0</span><br><span class="line">npm i @cfn-modules/ecs-cluster@1.0.0</span><br><span class="line">npm i @cfn-modules/alb@1.0.3</span><br><span class="line">npm i @cfn-modules/alb-listener@1.0.0</span><br><span class="line">npm i @cfn-modules/ecs-alb-target@1.0.2</span><br><span class="line">npm i @cfn-modules/fargate-service@1.1.1</span><br></pre></td></tr></table></figure><p>Create a new file to store your CloudFormation template:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">touch</span> example.yml</span><br></pre></td></tr></table></figure><p>You use the modules as nested stacks in your CloudFormation template. Let’s look at the first three modules that provide some basic functionality:</p><ul><li><a href="https://github.com/cfn-modules/alerting" target="_blank" rel="noopener">alerting</a>: Central SNS topic that receives alerts from other modules and forwards them to your team via email, HTTP, or HTTPS.</li><li><a href="https://github.com/cfn-modules/vpc" target="_blank" rel="noopener">vpc</a>: AWS VPC using three availability zones with public and private subnets, VPC endpoints for DynamoDB and S3, Flow Logs, and NAT gateways.</li><li><a href="https://github.com/cfn-modules/ecs-cluster" target="_blank" rel="noopener">ecs-cluster</a>: ECS cluster.</li></ul><p>See how we modules link to each other using parameters (e.g., <code>AlertingModule</code>) and the CloudFormation stack names (e.g., <code>!GetAtt &#39;Alerting.Outputs.StackName&#39;</code>).</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&#x27;cfn-modules: Fargate ALB with ambassador pattern example&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Alerting:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFormation::Stack&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Parameters:</span></span><br><span class="line">        <span class="attr">Email:</span> <span class="string">&#x27;email@domain.com&#x27;</span> <span class="comment"># replace with your email address to receive alerts</span></span><br><span class="line">        <span class="comment"># HttpsEndpoint: &#x27;https://api.marbot.io/v1/endpoint/xyz&#x27; # or uncommnet and receive alerts in Slack using marbot.io</span></span><br><span class="line">      <span class="attr">TemplateURL:</span> <span class="string">&#x27;./node_modules/@cfn-modules/alerting/module.yml&#x27;</span></span><br><span class="line">  <span class="attr">Vpc:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFormation::Stack&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Parameters:</span></span><br><span class="line">        <span class="attr">AlertingModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Alerting.Outputs.StackName&#x27;</span> <span class="comment"># link to alerting module</span></span><br><span class="line">      <span class="attr">TemplateURL:</span> <span class="string">&#x27;./node_modules/@cfn-modules/vpc/module.yml&#x27;</span></span><br><span class="line">  <span class="attr">Cluster:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFormation::Stack&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">TemplateURL:</span> <span class="string">&#x27;./node_modules/@cfn-modules/ecs-cluster/module.yml&#x27;</span></span><br></pre></td></tr></table></figure><p>Next, you will create the ALB setup with the following modules:</p><ul><li><a href="https://github.com/cfn-modules/alb" target="_blank" rel="noopener">alb</a>: Application load balancer shell.</li><li><a href="https://github.com/cfn-modules/alb-listener" target="_blank" rel="noopener">alb-listener</a>: ALB listener to listen on port 80 (we use HTTP, but HTTPS is also supported if you pass a <code>CertificateArn</code>).</li><li><a href="https://github.com/cfn-modules/ecs-alb-target" target="_blank" rel="noopener">ecs-alb-target</a>: ECS ALB target that connects the ALB with the Fargate service.</li></ul><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  [<span class="string">...</span>]</span><br><span class="line">  <span class="attr">Alb:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFormation::Stack&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Parameters:</span></span><br><span class="line">        <span class="attr">VpcModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Vpc.Outputs.StackName&#x27;</span> <span class="comment"># link to vpc module</span></span><br><span class="line">        <span class="attr">AlertingModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Alerting.Outputs.StackName&#x27;</span> <span class="comment"># link to alerting module</span></span><br><span class="line">      <span class="attr">TemplateURL:</span> <span class="string">&#x27;./node_modules/@cfn-modules/alb/module.yml&#x27;</span></span><br><span class="line">  <span class="attr">AlbListener:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFormation::Stack&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Parameters:</span></span><br><span class="line">        <span class="attr">AlbModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Alb.Outputs.StackName&#x27;</span> <span class="comment"># link to alb module</span></span><br><span class="line">      <span class="attr">TemplateURL:</span> <span class="string">&#x27;./node_modules/@cfn-modules/alb-listener/module.yml&#x27;</span></span><br><span class="line">  <span class="attr">Target:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFormation::Stack&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Parameters:</span></span><br><span class="line">        <span class="attr">AlbModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Alb.Outputs.StackName&#x27;</span></span><br><span class="line">        <span class="attr">AlbListenerModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;AlbListener.Outputs.StackName&#x27;</span></span><br><span class="line">        <span class="attr">VpcModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Vpc.Outputs.StackName&#x27;</span></span><br><span class="line">        <span class="attr">AlertingModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Alerting.Outputs.StackName&#x27;</span></span><br><span class="line">      <span class="attr">TemplateURL:</span> <span class="string">&#x27;./node_modules/@cfn-modules/ecs-alb-target/module.yml&#x27;</span></span><br></pre></td></tr></table></figure><p>Any finally, you define the Docker images in the <a href="https://github.com/cfn-modules/fargate-service" target="_blank" rel="noopener">fargate-service</a> module.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  [<span class="string">...</span>]</span><br><span class="line">  <span class="attr">Service:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFormation::Stack&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Parameters:</span></span><br><span class="line">        <span class="attr">ClusterModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Cluster.Outputs.StackName&#x27;</span></span><br><span class="line">        <span class="attr">VpcModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Vpc.Outputs.StackName&#x27;</span></span><br><span class="line">        <span class="attr">TargetModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Target.Outputs.StackName&#x27;</span></span><br><span class="line">        <span class="attr">AlertingModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Alerting.Outputs.StackName&#x27;</span></span><br><span class="line">        <span class="attr">AmbassadorImage:</span> <span class="string">&#x27;eeacms/varnish:4.1-6.2&#x27;</span></span><br><span class="line">        <span class="attr">AmbassadorPort:</span> <span class="string">&#x27;6081&#x27;</span></span><br><span class="line">        <span class="attr">AmbassadorEnvironment1Key:</span> <span class="string">&#x27;BACKENDS&#x27;</span></span><br><span class="line">        <span class="attr">AmbassadorEnvironment1Value:</span> <span class="string">&#x27;localhost&#x27;</span></span><br><span class="line">        <span class="attr">AmbassadorEnvironment2Key:</span> <span class="string">&#x27;BACKENDS_PORT&#x27;</span></span><br><span class="line">        <span class="attr">AmbassadorEnvironment2Value:</span> <span class="string">&#x27;80&#x27;</span></span><br><span class="line">        <span class="attr">AppImage:</span> <span class="string">&#x27;widdix/hello:v1&#x27;</span></span><br><span class="line">        <span class="attr">AppPort:</span> <span class="string">&#x27;80&#x27;</span></span><br><span class="line">      <span class="attr">TemplateURL:</span> <span class="string">&#x27;./node_modules/@cfn-modules/fargate-service/module.yml&#x27;</span></span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">Url:</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Sub</span> <span class="string">&#x27;http://$&#123;Alb.Outputs.DnsName&#125;&#x27;</span></span><br></pre></td></tr></table></figure><p>If you merge all the code snipets together in <code>example.yml</code>, you can deploy the template. You can also download the <a href="https://github.com/cfn-modules/docs/tree/master/examples/fargate-alb-ambassador-pattern" target="_blank" rel="noopener">example on GitHub</a>.</p><blockquote><p><a href="https://docs.aws.amazon.com/cli/latest/userguide/installing.html" target="_blank" rel="noopener">Install AWS CLI</a> if <code>aws</code> is not installed on your system </p></blockquote><p>If you use <code>cfn-modules</code> the first time, create an S3 bucket to store the artifacts first (otherwise, skip this step). Choose a unique bucket name, e.g. <code>cfn-modules-$Name-$Region</code>.</p><p>In the following command, replace <code>$Name</code> with a unique name (e.g. your initials or company name), and replace <code>$Region</code> with your AWS default region (e.g. <code>us-east-1</code>) to create an S3 bucket:</p><figure class="highlight armasm"><table><tr><td class="code"><pre><span class="line"><span class="symbol">aws</span> <span class="built_in">s3</span> mb <span class="built_in">s3</span>:<span class="comment">//cfn-modules-$Name-$Region</span></span><br></pre></td></tr></table></figure><p>Now you can upload all artifacts to S3:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws cloudformation package --template-file example.yml --s3-bucket cfn-modules-<span class="variable">$Name</span>-<span class="variable">$Region</span> --output-template-file packaged.yml</span><br></pre></td></tr></table></figure><p>Finally, you can create a CloudFormation stack with <code>aws cloudformation deploy</code>:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws cloudformation deploy --template-file packaged.yml --stack-name fargate-alb-ambassador-pattern-example --capabilities CAPABILITY_IAM</span><br></pre></td></tr></table></figure><p>Creating the stack will take about 15 minutes. You can find the URL to the “hello world” page in the stack outputs:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws cloudformation describe-stacks --stack-name fargate-alb-ambassador-pattern-example --query <span class="string">&quot;Stacks[0].Outputs[?OutputKey==&#x27;Url&#x27;].OutputValue&quot;</span> --output text</span><br></pre></td></tr></table></figure><p>Open the URL in your web browser.</p><p>Don’t forget to delete the stack once you are done with the demo:</p><figure class="highlight dsconfig"><table><tr><td class="code"><pre><span class="line"><span class="string">aws</span> <span class="string">cloudformation</span> <span class="built_in">delete-stack</span> <span class="built_in">--stack-name</span> <span class="string">fargate-alb-ambassador-pattern-example</span></span><br></pre></td></tr></table></figure><p>Check out the modules to learn more about additional parameters that are available.</p><ul><li><a href="https://github.com/cfn-modules/alerting" target="_blank" rel="noopener">alerting</a></li><li><a href="https://github.com/cfn-modules/vpc" target="_blank" rel="noopener">vpc</a></li><li><a href="https://github.com/cfn-modules/ecs-cluster" target="_blank" rel="noopener">ecs-cluster</a></li><li><a href="https://github.com/cfn-modules/alb" target="_blank" rel="noopener">alb</a></li><li><a href="https://github.com/cfn-modules/alb-listener" target="_blank" rel="noopener">alb-listener</a></li><li><a href="https://github.com/cfn-modules/ecs-alb-target" target="_blank" rel="noopener">ecs-alb-target</a></li></ul><h2 id="Summary-and-Fargate-limitations"><a href="#Summary-and-Fargate-limitations" class="headerlink" title="Summary and Fargate limitations"></a>Summary and Fargate limitations</h2><p>Fargate is the easiest way to run Docker workloads on AWS. We recommend using CloudFormation to put the AWS building blocks together. We maintain two open source projects with production-ready templates for you to use:</p><ul><li><a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a> are composable and very opinionated but easy to use and CloudFormation beginner friendly.</li><li><a href="https://github.com/cfn-modules" target="_blank" rel="noopener">cfn-modules</a> are highly modularized and flexible but require more CloudFormation experience.</li></ul><p>Keep in mind the following limitations of Fargate:</p><ul><li>No support for EFS file systems</li><li>No support for EBS volumes</li><li>No support for encryption of data at rest</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>2018 in Review</title>
      <link>https://cloudonaut.io/2018-in-review/</link>
      <description>
        <![CDATA[<p>The year 2018 is coming to an end. We want to thank our readers, customers, supporters, and partners. It was a pleasure to be part of an]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/retrospective/">retrospective</category>
      <guid isPermaLink="true">https://cloudonaut.io/2018-in-review/</guid>
      <pubDate>Sat, 29 Dec 2018 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The year 2018 is coming to an end. We want to thank our readers, customers, supporters, and partners. It was a pleasure to be part of an inspiring community.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/12/newyear@730w.webp 730w, /images/2018/12/newyear@730w2x.webp 1460w, /images/2018/12/newyear@610w.webp 610w, /images/2018/12/newyear@610w2x.webp 1220w, /images/2018/12/newyear@450w.webp 450w, /images/2018/12/newyear@450w2x.webp 900w, /images/2018/12/newyear@330w.webp 330w, /images/2018/12/newyear@330w2x.webp 660w, /images/2018/12/newyear@545w.webp 545w, /images/2018/12/newyear@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/12/newyear@730w.jpg 730w, /images/2018/12/newyear@730w2x.jpg 1460w, /images/2018/12/newyear@610w.jpg 610w, /images/2018/12/newyear@610w2x.jpg 1220w, /images/2018/12/newyear@450w.jpg 450w, /images/2018/12/newyear@450w2x.jpg 900w, /images/2018/12/newyear@330w.jpg 330w, /images/2018/12/newyear@330w2x.jpg 660w, /images/2018/12/newyear@545w.jpg 545w, /images/2018/12/newyear@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/12/newyear.jpg" alt="Happy New Year" title="Happy New Year"></picture></p><h2 id="Blog"><a href="#Blog" class="headerlink" title="Blog"></a>Blog</h2><p>We share how-tos, lessons learned and opinions on our blog <a href="https://cloudonaut.io/">cloudonaut.io</a>. Thanks for reading, sharing, and providing feedback!</p><p>A few facts and numbers for 2018:</p><ul><li>500,000 views</li><li>278,000 readers</li><li>37 articles published</li><li>40,000 written words</li></ul><p>Top 10 articles in 2018:</p><ul><li><a href="/ec2-network-performance-cheat-sheet/">EC2 Network Performance Cheat Sheet</a></li><li><a href="/eks-vs-ecs-orchestrating-containers-on-aws/">EKS vs. ECS: orchestrating containers on AWS</a></li><li><a href="/migrating-to-amazon-linux-2/">Migrating to Amazon Linux 2</a></li><li><a href="/aws-monitoring-primer/">AWS Monitoring Primer</a></li><li><a href="/configure-your-cloudformation-managed-infrastructure-with-parameter-store-and-codepipeline/">Configure your CloudFormation managed infrastructure with Parameter Store and CodePipeline</a></li><li><a href="/my-mental-model-of-aws/">My mental model of AWS</a></li><li><a href="/restricting-access-to-ec2-instances-based-on-tags/">Restricting Access to EC2 Instances Based on Tags</a></li><li><a href="/cleaning-up-an-s3-bucket-with-the-help-of-athena/">Cleaning up an S3 bucket with the help of Athena</a></li><li><a href="/tweaking-rds-performance-and-elasticache/">Tweaking RDS database performance and ElastiCache</a></li><li><a href="/dead-mans-switch-with-cloudwatch/">Dead man’s switch with CloudWatch</a></li></ul><h2 id="Book"><a href="#Book" class="headerlink" title="Book"></a>Book</h2><p>We published the second edition of <a href="http://bit.ly/amazon-web-services-in-action-2nd-edition" target="_blank" rel="noopener">Amazon Web Services in Action</a> as printed book and ebook (PDF) in 2018. As AWS is iterating fast a lot has changed since the first edition in 2015 we have completely revised and updated all chapters. On top of that, we have added three entirely new chapters to the second edition: Amazon Elastic File System (EFS), Amazon ElastiCache, and AWS Lambda.</p><h2 id="Product"><a href="#Product" class="headerlink" title="Product"></a>Product</h2><p>We made massive progress with <a href="https://marbot.io/" target="_blank" rel="noopener">marbot, our easy-going incident management tool for AWS</a> in 2018.</p><ul><li>Improved conversations with marbot by reducing latency.</li><li>Introduced aggregation - also known as deduplication - of alerts.</li><li>Added integrations for OpsWorks and Elastic Beanstalk alerts.</li><li>Implemented auto-close for CloudWatch alarms.</li><li>Improved reliability, for example by introducing rate limits for alerts.</li></ul><p>Thanks a lot to all our customers for supporting the development of marbot.</p><h2 id="Open-Source"><a href="#Open-Source" class="headerlink" title="Open Source"></a>Open Source</h2><p>We do profit from Open Source in our day-to-day work. That’s why we are trying to give something back to the community by sharing the following projects with you.</p><ul><li><a href="https://github.com/cfn-modules" target="_blank" rel="noopener">cfn-modules: Rapid CloudFormation: modular, production ready, open source</a> (53 stars)</li><li><a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">widdix&#x2F;aws-cf-templates: Free Templates for AWS CloudFormation</a> (1205 stars and 499 forks)</li><li><a href="https://github.com/widdix/aws-ec2-ssh" target="_blank" rel="noopener">widdix&#x2F;aws-ec2-ssh: Manage AWS EC2 SSH access with IAM</a> (609 stars and 207 forks)</li><li><a href="https://github.com/widdix/aws-s3-virusscan" target="_blank" rel="noopener">widdix&#x2F;aws-s3-virusscan: Free Antivirus for S3 Buckets</a> (222 stars and 56 forks)</li><li><a href="https://github.com/widdix/cfn-create-or-update" target="_blank" rel="noopener">widdix&#x2F;cfn-create-or-update: Create or update CloudFormation stack also if no updates are to be performed</a> (45 stars and 11 forks)</li><li><a href="https://github.com/widdix/cloudwatch-alarm-to-slack" target="_blank" rel="noopener">widdix&#x2F;cloudwatch-alarm-to-slack: Send CloudWatch Alarms to Slack with AWS Lambda</a> (19 stars and 4 fork)</li></ul><p>Thanks to all users and contributors for your feedback and support.</p><h2 id="Consulting-Projects"><a href="#Consulting-Projects" class="headerlink" title="Consulting Projects"></a>Consulting Projects</h2><p>We enjoy accompanying our consulting clients with our AWS and DevOps expertise. In 2018 we have worked together with small startups, medium-sized companies, and big enterprises.</p><p>A few technologies that we have used in 2018 to solve the real-world challenges of our clients:</p><ul><li>Docker on AWS with ECS and EKS</li><li>Deployment Pipelines with CodePipeline</li><li>Infrastructure as Code with CloudFormation</li><li>Serverless with Lambda, API Gateway, Cognito, and DynamoDB</li><li>Storing and querying hierarchical data with Cloud Directory</li><li>Event-driven architectures with Kinesis and Lambda</li><li>Authorization concept with IAM and Organizations</li></ul><p>Thanks to our consulting clients for putting their trust into our expertise.</p><p><strong>We wish you a Happy New Year.</strong></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>How to become an AWS expert</title>
      <link>https://cloudonaut.io/how-to-become-an-aws-expert/</link>
      <description>
        <![CDATA[<blockquote>
<p>This post was original published on the <a href="https://aws.amazon.com/blogs/aws/how-to-become-an-aws-expert/" target="_bla]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/career/">career</category>
      <guid isPermaLink="true">https://cloudonaut.io/how-to-become-an-aws-expert/</guid>
      <pubDate>Wed, 19 Dec 2018 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<blockquote><p>This post was original published on the <a href="https://aws.amazon.com/blogs/aws/how-to-become-an-aws-expert/" target="_blank" rel="noopener">AWS News Blog</a>.</p></blockquote><p>If you are just starting to use AWS today, you might think it’s going to be hard to catch up. How can you become an AWS expert? How can you know everything about AWS? I asked myself the same questions some time ago. Let me share my answer on how to become an AWS expert.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/12/learn@730w.webp 730w, /images/2018/12/learn@730w2x.webp 1460w, /images/2018/12/learn@610w.webp 610w, /images/2018/12/learn@610w2x.webp 1220w, /images/2018/12/learn@450w.webp 450w, /images/2018/12/learn@450w2x.webp 900w, /images/2018/12/learn@330w.webp 330w, /images/2018/12/learn@330w2x.webp 660w, /images/2018/12/learn@545w.webp 545w, /images/2018/12/learn@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/12/learn@730w.jpg 730w, /images/2018/12/learn@730w2x.jpg 1460w, /images/2018/12/learn@610w.jpg 610w, /images/2018/12/learn@610w2x.jpg 1220w, /images/2018/12/learn@450w.jpg 450w, /images/2018/12/learn@450w2x.jpg 900w, /images/2018/12/learn@330w.jpg 330w, /images/2018/12/learn@330w2x.jpg 660w, /images/2018/12/learn@545w.jpg 545w, /images/2018/12/learn@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/12/learn.jpg" alt="Learn" title="Learn"></picture></p><h2 id="My-story"><a href="#My-story" class="headerlink" title="My story"></a>My story</h2><p>I have used AWS for more than five years. Working with my clients of all sizes and industries challenges my AWS knowledge every day. I also maintain several <a href="http://github.com/widdix/" target="_blank" rel="noopener">AWS open source projects that I try to keep up-to-date with the ever-improving AWS platform</a>.</p><p>Let me show you my tips on staying up-to-date with AWS and learning new things. Here are three examples of the most exciting and surprising things I learned about AWS this year:</p><ul><li>Network Load Balancers</li><li>Amazon Linux 2</li><li>Amazon Cloud Directory</li></ul><h2 id="Network-Load-Balancers"><a href="#Network-Load-Balancers" class="headerlink" title="Network Load Balancers"></a>Network Load Balancers</h2><p>When I started using AWS, there was one option to load balance HTTP and raw TCP traffic: what is now called a Classic Load Balancer. Since then, the portfolio of load balancers has expanded. You can now also choose the Application Load Balancer to distribute HTTP(S) traffic (including HTTP2 and WebSockets) or the Network Load Balancer, which operates on layer 4 to load balance TCP traffic.</p><p>When reading the Network Load Balancer announcement, I found myself interested in this shiny new thing. And that’s the first important part of learning something new: If you are interested in the topic, it’s much easier to learn.</p><p><strong>Tip #1: Pick the topics that are interesting.</strong></p><p>When I’m interested in a topic, I dive into the documentation and read it from top to bottom. It can take a few hours to finish reading before you can start using the new service or feature. However, you then know about all the concepts, best practices, and pitfalls, which saves you time in the long run.</p><p><strong>Tip #2: Reading the documentation is a good investment.</strong></p><p>Can I remember everything that I read? No. For example, there is one <a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-troubleshooting.html#loopback-timeout" target="_blank" rel="noopener">documented limitation</a> to keep in mind when using the Network Load Balancer: Internal load balancers do not support hairpinning or loopback. I read about it and still ran into it. Sometimes, I have to learn the hard way as well.</p><h2 id="Amazon-Linux-2"><a href="#Amazon-Linux-2" class="headerlink" title="Amazon Linux 2"></a>Amazon Linux 2</h2><p><a href="https://aws.amazon.com/amazon-linux-2/" target="_blank" rel="noopener">Amazon Linux 2</a> is the successor of Amazon Linux. Both distributions come with a superb AWS integration, a secure default configuration, and regular security updates. You can open AWS Support tickets if you run into any problems.</p><p>So, what’s new with Amazon Linux 2? You get long-term support for five years and you can now run a copy of Amazon Linux 2 on your local machine or on premises. The most significant changes are the replacement of <a href="https://en.wikipedia.org/wiki/Init#SysV-style" target="_blank" rel="noopener">SysVinit</a> with <a href="https://en.wikipedia.org/wiki/Systemd" target="_blank" rel="noopener">systemd</a> and a new way to install additional software, also known as the extras library.</p><p>The systemd init system was all new to me. I decided that it was time to change that and I remembered <a href="https://www.meetup.com/AWS-UserGroup-Stuttgart/events/nlpnsmyxdblb/" target="_blank" rel="noopener">a session from the local AWS User Group in my city</a> about systemd that I had missed. Luckily, I knew the speaker well. I asked Thorsten a few questions to get an idea about the topics I should learn about to understand how systemd works.</p><p>There is always someone who knows what you want to learn. You have to find that person. I encourage you to connect with your <a href="https://aws.amazon.com/developer/community/usergroups/" target="_blank" rel="noopener">local AWS community</a>.</p><p><strong>Tip #3: It’s easier to learn if you have a network of people to ask questions of and get inspired by.</strong></p><h2 id="Amazon-Cloud-Directory"><a href="#Amazon-Cloud-Directory" class="headerlink" title="Amazon Cloud Directory"></a>Amazon Cloud Directory</h2><p>One of my projects this year was all about hierarchical data. I was looking for a way to store this kind of data in AWS, and I discovered <a href="https://aws.amazon.com/cloud-directory" target="_blank" rel="noopener">Amazon Cloud Directory</a>. Cloud Directory was all new to me and seemed difficult to learn about. I read all of the documentation. Still, it was painful and I wanted to give up a few times. That’s normal. That’s why I reward myself from time to time (for example, read one more hour of docs and then go for a walk).</p><p><strong>Tip #4: Learning a new thing is hard at first. Keep going.</strong></p><p>Cloud Directory is a fully managed, hierarchical data store on AWS. Hierarchical data is connected using parent-child relationships. Let me give you an example. Imagine a chat system with teams and channels. The following figure shows a Cloud Directory data model for the imaginary chat system.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/09/cloud-directory-hierarchy@730w.webp 730w, /images/2018/09/cloud-directory-hierarchy@730w2x.webp 1460w, /images/2018/09/cloud-directory-hierarchy@610w.webp 610w, /images/2018/09/cloud-directory-hierarchy@610w2x.webp 1220w, /images/2018/09/cloud-directory-hierarchy@450w.webp 450w, /images/2018/09/cloud-directory-hierarchy@450w2x.webp 900w, /images/2018/09/cloud-directory-hierarchy@330w.webp 330w, /images/2018/09/cloud-directory-hierarchy@330w2x.webp 660w, /images/2018/09/cloud-directory-hierarchy@545w.webp 545w, /images/2018/09/cloud-directory-hierarchy@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/09/cloud-directory-hierarchy@730w.png 730w, /images/2018/09/cloud-directory-hierarchy@730w2x.png 1460w, /images/2018/09/cloud-directory-hierarchy@610w.png 610w, /images/2018/09/cloud-directory-hierarchy@610w2x.png 1220w, /images/2018/09/cloud-directory-hierarchy@450w.png 450w, /images/2018/09/cloud-directory-hierarchy@450w2x.png 900w, /images/2018/09/cloud-directory-hierarchy@330w.png 330w, /images/2018/09/cloud-directory-hierarchy@330w2x.png 660w, /images/2018/09/cloud-directory-hierarchy@545w.png 545w, /images/2018/09/cloud-directory-hierarchy@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/09/cloud-directory-hierarchy.png" alt="Cloud Directory Hierarchy" title="Cloud Directory Hierarchy"></picture></p><p>When you have a good understanding of a topic, it’s time to master it by using it. You also learn so much while explaining a concept to someone else. That’s why I wrote a blog post, <a href="/a-neglected-serverless-data-store-cloud-directory/">A neglected serverless data store: Cloud Directory</a>.</p><p><strong>Tip #5: Apply your knowledge. Share your knowledge. Teach others.</strong></p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Becoming an AWS expert is a journey without a final destination. There is always something more to learn. AWS is a huge platform with 100+ services and countless capabilities. The offerings are constantly changing.</p><p>I want to encourage you to become an AWS expert. Why not start with one of the new services released at re:Invent this year? Pick the one that is most interesting for you. Read the documentation. Ask questions of others. Be inspired by others. Apply your knowledge. Share with a blog post or a talk to your local AWS user group. Isn’t this what an expert does?</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Eat your own dog food: how AWS leverages Serverless</title>
      <link>https://cloudonaut.io/eat-your-own-dog-food-how-aws-leverages-serverless/</link>
      <description>
        <![CDATA[<p><a href="https://www.linkedin.com/in/timbraysoftwareguy/" target="_blank" rel="noopener">Tim Bray</a> from Amazon gave a great <a href="h]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/reinvent/">reinvent</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/eat-your-own-dog-food-how-aws-leverages-serverless/</guid>
      <pubDate>Mon, 10 Dec 2018 12:12:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><a href="https://www.linkedin.com/in/timbraysoftwareguy/" target="_blank" rel="noopener">Tim Bray</a> from Amazon gave a great <a href="https://www.youtube.com/watch?v=IPOvrK3S3gQ" target="_blank" rel="noopener">talk at re:Invent 2018</a> where he shows us how AWS uses Serverless technologies. Did you know that some of the newer services such as API Gateway and EKS are using API Gateway and Lambda to implement the control plane? In this post, I summarise a few interesting points he made. I also encourage you to watch the <a href="https://www.youtube.com/watch?v=IPOvrK3S3gQ" target="_blank" rel="noopener">full video</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/12/dog@730w.webp 730w, /images/2018/12/dog@730w2x.webp 1460w, /images/2018/12/dog@610w.webp 610w, /images/2018/12/dog@610w2x.webp 1220w, /images/2018/12/dog@450w.webp 450w, /images/2018/12/dog@450w2x.webp 900w, /images/2018/12/dog@330w.webp 330w, /images/2018/12/dog@330w2x.webp 660w, /images/2018/12/dog@545w.webp 545w, /images/2018/12/dog@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/12/dog@730w.jpg 730w, /images/2018/12/dog@730w2x.jpg 1460w, /images/2018/12/dog@610w.jpg 610w, /images/2018/12/dog@610w2x.jpg 1220w, /images/2018/12/dog@450w.jpg 450w, /images/2018/12/dog@450w2x.jpg 900w, /images/2018/12/dog@330w.jpg 330w, /images/2018/12/dog@330w2x.jpg 660w, /images/2018/12/dog@545w.jpg 545w, /images/2018/12/dog@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/12/dog.jpg" alt="Dog Food" title="Dog Food"></picture></p><h2 id="SQS-internals"><a href="#SQS-internals" class="headerlink" title="SQS internals"></a>SQS internals</h2><p>SQS provides message queues as a service. The following figure shows a high-level architecture of the SQS service.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/12/sqs-internals@730w.webp 730w, /images/2018/12/sqs-internals@730w2x.webp 1460w, /images/2018/12/sqs-internals@610w.webp 610w, /images/2018/12/sqs-internals@610w2x.webp 1220w, /images/2018/12/sqs-internals@450w.webp 450w, /images/2018/12/sqs-internals@450w2x.webp 900w, /images/2018/12/sqs-internals@330w.webp 330w, /images/2018/12/sqs-internals@330w2x.webp 660w, /images/2018/12/sqs-internals@545w.webp 545w, /images/2018/12/sqs-internals@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/12/sqs-internals@730w.png 730w, /images/2018/12/sqs-internals@730w2x.png 1460w, /images/2018/12/sqs-internals@610w.png 610w, /images/2018/12/sqs-internals@610w2x.png 1220w, /images/2018/12/sqs-internals@450w.png 450w, /images/2018/12/sqs-internals@450w2x.png 900w, /images/2018/12/sqs-internals@330w.png 330w, /images/2018/12/sqs-internals@330w2x.png 660w, /images/2018/12/sqs-internals@545w.png 545w, /images/2018/12/sqs-internals@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/12/sqs-internals.png" alt="SQS internals" title="SQS internals"></picture></p><p>When a client make calls to the SQS API, you talk to the <strong>Front End</strong> service of SQS. The <strong>Front End</strong> service asks the <strong>Metadata</strong> service (backed by DynamoDB) for internal data about the queue. Does it exist? How does the queue policy look like? Which <strong>Back End</strong> cluster is responsible for the queue? Once the <strong>Front End</strong> knows the answers, it likely checks if the requestor is allowed to make the request and routes the request to one of the many <strong>Back End</strong> clusters to process the message. A single <strong>Back End</strong> cluster is responsible for one or many queues (or partitions of a queue) and stores your messages in a distributed manner on the cluster.</p><p>In the background, the <strong>Load Manager</strong> service continually checks the utilization of the <strong>Back End</strong> clusters. If a cluster gets hot, the queues are partitioned and&#x2F;or moved to other&#x2F;new clusters. If clusters idle, queues are moved as well to shut down whole clusters. As users of SQS, we don’t see any of this. We send and retrieve messages.</p><p>One funny anecdote Tim shares with us: the SQS team replaced an Oracle Database with DynamoDB.</p><h2 id="Amazon-MQ-internals"><a href="#Amazon-MQ-internals" class="headerlink" title="Amazon MQ internals"></a>Amazon MQ internals</h2><p>Amazon MQ runs <a href="http://activemq.apache.org/" target="_blank" rel="noopener">ActiveMQ</a> brokers on your behalf. The following figure shows a high-level architecture of the ActiveMQ service.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/12/amazon-mq-internals@730w.webp 730w, /images/2018/12/amazon-mq-internals@730w2x.webp 1460w, /images/2018/12/amazon-mq-internals@610w.webp 610w, /images/2018/12/amazon-mq-internals@610w2x.webp 1220w, /images/2018/12/amazon-mq-internals@450w.webp 450w, /images/2018/12/amazon-mq-internals@450w2x.webp 900w, /images/2018/12/amazon-mq-internals@330w.webp 330w, /images/2018/12/amazon-mq-internals@330w2x.webp 660w, /images/2018/12/amazon-mq-internals@545w.webp 545w, /images/2018/12/amazon-mq-internals@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/12/amazon-mq-internals@730w.png 730w, /images/2018/12/amazon-mq-internals@730w2x.png 1460w, /images/2018/12/amazon-mq-internals@610w.png 610w, /images/2018/12/amazon-mq-internals@610w2x.png 1220w, /images/2018/12/amazon-mq-internals@450w.png 450w, /images/2018/12/amazon-mq-internals@450w2x.png 900w, /images/2018/12/amazon-mq-internals@330w.png 330w, /images/2018/12/amazon-mq-internals@330w2x.png 660w, /images/2018/12/amazon-mq-internals@545w.png 545w, /images/2018/12/amazon-mq-internals@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/12/amazon-mq-internals.png" alt="Amazon MQ internals" title="Amazon MQ internals"></picture></p><p>The brokers itself run on EC2 Instances and likely persist your state on EBS volumes. Tim calls this the <strong>Data Plane</strong>. Interestingly, the <strong>Control Plane</strong> (e.g., the public API) is implemented using API Gateway and Lambda functions that store the state about the ActiveMQ clusters in DynamoDB.</p><h2 id="AWS-uses-Serverless-to-implement-control-planes"><a href="#AWS-uses-Serverless-to-implement-control-planes" class="headerlink" title="AWS uses Serverless to implement control planes"></a>AWS uses Serverless to implement control planes</h2><p>Also, Tim talks about a few other services that come with Serverless control planes:</p><ul><li>Amazon SageMaker</li><li>AWS Batch</li><li>AWS Elemental</li><li>AWS AppSync</li><li>AWS IoT Core</li><li>Amazon GuardDuty</li><li>Amazon EKS</li><li>Amazon API Gateway</li></ul><p>Funny story: API Gateway itself uses a Serverless control plane.</p><h2 id="Full-video"><a href="#Full-video" class="headerlink" title="Full video"></a>Full video</h2><p>I hope you are now interested in the full talk!</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=IPOvrK3S3gQ">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/IPOvrK3S3gQ" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p>What’s your favorite talk from re:Invent? <a href="mailto:&#109;&#105;&#x63;&#104;&#97;&#101;&#108;&#x40;&#x77;&#105;&#x64;&#x64;&#105;&#120;&#x2e;&#x64;&#101;">Let me know!</a></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>The somewhat different AWS re:Invent recap</title>
      <link>https://cloudonaut.io/the-somewhat-different-aws-reinvent-recap/</link>
      <description>
        <![CDATA[<p>I’m still recovering from our trip to AWS re:Invent in Las Vegas. The conference was a blast. It was a huge pleasure to meet old and new]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/reinvent/">reinvent</category>
      <guid isPermaLink="true">https://cloudonaut.io/the-somewhat-different-aws-reinvent-recap/</guid>
      <pubDate>Wed, 05 Dec 2018 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>I’m still recovering from our trip to AWS re:Invent in Las Vegas. The conference was a blast. It was a huge pleasure to meet old and new friends. This blog post contains a somewhat different recap of re:Invent 2018. Are you looking for a more traditional recap including all announcements instead? Check out <a href="https://acloud.guru/series/aws-this-week/view/177" target="_blank" rel="noopener">A Cloud Guru’s re:Invent Special</a> or <a href="https://www.trek10.com/blog/reinvent-2018-use-cases/" target="_blank" rel="noopener">treck10’s Real-world use cases for our favorite re:Invent 2018 announcements</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/12/lasvegas@730w.webp 730w, /images/2018/12/lasvegas@730w2x.webp 1460w, /images/2018/12/lasvegas@610w.webp 610w, /images/2018/12/lasvegas@610w2x.webp 1220w, /images/2018/12/lasvegas@450w.webp 450w, /images/2018/12/lasvegas@450w2x.webp 900w, /images/2018/12/lasvegas@330w.webp 330w, /images/2018/12/lasvegas@330w2x.webp 660w, /images/2018/12/lasvegas@545w.webp 545w, /images/2018/12/lasvegas@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/12/lasvegas@730w.jpg 730w, /images/2018/12/lasvegas@730w2x.jpg 1460w, /images/2018/12/lasvegas@610w.jpg 610w, /images/2018/12/lasvegas@610w2x.jpg 1220w, /images/2018/12/lasvegas@450w.jpg 450w, /images/2018/12/lasvegas@450w2x.jpg 900w, /images/2018/12/lasvegas@330w.jpg 330w, /images/2018/12/lasvegas@330w2x.jpg 660w, /images/2018/12/lasvegas@545w.jpg 545w, /images/2018/12/lasvegas@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/12/lasvegas.jpg" alt="Las Vegas" title="Las Vegas"></picture></p><h2 id="What-was-missing"><a href="#What-was-missing" class="headerlink" title="What was missing?"></a>What was missing?</h2><p>Neither Andy Jassy (CEO) nor Werner Vogels (CTO) talked about containers in their keynotes on Wednesday and Thursday. Instead of that AWS is pushing Serverless with success stories from enterprise customers. However, containers were not only missing during the keynotes. I was also missing essential announcements from ECS, EKS, and Fargate. For example:</p><ul><li>EKS is still only available in four regions.</li><li>The integration of EKS with VPC, IAM, ELB is not satisfactory.</li><li>Fargate does still not support persistent storage (EBS, EFS, …).</li><li>A reserved pricing option for Fargate is still missing.</li></ul><p>If I remember correctly, Andy Jassy spread the word about AWS’s enormous <strong>pace of innovation</strong> in the past keynotes. This year, the slide with the number of innovations was missing. Is that because the pace of innovation is stagnating? Alternatively, does AWS switch focus from the neophiliacs to the neophobes?</p><h2 id="What-was-exciting"><a href="#What-was-exciting" class="headerlink" title="What was exciting?"></a>What was exciting?</h2><p>AWS announced more than 100 new features around re:Invent. Most of the new features are evolutionary, not revolutionary. Nevertheless, some of the announcement excited me. Here is my top 10.</p><ul><li><a href="https://aws.amazon.com/about-aws/whats-new/2018/11/alb-can-now-invoke-lambda-functions-to-serve-https-requests/" target="_blank" rel="noopener">Application Load Balancer can now invoke Lambda functions</a>(Generally Available)</li><li><a href="https://aws.amazon.com/about-aws/whats-new/2018/11/aws-step-functions-adds-eight-more-service-integrations/" target="_blank" rel="noopener">AWS Step Functions adds eight more service integrations</a> (Generally Available)</li><li><a href="https://aws.amazon.com/about-aws/whats-new/2018/11/introducing-amazon-managed-streaming-for-kafka-in-public-preview/" target="_blank" rel="noopener">Amazon Kafka, a managed service for streaming workloads</a> (Public Preview)</li><li><a href="https://aws.amazon.com/about-aws/whats-new/2018/11/introducing-aws-app-mesh---service-mesh-for-microservices-on-aws/" target="_blank" rel="noopener">App Mesh, a service mesh for microservices based on envoy</a> (Public Preview)</li><li><a href="https://aws.amazon.com/about-aws/whats-new/2018/11/announcing-amazon-timestream/" target="_blank" rel="noopener">Amazon Timestream, a fast, scalable, fully managed Time Series Database</a> (Preview)</li><li><a href="https://aws.amazon.com/about-aws/whats-new/2018/11/announcing-amazon-dynamodb-on-demand/" target="_blank" rel="noopener">Amazon DynamoDB on-demand, no need to provision throughput any more</a> (Generally Available)</li><li><a href="https://aws.amazon.com/about-aws/whats-new/2018/11/announcing-amazon-dynamodb-support-for-transactions/" target="_blank" rel="noopener">Amazon DynamoDB support for transactions</a> (Generally Available)</li><li><a href="https://aws.amazon.com/about-aws/whats-new/2018/11/sign-up-for-the-preview-of-amazon-aurora-postgresql-serverless/" target="_blank" rel="noopener">Amazon Aurora PostgreSQL Serverless</a> (Preview)</li><li><a href="https://aws.amazon.com/about-aws/whats-new/2018/11/introducing-aws-transit-gateway/" target="_blank" rel="noopener">AWS Transit Gateway to scale connectivity across thousands of Amazon VPCs</a> (Generally Available)</li><li><a href="https://aws.amazon.com/about-aws/whats-new/2018/11/introducing-aws-global-accelerator/" target="_blank" rel="noopener">AWS Global Accelerator, improve global application availability and performance</a> (Generally Available)</li></ul><h2 id="What-was-noticeable"><a href="#What-was-noticeable" class="headerlink" title="What was noticeable?"></a>What was noticeable?</h2><p>Within the previous years watching the re:Invent keynotes was frustrating as most of the announcements were not generally available yet. In some cases, it took AWS almost a year to deliver what was promised during the keynotes (e.g., CloudFormation drift detection, EKS, …). Nevertheless, this year many of the announcements are already generally available in most regions which is excellent!</p><p>Werner Vogels was talking about the importance of being in control of the whole stack for being able to innovate during his keynote. He was using RDS Aurora, Amazon’s cloud-native database engine, as an example to illustrate his proposition. When being in control of your whole stack is that important, why should we - as an AWS customer - use services like RDS Aurora or Lambda where we hand over the control of the stack entirely to AWS?</p><p>AWS is selling the idea of migrating from old-school database systems to cloud-native databases like RDS Aurora or DynamoDB harder than ever before. A long but significant way to go for their enterprise customers.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/12/reinvent@730w.webp 730w, /images/2018/12/reinvent@730w2x.webp 1460w, /images/2018/12/reinvent@610w.webp 610w, /images/2018/12/reinvent@610w2x.webp 1220w, /images/2018/12/reinvent@450w.webp 450w, /images/2018/12/reinvent@450w2x.webp 900w, /images/2018/12/reinvent@330w.webp 330w, /images/2018/12/reinvent@330w2x.webp 660w, /images/2018/12/reinvent@545w.webp 545w, /images/2018/12/reinvent@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/12/reinvent@730w.jpg 730w, /images/2018/12/reinvent@730w2x.jpg 1460w, /images/2018/12/reinvent@610w.jpg 610w, /images/2018/12/reinvent@610w2x.jpg 1220w, /images/2018/12/reinvent@450w.jpg 450w, /images/2018/12/reinvent@450w2x.jpg 900w, /images/2018/12/reinvent@330w.jpg 330w, /images/2018/12/reinvent@330w2x.jpg 660w, /images/2018/12/reinvent@545w.jpg 545w, /images/2018/12/reinvent@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/12/reinvent.jpg" alt="AWS re:Invent 2018" title="AWS re:Invent 2018"></picture></p><h2 id="Outro"><a href="#Outro" class="headerlink" title="Outro"></a>Outro</h2><p>AWS is doing a great job with evolving their platform for builders. For example, we are now using DynamoDB On-Demand for <a href="https://marbot.io/" target="_blank" rel="noopener">marbot, our incident management solution for AWS</a>. Doing so allows our chatbot to handle traffic spikes without an effect on latency and decreased costs. </p><p>What are your opinions about the stories being told at re:Invent? Which announcements excited you the most?</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>My mental model of AWS</title>
      <link>https://cloudonaut.io/my-mental-model-of-aws/</link>
      <description>
        <![CDATA[<p><strong>AWS is a complex system that no one can understand end-to-end?</strong> As AWS professionals we still have to deal with the AWS s]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/aws-architecture/">aws-architecture</category>
      <guid isPermaLink="true">https://cloudonaut.io/my-mental-model-of-aws/</guid>
      <pubDate>Thu, 15 Nov 2018 16:12:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><strong>AWS is a complex system that no one can understand end-to-end?</strong> As AWS professionals we still have to deal with the AWS system daily. We design for AWS, and we debug our applications running in AWS. How can we deal with a system that is too complex to understand in detail? <strong>A good mental model can help us</strong>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/11/mental-model@730w.webp 730w, /images/2018/11/mental-model@730w2x.webp 1460w, /images/2018/11/mental-model@610w.webp 610w, /images/2018/11/mental-model@610w2x.webp 1220w, /images/2018/11/mental-model@450w.webp 450w, /images/2018/11/mental-model@450w2x.webp 900w, /images/2018/11/mental-model@330w.webp 330w, /images/2018/11/mental-model@330w2x.webp 660w, /images/2018/11/mental-model@545w.webp 545w, /images/2018/11/mental-model@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/11/mental-model@730w.jpg 730w, /images/2018/11/mental-model@730w2x.jpg 1460w, /images/2018/11/mental-model@610w.jpg 610w, /images/2018/11/mental-model@610w2x.jpg 1220w, /images/2018/11/mental-model@450w.jpg 450w, /images/2018/11/mental-model@450w2x.jpg 900w, /images/2018/11/mental-model@330w.jpg 330w, /images/2018/11/mental-model@330w2x.jpg 660w, /images/2018/11/mental-model@545w.jpg 545w, /images/2018/11/mental-model@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/11/mental-model.jpg" alt="My mental model of AWS" title="My mental model of AWS"></picture></p><p>A mental model is a simple representation of the system that is good enough to predict how the system behaves. If AWS behaves differently than I predicted, I know that my mental model is not good enough. I tweak it. Over the years, I end up with a mental model that works in most situations. Be aware that AWS is evolving quickly. Sometimes, I have to adjust my mental model to take this into account. You can build your mental model by using AWS extensively, reading the documentation, blogs, books; listening to podcasts; talking to peers; Today, you also have the chance to look into my head. I present my mental model of AWS to you. I also added a few exercises to challenge your mental model.</p><h2 id="AWS-API"><a href="#AWS-API" class="headerlink" title="AWS API"></a>AWS API</h2><p>All responses from the AWS API (using CLI or SDK) are eventually consistent. A recent change might not appear in the result. Making the same request from two clients at the same time can result in different responses. There is no guarantee to read your writes.</p><p>Calls to the AWS API should be retried if they fail and are retriable.</p><p>All APIs are rate limited and when retrying an exponential backoff with a random component should be applied.</p><p>CloudTrail can be used to debug failed requests.</p><p><em>Exercise: If you write a script to copy snapshots from one AWS account to another, what are your assumptions?</em></p><h2 id="AWS-Resources"><a href="#AWS-Resources" class="headerlink" title="AWS Resources"></a>AWS Resources</h2><p>Most AWS resources send metrics to CloudWatch. You have to create CloudWatch Alarms to monitor those metrics. <strong>Pro tip</strong>: We offer a product to <a href="https://marbot.io/" target="_blank" rel="noopener">configure your AWS monitoring and manage your incidents using Slack</a>.</p><p>An Infrastructure as Code (IaC) tool (e.g., CloudFormation) is used to create&#x2F;update&#x2F;delete resources and rolls back on error. A deployment pipeline (e.g., CodePipeline) invokes the IaC tool.</p><p><em>Exercise: Pick three AWS resources with finite resources (CPU, Memory, Disk, …) and check if you monitor them with CloudWatch Alarms.</em></p><h2 id="Economics"><a href="#Economics" class="headerlink" title="Economics"></a>Economics</h2><p>Labor is expensive. Comparing costs of AWS services should take this into account (e.g., running a database on EC2 seems cheaper compared to RDS, but how many hours of labor does the EC2 solution require?)</p><p>Managed services from AWS are a good choice.</p><p>There are many ways to solve a problem with AWS. Know what you optimize for and design accordingly.</p><p><em>Exercise: Pick an infrastructure service that your team operates and calculate how many hours&#x2F;month you work to maintain the solution.</em></p><h2 id="Network"><a href="#Network" class="headerlink" title="Network"></a>Network</h2><p>The smallest unit to reason about is the Elastic Network Interface (ENI). Internal traffic in AWS is received on or send from an ENI. An EC2 instance comes with at least one ENI. As well as an RDS instance, ElastiCache node, and so on.</p><p>If a security group or NACL blocks a packet, Flow Logs can be used to see this (with a delay). Issues with route tables are not visible in Flow Logs.</p><p>Security groups provide enough security to control network traffic. NACLs are not needed most of the time.</p><p>Traffic inside VPCs is referenced using Security Groups (not IP addresses).</p><blockquote><p>Network Load Balancers (NLB) are very different. I have still no working mental model. Always a surprise.</p></blockquote><p><em>Exercise: Use Flow Logs to track a request you made. How can you debug a problem that is caused by a routing table?</em></p><h2 id="Identity-and-Access-Management-IAM"><a href="#Identity-and-Access-Management-IAM" class="headerlink" title="Identity and Access Management (IAM)"></a>Identity and Access Management (IAM)</h2><h3 id="Authentication"><a href="#Authentication" class="headerlink" title="Authentication"></a>Authentication</h3><p>An IAM user is either a human or a technical user for workloads outside of AWS.</p><p>Before an IAM role can be assumed authentication happens using an IAM user, AWS service, or Identity Federation.</p><p>The trust policy of an IAM role can give access to the outside of the AWS account.</p><h3 id="Authorization"><a href="#Authorization" class="headerlink" title="Authorization"></a>Authorization</h3><p>IAM policies manage access to resources. If the resources are in the same AWS account, the IAM policy controls access (except for KMS CMKs where you have to allow it IAM access explicitly using a resource policy aka key policy). If the resources are from another AWS account, a resource policy in the other AWS account controls access as well.</p><p>Only <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_actions-resources-contextkeys.html" target="_blank" rel="noopener">some actions</a> support resource-level permissions.</p><p>Granting least privileges is the goal. Only allow the minimum set of actions on resources.</p><p>Any IAM user&#x2F;group&#x2F;role with administrator access to IAM can escalate its own privileges. With IAM permission boundaries privilege escalation can be prevented.</p><blockquote><p>I have still no working mental model for IAM permission boundaries.</p></blockquote><h3 id="Others"><a href="#Others" class="headerlink" title="Others"></a>Others</h3><p>Service Control Policies (SCPs) affect all users (including the root user) and roles (excluding service-linked roles) in member accounts. SCPs take precedence over IAM policies (resource policies are not affected). A <code>Deny</code> can never be allowed again in the chain of SCPs and IAM policies. An IAM policy can only <code>Allow</code> what is allowed by the SCP. Think of SCPs as a guardrail.</p><p>Many services offer additional ways to authentication&#x2F;authorization (S3 bucket policies, S3 signed URL, SQS queue policy, …) and they can give access to the outside of the AWS account.</p><p><em>Exercise: Pick a random IAM role and understand the attached policies in detail.</em></p><h2 id="Burstable-performance"><a href="#Burstable-performance" class="headerlink" title="Burstable performance"></a>Burstable performance</h2><p>Many AWS resources do not provide stable performance. Instead, a burst mode is used to provide high performance for short periods of time. In constant and high load scenarios this can be a problem. </p><p>Many EC2 instance types come with burstable performance. Some are obvious (CPU of the <code>t</code> family), and predictable some are not (network bandwidth of most EC2 instances is burstable).</p><p>EBS <code>gp2</code> volumes burst using a credit system.</p><p>Load tests have to run long enough (e.g., 1 hour). Otherwise, you measure burst performance which suddenly drops significantly.</p><p><em>Exercise: How can you monitor burstable performance?</em></p><h2 id="Storage"><a href="#Storage" class="headerlink" title="Storage"></a>Storage</h2><table class="table table-striped table-responsive"><thead><tr><th>Service</th><th>Access</th><th>Maximum storage volume</th><th>Latency</th><th>Storage Cost</th><th>Notes</th></tr></thead><tbody><tr><td>S3</td><td>AWS API (SDKs, CLI), third party tools</td><td>unlimited</td><td>high</td><td>very low</td><td>Replicated in the region</td></tr><tr><td>Glacier</td><td>S3, AWS API (SDKs, CLI), third party tools</td><td>unlimited</td><td>extreme high</td><td>extreme low</td><td>Replicated in the region</td></tr><tr><td>EBS (SSD)</td><td>Attached to an EC2 instance via network</td><td>17.5 TB</td><td>low</td><td>low</td><td>Replicated in the AZ; magnetic disks are also available</td></tr><tr><td>EC2 Instance Store (SSD)</td><td>Attached to an EC2 instance directly</td><td>51.5 TB</td><td>very low</td><td>very low</td><td>Data is lost if instance is stopped/terminated/fails; data is not replicated; magnetic disks are also available</td></tr><tr><td>EFS</td><td>NFSv4.1, e.g., from EC2 instance or on-premises</td><td>unlimited</td><td>medium</td><td>medium</td><td>Replicated in the region; no native backup available</td></tr><tr><td>RDS (MySQL, SSD)</td><td>SQL</td><td>17.5 TB</td><td>medium</td><td>low</td><td>Use Multi-AZ to replicate to a second AZ; other engines available as well</td></tr></tbody></table><p><em>Source: <a href="http://bit.ly/amazon-web-services-in-action-2nd-edition" target="_blank" rel="noopener">Amazon Web Services in Action, Second Edition (Manning)</a></em></p><h2 id="Encryption"><a href="#Encryption" class="headerlink" title="Encryption"></a>Encryption</h2><h3 id="In-transit"><a href="#In-transit" class="headerlink" title="In transit"></a>In transit</h3><p>For AWS services, in transit encryption can be enabled (if not enabled by default). Certificates are usually issued and managed by the Certificate Manager.</p><h3 id="At-rest"><a href="#At-rest" class="headerlink" title="At rest"></a>At rest</h3><p>AWS services that persist data offer server-side encryption (SSE) that you usually have to enable. SSE usually used a secret key managed by KMS.</p><p>You can delete a KMS customer managed key (CMK) that a resource (e.g., EBS volume) uses for SSE. Usage can also be passive until you want to use the resource again (e.g., you cannot restore an RDS snapshot if the CMK is deleted). Backups (e.g., RDS snapshots) should be copied to another AWS account using a KMS CMK of that other AWS account to protect against data loss caused by the deletion of the key.</p><p><em>Exercise: If you use KMS: what is your strategy to prevent data loss caused by (accidentally) KMS CMK deletion?</em></p><h2 id="Services"><a href="#Services" class="headerlink" title="Services"></a>Services</h2><p>AWS services are either global (e.g., Route 53, CloudFront), regional (e.g., Lambda), or zonal (e.g., EBS). EC2 (and services based on EC2) are different and rely on a single hypervisor.</p><h3 id="EC2"><a href="#EC2" class="headerlink" title="EC2"></a>EC2</h3><p>CPU, RAM, local disks, network bandwidth, EBS bandwidth (only dedicated for EBS optimized instances, otherwise shared with network bandwidth) are finite resources and can be saturated. CloudWatch Alarms are needed to know when saturation happens. </p><p>Resource utilization of more than 80% can affect latency significantly.</p><p>A single EC2 instance is always at risk (e.g., hardware failure). Even the EC2 SLA does not apply to single instances.</p><p>A backup strategy is needed if data is stored on EC2 instances. Backups have to be consistent. </p><ul><li>On Linux, <code>fsfreeze -f</code> to flush open writes in combination with an EBS snapshot can be used to backup non-root volumes. Linux root volumes cannot be consistently backed up while the instance is running (using <code>fsfreeze -f</code> on the root volume can crash the instance). Two options: Stopping the instance and taking a snapshot or moving the valuable data out of the root volume.</li><li>On Windows, snapshots of all volumes can be created while the instance is running.</li></ul><p>A group of EC2 instances managed by an Auto Scaling Group (ASG) is tolerant to EC2 instance failure and AZ failure. The EC2 SLA covers a group of instances.</p><p>Operating EC2 instances is much effort (e.g., patching, logging, deployment). Containers are much easier to operate using Fargate. Lambda might also be a good choice.</p><p>Logs should flow to a central service (e.g., CloudWatch Logs, Kibana provided by the Elasticearch service).</p><p><em>Exercise: Which of your EC2 instances need a backup? How do you achieve a consistent backup?</em></p><h3 id="RDS"><a href="#RDS" class="headerlink" title="RDS"></a>RDS</h3><p>RDS instances are based on EC2 instances and share the same resource characteristics (finite CPU, RAM, network, …).</p><p>Different engines work very differently as well as Aurora MySQL&#x2F;PostgreSQL&#x2F;Serverless.</p><p><em>Exercise: What happens if y minor&#x2F;major update to your Multi-AZ engine of choice happens? There might be a downtime!</em></p><h3 id="Lambda"><a href="#Lambda" class="headerlink" title="Lambda"></a>Lambda</h3><p>A Lambda function implementation has to be idempotent.</p><p>The order of Lambda function invocations is not the order of Lambda function executions.</p><p>A Lambda function execution can be aborted at any time in the code.</p><p>If multiple functions work together, Step Functions should be used.</p><p>Local testing of a Lambda function is possible as with any other code. You have to mock calls to the AWS API as to any other external service you integrate with. An integration test against real AWS APIs is the second step after unit tests pass.</p><p><em>Exercise: Take one of your Lambda functions. For each line, figure out what happens if you call this line two times instead of once. Does it still work?</em></p><h3 id="Others-1"><a href="#Others-1" class="headerlink" title="Others"></a>Others</h3><table class="table table-striped table-responsive"><thead><tr><th>Service</th><th>Notes</th></tr></thead><tbody><tr><td>SNS</td><td>An SNS subscription is triggered at-least-once. Processing has to be idempotent.<br><br>HTTP(S) subscriptions are only retried in case of 5XX or timeouts. Not 429. The <code>NumberOfNotificationsFailed</code> metrics increases by one for each failed HTTP(S) request, even if a retry is successful.</td></tr><tr><td>SQS</td><td>No order.<br><br>Processing has to be idempotent.</td></tr><tr><td>Kinesis</td><td>Order within partition key.</td></tr><tr><td>DynamoDB</td><td>Operations are atomic.<br><br>No concurrent writes to the same primary key (partition + sort key).<br><br>Multi-region tables are a different game. Your application should ensure that you do not write to the same primary key in two regions. Otherwise, the outcome is not predictable (&quot;last&quot; write wins).</td></tr></tbody></table><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>A mental model can help you to navigate in a complex system such as AWS. I hope you improved your mental model of AWS by reading this article. I’m interested in learning about your mental models. <a href="/meet-us-at-reinvent/">Meet me at re:Invent</a> or send me an <a href="mailto:&#x6d;&#105;&#99;&#104;&#x61;&#101;&#108;&#64;&#x77;&#x69;&#100;&#x64;&#105;&#120;&#x2e;&#100;&#101;">email</a> with your feedback.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Meet us at re:Invent (Update)</title>
      <link>https://cloudonaut.io/meet-us-at-reinvent/</link>
      <description>
        <![CDATA[<p>Michael and I are looking forward to attending re:Invent 2018 in Las Vegas. The most important reason for us to fly off to the other side]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/reinvent/">reinvent</category>
      <guid isPermaLink="true">https://cloudonaut.io/meet-us-at-reinvent/</guid>
      <pubDate>Thu, 08 Nov 2018 20:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Michael and I are looking forward to attending re:Invent 2018 in Las Vegas. The most important reason for us to fly off to the other side of the world is to meet old and new friends. Unfortunately, time is limited, and therefore we have already started scheduling meetings to be able to have as many interesting conversations as possible.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/11/lasvegas@730w.webp 730w, /images/2018/11/lasvegas@730w2x.webp 1460w, /images/2018/11/lasvegas@610w.webp 610w, /images/2018/11/lasvegas@610w2x.webp 1220w, /images/2018/11/lasvegas@450w.webp 450w, /images/2018/11/lasvegas@450w2x.webp 900w, /images/2018/11/lasvegas@330w.webp 330w, /images/2018/11/lasvegas@330w2x.webp 660w, /images/2018/11/lasvegas@545w.webp 545w, /images/2018/11/lasvegas@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/11/lasvegas@730w.jpg 730w, /images/2018/11/lasvegas@730w2x.jpg 1460w, /images/2018/11/lasvegas@610w.jpg 610w, /images/2018/11/lasvegas@610w2x.jpg 1220w, /images/2018/11/lasvegas@450w.jpg 450w, /images/2018/11/lasvegas@450w2x.jpg 900w, /images/2018/11/lasvegas@330w.jpg 330w, /images/2018/11/lasvegas@330w2x.jpg 660w, /images/2018/11/lasvegas@545w.jpg 545w, /images/2018/11/lasvegas@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/11/lasvegas.jpg" alt="Meet us at re:Invent in Las Vegas" title="Meet us at re:Invent in Las Vegas"></picture></p><h2 id="Personal-Meeting"><a href="#Personal-Meeting" class="headerlink" title="Personal Meeting"></a>Personal Meeting</h2><p>If you are asking yourself one of the following questions, or if you have found groundbreaking answers to one of these questions, please let us know. We want to talk to you!</p><ul><li><em>Monitoring and Incident Management</em> - How do you make sure you are not missing any failures or service degradations? Do you need more than CloudWatch to monitor your systems? How do you make sure the whole DevOps team is responding and fixing incidents in an agile environment?</li><li><em>AWS Bootstrapping</em> - What is the easiest and fastest way to build your cloud infrastructure from scratch? How do you achieve production-readiness?</li><li><em>Going Serverless</em> - Should I use AWS Lambda &amp; Co. for my specific use case? How does the development process change when building Serverless applications? What happens to operations in a Serverless world?</li></ul><p>Fill out our <a href="https://docs.google.com/forms/d/e/1FAIpQLSfOFcqWNtgF1L9EuTXlt7Kqs3ByYan2XylNqaIpvMo4disHGQ/viewform?usp=sf_link" target="_blank" rel="noopener">Meet us at re:Invent form</a> if you want to schedule a 20-minute meeting with either Michael or me, please.</p><h2 id="Meet-the-Authors"><a href="#Meet-the-Authors" class="headerlink" title="Meet the Authors"></a>Meet the Authors</h2><p>Are you a reader of our book or blog? We want to meet you in person and connect you with other readers as well. We’ll gather at the entrance of the congress center and enjoy the Pub Crawl together.</p><p><strong>When?</strong> Tuesday, November 27th, 06:00 pm PT<br><strong>Where?</strong> Starbucks next to The Sands Showroom, The Venetian</p><h2 id="AWS-Freelancer-Breakfast"><a href="#AWS-Freelancer-Breakfast" class="headerlink" title="AWS Freelancer Breakfast"></a>AWS Freelancer Breakfast</h2><p>Are you a freelancer focusing on AWS? Join us for breakfast with other freelancers to share your story and know-how.</p><p><strong>When?</strong> Tuesday, November 27th, 08:00 am PT<br><strong>Where?</strong> Grand Lux Cafe, The Venetian</p><p><em>Be aware that there is a 2nd Grand Lux Cafe at The Palazzo as well.</em></p><h2 id="AWS-Community-Open-Source-Coffee"><a href="#AWS-Community-Open-Source-Coffee" class="headerlink" title="AWS Community Open Source Coffee"></a>AWS Community Open Source Coffee</h2><p>Are you maintaining or contributing to a community-lead open sources project focusing on AWS? Meet other maintainers or contributors for a coffee and introduce your project.</p><p><strong>When?</strong> Wednesday, November 28th, 03:00 pm PT<br><strong>Where?</strong> Coffee bar next to AWS Certification Center, Casanova 1st Floor, The Venetian</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Three simple rules to avoid data leaking from S3</title>
      <link>https://cloudonaut.io/three-simple-rules-to-avoid-data-leaking-from-s3/</link>
      <description>
        <![CDATA[<p>Reviewing AWS accounts with a focus on security is part of my day-to-day job. My most common finding: unwanted public read or write acces]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <guid isPermaLink="true">https://cloudonaut.io/three-simple-rules-to-avoid-data-leaking-from-s3/</guid>
      <pubDate>Tue, 30 Oct 2018 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Reviewing AWS accounts with a focus on security is part of my day-to-day job. My most common finding: unwanted public read or write access to S3 buckets. Why is that? Because there are three different ways to define who can access your S3 buckets: IAM policy, bucket policy, and bucket or object ACLs. It is very easy to get confused and find your personal data leak story on the news later (see <a href="https://www.upguard.com/breaches/out-of-pocket-how-an-isp-exposed-administrative-system-credentials" target="_blank" rel="noopener">Out of Pocket: How an ISP Exposed Administrative System Credentials</a>, for example).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/10/fence@730w.webp 730w, /images/2018/10/fence@730w2x.webp 1460w, /images/2018/10/fence@610w.webp 610w, /images/2018/10/fence@610w2x.webp 1220w, /images/2018/10/fence@450w.webp 450w, /images/2018/10/fence@450w2x.webp 900w, /images/2018/10/fence@330w.webp 330w, /images/2018/10/fence@330w2x.webp 660w, /images/2018/10/fence@545w.webp 545w, /images/2018/10/fence@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/10/fence@730w.jpg 730w, /images/2018/10/fence@730w2x.jpg 1460w, /images/2018/10/fence@610w.jpg 610w, /images/2018/10/fence@610w2x.jpg 1220w, /images/2018/10/fence@450w.jpg 450w, /images/2018/10/fence@450w2x.jpg 900w, /images/2018/10/fence@330w.jpg 330w, /images/2018/10/fence@330w2x.jpg 660w, /images/2018/10/fence@545w.jpg 545w, /images/2018/10/fence@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/10/fence.jpg" alt="Three simple rules to avoid data leaking from S3" title="Three simple rules to avoid data leaking from S3"></picture></p><p>Make sure to avoid granting unauthenticated access to your S3 buckets by following three simple rules:</p><ol><li>Use an IAM policy to grant access to your S3 bucket whenever the caller is able to authenticate as IAM user or role.</li><li>When using an IAM policy is not an option, use a bucket policy instead.</li><li>Never use bucket or object ACLs unless you have double checked your argument to skip the previous rules with either AWS support or me.</li></ol><h2 id="IAM-Policy"><a href="#IAM-Policy" class="headerlink" title="IAM Policy"></a>IAM Policy</h2><p>Do you need to grant access to your S3 buckets to an application running on an EC2 instance, a Lambda function, or any other AWS resource that uses IAM roles to authenticate? In this case, attach an IAM policy to the appropriate role. Typical use cases for this scenario are: your web application running on EC2 stores user-generated content on S3, your Lambda function reads data from S3, or you are using IAM roles for cross-account access.</p><p>Whenever, it is not possible to use an IAM role to authenticate requests to S3, use an IAM user instead. Typically, you need to download the AWS Credentials for the IAM user and configure the application to use these credentials for authentication. Typical use cases for this scenario are your backup solution which uploads data to S3, your third-party browsers&#x2F;clients for S3, your AWS CLI running on your local machine.</p><p>For example, the following IAM policy grants read access to the S3 bucket <code>cloudonaut</code>.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2012-10-17&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Statement&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Action&quot;</span>: <span class="string">&quot;s3:ListBucket&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;arn:aws:s3:::cloudonaut&quot;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Action&quot;</span>: <span class="string">&quot;s3:GetObject&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;arn:aws:s3:::cloudonaut/*&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Bucket-Policy"><a href="#Bucket-Policy" class="headerlink" title="Bucket Policy"></a>Bucket Policy</h2><p>Use a bucket policy only when the caller is not able to authenticate via IAM - neither IAM role nor IAM user.</p><p>Use a bucket policy when you need to grant access to your S3 bucket to an AWS service that does not support IAM roles. For example, use the following bucket policy to allow the ELB within region <code>eu-west-1</code> to store access logs within your S3 bucket.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2012-10-17&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Statement&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;Action&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;s3:PutObject&quot;</span></span><br><span class="line">      ],</span><br><span class="line">      <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;arn:aws:s3:::cloudonaut-alb-logs/AWSLogs/111111111111/*&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Principal&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;AWS&quot;</span>: [</span><br><span class="line">          <span class="string">&quot;156460612806&quot;</span></span><br><span class="line">        ]</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Furthermore, allow CloudFront to read assets from your S3 bucket with the following policy.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2012-10-17&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Statement&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Principal&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;AWS&quot;</span>: <span class="string">&quot;arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity XYZ&quot;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="string">&quot;Action&quot;</span>: <span class="string">&quot;s3:GetObject&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;arn:aws:s3:::cloudonaut/*&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The bucket policy is also the way to go when you want to enable unauthenticated access to your S3 bucket. However, that is out of scope for this post. </p><h2 id="Bucket-Object-ACLs"><a href="#Bucket-Object-ACLs" class="headerlink" title="Bucket&#x2F;Object ACLs"></a>Bucket&#x2F;Object ACLs</h2><p>Use neither bucket nor object ACLs to control access to your S3 bucket. Typically, an IAM or bucket policy fits 99% of all scenarios. Did you find a valid use case for a bucket or object ACLs? Feel free to double check your argument with the AWS support or me.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/10/s3-acl@730w.webp 730w, /images/2018/10/s3-acl@730w2x.webp 1460w, /images/2018/10/s3-acl@610w.webp 610w, /images/2018/10/s3-acl@610w2x.webp 1220w, /images/2018/10/s3-acl@450w.webp 450w, /images/2018/10/s3-acl@450w2x.webp 900w, /images/2018/10/s3-acl@330w.webp 330w, /images/2018/10/s3-acl@330w2x.webp 660w, /images/2018/10/s3-acl@545w.webp 545w, /images/2018/10/s3-acl@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/10/s3-acl@730w.png 730w, /images/2018/10/s3-acl@730w2x.png 1460w, /images/2018/10/s3-acl@610w.png 610w, /images/2018/10/s3-acl@610w2x.png 1220w, /images/2018/10/s3-acl@450w.png 450w, /images/2018/10/s3-acl@450w2x.png 900w, /images/2018/10/s3-acl@330w.png 330w, /images/2018/10/s3-acl@330w2x.png 660w, /images/2018/10/s3-acl@545w.png 545w, /images/2018/10/s3-acl@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/10/s3-acl.png" alt="Avoid using a bucket and object ACLs" title="Avoid using a bucket and object ACLs"></picture></p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>To grant access to your S3 buckets use an IAM policy whenever possible. Use a bucket policy as an alternative. However, stay away from bucket and object ACLs.</p><p>One more thing. When using an IAM policy to control access to your S3 buckets, make sure you are following the least privilege principle. Only allow read or write access to the buckets or even prefixes that are absolutely needed.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Customized rate limiting for API Gateway by path parameter, query parameter, and more</title>
      <link>https://cloudonaut.io/customized-rate-limiting-for-api-gateway-by-path-parameter-query-parameter-and-more/</link>
      <description>
        <![CDATA[<p>API Gateway provides a feature to limit the number of requests a client can make per second (rate) and per day&#x2F;week&#x2F;month (quot]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/apigateway/">apigateway</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/customized-rate-limiting-for-api-gateway-by-path-parameter-query-parameter-and-more/</guid>
      <pubDate>Wed, 17 Oct 2018 13:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>API Gateway provides a feature to limit the number of requests a client can make per second (rate) and per day&#x2F;week&#x2F;month (quota). Rate limiting is very useful to protect your system from resource starvation caused by a client flooding your system with requests. Quotas are more useful to protect against data scrapers or to limit the number of expensive operations a client can perform without paying appropriately.</p><p>You have to combine two features of API Gateway to implement rate limiting: Usage plans and API keys. API keys are used to identify the client while a usage plan defines the rate limit for a set of API keys and tracks their usage. Clients are expected to send the API key as the HTTP <code>X-API-Key</code> header. However, what about using a path parameter to identify the client like <code>/v1/some-client-id/task</code> or a query parameter like <code>/v1/task?clientId=some-client-id</code>? In this article, you learn to use a Custom Authorizer to extract a parameter from the path and use the value as the API Key. You can follow the same approach to extract the API Key value from a query parameter or the body.</p><blockquote><p>Keep in mind that there is a soft limit of 500 API keys. AWS will not raise this limit as high as you wish. The upper limit seems to be 10,000 API keys.</p></blockquote><h2 id="Custom-Authorizer"><a href="#Custom-Authorizer" class="headerlink" title="Custom Authorizer"></a>Custom Authorizer</h2><p>A Custom Authorizer is implemented by a Lambda function to execute custom logic. Every request to the API Gateway first invokes the Custom Authorizer. The Custom Authorizer returns an access policy (<code>policyDocument</code>) and the API key value (<code>usageIdentifierKey</code>). API Gateway takes the result from the Custom Authorizer, checks if the API key exists and if the client is allowed to make the request according to the access policy.</p><p>Create the Lambda function:</p><ol><li>Author a Lambda function from scratch<br><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/10/lambda-authorizer@730w.webp 730w, /images/2018/10/lambda-authorizer@730w2x.webp 1460w, /images/2018/10/lambda-authorizer@610w.webp 610w, /images/2018/10/lambda-authorizer@610w2x.webp 1220w, /images/2018/10/lambda-authorizer@450w.webp 450w, /images/2018/10/lambda-authorizer@450w2x.webp 900w, /images/2018/10/lambda-authorizer@330w.webp 330w, /images/2018/10/lambda-authorizer@330w2x.webp 660w, /images/2018/10/lambda-authorizer@545w.webp 545w, /images/2018/10/lambda-authorizer@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/10/lambda-authorizer@730w.png 730w, /images/2018/10/lambda-authorizer@730w2x.png 1460w, /images/2018/10/lambda-authorizer@610w.png 610w, /images/2018/10/lambda-authorizer@610w2x.png 1220w, /images/2018/10/lambda-authorizer@450w.png 450w, /images/2018/10/lambda-authorizer@450w2x.png 900w, /images/2018/10/lambda-authorizer@330w.png 330w, /images/2018/10/lambda-authorizer@330w2x.png 660w, /images/2018/10/lambda-authorizer@545w.png 545w, /images/2018/10/lambda-authorizer@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/10/lambda-authorizer.png" alt="Lambda function from scratch" title="Lambda function from scratch"></picture></li><li>Set a <strong>Name</strong></li><li>Set the <strong>Runtime</strong> to <code>Node.js 8.10</code></li><li>Set <strong>Role</strong> to <code>Create a new role from one or more templates</code> to create a new IAM role for the Lambda function</li><li>Set a <strong>Role Name</strong></li><li>Click <strong>Create function</strong> to save</li></ol><p>The following Lambda function code uses the value of the path parameter <code>clientId</code> as the API key value and allows access to all API resources and methods.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="built_in">exports</span>.<span class="property">handler</span> = <span class="title function_">async</span> (event) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="string">&#x27;pathParameters&#x27;</span> <span class="keyword">in</span> event &amp;&amp; <span class="string">&#x27;clientId&#x27;</span> <span class="keyword">in</span> event.<span class="property">pathParameters</span>) &#123; <span class="comment">// check if the path parameter is present</span></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">principalId</span>: event.<span class="property">pathParameters</span>.<span class="property">clientId</span>,</span><br><span class="line">      <span class="attr">policyDocument</span>: &#123;</span><br><span class="line">        <span class="title class_">Version</span>: <span class="string">&#x27;2012-10-17&#x27;</span>,</span><br><span class="line">        <span class="title class_">Statement</span>: [&#123; <span class="comment">// allow all HTTP methods on all resources</span></span><br><span class="line">          <span class="title class_">Action</span>: <span class="string">&#x27;execute-api:Invoke&#x27;</span>,</span><br><span class="line">          <span class="title class_">Effect</span>: <span class="string">&#x27;Allow&#x27;</span>, </span><br><span class="line">          <span class="title class_">Resource</span>: <span class="string">`arn:aws:execute-api:us-east-1:*:<span class="subst">$&#123;event.requestContext.apiId&#125;</span>/*`</span></span><br><span class="line">        &#125;]</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">usageIdentifierKey</span>: event.<span class="property">pathParameters</span>.<span class="property">clientId</span> <span class="comment">// the value of path parameter clientId is used as the API key value</span></span><br><span class="line">    &#125;;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;path parameter clientId missing&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>Once the Lambda function is in place you can create the Custom Authorizer in API Gateway:</p><ol><li>Set a <strong>Name</strong></li><li>Select the <strong>Lambda Function</strong> you created earlier<br><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/10/api-gateway-authorizer@730w.webp 730w, /images/2018/10/api-gateway-authorizer@730w2x.webp 1460w, /images/2018/10/api-gateway-authorizer@610w.webp 610w, /images/2018/10/api-gateway-authorizer@610w2x.webp 1220w, /images/2018/10/api-gateway-authorizer@450w.webp 450w, /images/2018/10/api-gateway-authorizer@450w2x.webp 900w, /images/2018/10/api-gateway-authorizer@330w.webp 330w, /images/2018/10/api-gateway-authorizer@330w2x.webp 660w, /images/2018/10/api-gateway-authorizer@545w.webp 545w, /images/2018/10/api-gateway-authorizer@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/10/api-gateway-authorizer@730w.png 730w, /images/2018/10/api-gateway-authorizer@730w2x.png 1460w, /images/2018/10/api-gateway-authorizer@610w.png 610w, /images/2018/10/api-gateway-authorizer@610w2x.png 1220w, /images/2018/10/api-gateway-authorizer@450w.png 450w, /images/2018/10/api-gateway-authorizer@450w2x.png 900w, /images/2018/10/api-gateway-authorizer@330w.png 330w, /images/2018/10/api-gateway-authorizer@330w2x.png 660w, /images/2018/10/api-gateway-authorizer@545w.png 545w, /images/2018/10/api-gateway-authorizer@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/10/api-gateway-authorizer.png" alt="Create API Gateway custom authorizer" title="Create API Gateway custom authorizer"></picture></li><li>Set the <strong>Lambda Event Payload</strong> to <code>Request</code></li><li>Set the <strong>Identity Sources</strong> to <code>Context apiId</code></li><li>Disable <strong>Authorization Caching</strong></li><li>Click <strong>Create</strong> to save</li><li>You are asked to grant permissions<br><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/10/api-gateway-authorizer-permission@730w.webp 730w, /images/2018/10/api-gateway-authorizer-permission@730w2x.webp 1460w, /images/2018/10/api-gateway-authorizer-permission@610w.webp 610w, /images/2018/10/api-gateway-authorizer-permission@610w2x.webp 1220w, /images/2018/10/api-gateway-authorizer-permission@450w.webp 450w, /images/2018/10/api-gateway-authorizer-permission@450w2x.webp 900w, /images/2018/10/api-gateway-authorizer-permission@330w.webp 330w, /images/2018/10/api-gateway-authorizer-permission@330w2x.webp 660w, /images/2018/10/api-gateway-authorizer-permission@545w.webp 545w, /images/2018/10/api-gateway-authorizer-permission@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/10/api-gateway-authorizer-permission@730w.png 730w, /images/2018/10/api-gateway-authorizer-permission@730w2x.png 1460w, /images/2018/10/api-gateway-authorizer-permission@610w.png 610w, /images/2018/10/api-gateway-authorizer-permission@610w2x.png 1220w, /images/2018/10/api-gateway-authorizer-permission@450w.png 450w, /images/2018/10/api-gateway-authorizer-permission@450w2x.png 900w, /images/2018/10/api-gateway-authorizer-permission@330w.png 330w, /images/2018/10/api-gateway-authorizer-permission@330w2x.png 660w, /images/2018/10/api-gateway-authorizer-permission@545w.png 545w, /images/2018/10/api-gateway-authorizer-permission@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/10/api-gateway-authorizer-permission.png" alt="Create API Gateway Custom Authorizer" title="Create API Gateway Custom Authorizer"></picture></li></ol><blockquote><p>Unfortunately, you can not use a path parameter as an Identity Source. We use the API id which is the same for all requests. Therefore, it is important to disable Authorization Caching. Otherwise, all requests (no matter what path parameter used) would use the same API key value from the cached authorizer response. Confusingly, a query parameter is valid Identity Source.</p></blockquote><h2 id="Configuring-API-Gateway"><a href="#Configuring-API-Gateway" class="headerlink" title="Configuring API Gateway"></a>Configuring API Gateway</h2><p>By default, API Gateway does not use the <code>usageIdentifierKey</code> value of the Custom Authorizer. You have to turn this behavior on explicitly.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/10/api-gateway-settings@730w.webp 730w, /images/2018/10/api-gateway-settings@730w2x.webp 1460w, /images/2018/10/api-gateway-settings@610w.webp 610w, /images/2018/10/api-gateway-settings@610w2x.webp 1220w, /images/2018/10/api-gateway-settings@450w.webp 450w, /images/2018/10/api-gateway-settings@450w2x.webp 900w, /images/2018/10/api-gateway-settings@330w.webp 330w, /images/2018/10/api-gateway-settings@330w2x.webp 660w, /images/2018/10/api-gateway-settings@545w.webp 545w, /images/2018/10/api-gateway-settings@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/10/api-gateway-settings@730w.png 730w, /images/2018/10/api-gateway-settings@730w2x.png 1460w, /images/2018/10/api-gateway-settings@610w.png 610w, /images/2018/10/api-gateway-settings@610w2x.png 1220w, /images/2018/10/api-gateway-settings@450w.png 450w, /images/2018/10/api-gateway-settings@450w2x.png 900w, /images/2018/10/api-gateway-settings@330w.png 330w, /images/2018/10/api-gateway-settings@330w2x.png 660w, /images/2018/10/api-gateway-settings@545w.png 545w, /images/2018/10/api-gateway-settings@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/10/api-gateway-settings.png" alt="Configure API Gateway to use the Custom Authorizer to return the API key value" title="Configure API Gateway to use the Custom Authorizer to return the API key value"></picture></p><h2 id="Protecting-a-resource"><a href="#Protecting-a-resource" class="headerlink" title="Protecting a resource"></a>Protecting a resource</h2><p>To protect a resource with a Custom Authorizer, you have to configure the Method Request.</p><ol><li>Ensure that you have the path parameter <code>clientId</code> in the path of the resource<br><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/10/api-gateway-resource@730w.webp 730w, /images/2018/10/api-gateway-resource@730w2x.webp 1460w, /images/2018/10/api-gateway-resource@610w.webp 610w, /images/2018/10/api-gateway-resource@610w2x.webp 1220w, /images/2018/10/api-gateway-resource@450w.webp 450w, /images/2018/10/api-gateway-resource@450w2x.webp 900w, /images/2018/10/api-gateway-resource@330w.webp 330w, /images/2018/10/api-gateway-resource@330w2x.webp 660w, /images/2018/10/api-gateway-resource@545w.webp 545w, /images/2018/10/api-gateway-resource@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/10/api-gateway-resource@730w.png 730w, /images/2018/10/api-gateway-resource@730w2x.png 1460w, /images/2018/10/api-gateway-resource@610w.png 610w, /images/2018/10/api-gateway-resource@610w2x.png 1220w, /images/2018/10/api-gateway-resource@450w.png 450w, /images/2018/10/api-gateway-resource@450w2x.png 900w, /images/2018/10/api-gateway-resource@330w.png 330w, /images/2018/10/api-gateway-resource@330w2x.png 660w, /images/2018/10/api-gateway-resource@545w.png 545w, /images/2018/10/api-gateway-resource@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/10/api-gateway-resource.png" alt="Protecting a resource" title="Protecting a resource"></picture></li><li>Set <strong>Authorization</strong> to the authorizer you created</li><li>Set <strong>API Key Required</strong> to <code>true</code></li></ol><p>If you send a <code>GET</code> request to the resource <code>/550e8400-e29b-11d4-a716-446655440000/task</code>, you get a <code>403</code> error with the message <code>Forbidden</code>. The problem: The API Key with the value <code>550e8400-e29b-11d4-a716-446655440000</code> does not exist. Let’s change that.</p><h2 id="Creating-an-API-Key"><a href="#Creating-an-API-Key" class="headerlink" title="Creating an API Key"></a>Creating an API Key</h2><p>To create an API Key:</p><ol><li>In the sub-navigation, click on <strong>API Keys</strong><br><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/10/api-gateway-api-key@730w.webp 730w, /images/2018/10/api-gateway-api-key@730w2x.webp 1460w, /images/2018/10/api-gateway-api-key@610w.webp 610w, /images/2018/10/api-gateway-api-key@610w2x.webp 1220w, /images/2018/10/api-gateway-api-key@450w.webp 450w, /images/2018/10/api-gateway-api-key@450w2x.webp 900w, /images/2018/10/api-gateway-api-key@330w.webp 330w, /images/2018/10/api-gateway-api-key@330w2x.webp 660w, /images/2018/10/api-gateway-api-key@545w.webp 545w, /images/2018/10/api-gateway-api-key@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/10/api-gateway-api-key@730w.png 730w, /images/2018/10/api-gateway-api-key@730w2x.png 1460w, /images/2018/10/api-gateway-api-key@610w.png 610w, /images/2018/10/api-gateway-api-key@610w2x.png 1220w, /images/2018/10/api-gateway-api-key@450w.png 450w, /images/2018/10/api-gateway-api-key@450w2x.png 900w, /images/2018/10/api-gateway-api-key@330w.png 330w, /images/2018/10/api-gateway-api-key@330w2x.png 660w, /images/2018/10/api-gateway-api-key@545w.png 545w, /images/2018/10/api-gateway-api-key@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/10/api-gateway-api-key.png" alt="Creating an API Key" title="Creating an API Key"></picture></li><li>Push the <strong>Actions</strong> button and click on <strong>Create API key</strong></li><li>Set a <strong>Name</strong></li><li>Select <strong>API key</strong> to <code>Custom</code> and provide the value that you want to use for the path parameter <code>clientId</code> (e.g., <code>550e8400-e29b-11d4-a716-446655440000</code>)</li><li>Click <strong>Save</strong></li></ol><p>The HTTP request still doesn’t work. You have to assign the API Key with a Usage plan.</p><p>To create a Usage Plan:</p><ol><li>In the sub-navigation, click on <strong>Usage Plans</strong><br><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/10/api-gateway-usage-plan1@730w.webp 730w, /images/2018/10/api-gateway-usage-plan1@730w2x.webp 1460w, /images/2018/10/api-gateway-usage-plan1@610w.webp 610w, /images/2018/10/api-gateway-usage-plan1@610w2x.webp 1220w, /images/2018/10/api-gateway-usage-plan1@450w.webp 450w, /images/2018/10/api-gateway-usage-plan1@450w2x.webp 900w, /images/2018/10/api-gateway-usage-plan1@330w.webp 330w, /images/2018/10/api-gateway-usage-plan1@330w2x.webp 660w, /images/2018/10/api-gateway-usage-plan1@545w.webp 545w, /images/2018/10/api-gateway-usage-plan1@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/10/api-gateway-usage-plan1@730w.png 730w, /images/2018/10/api-gateway-usage-plan1@730w2x.png 1460w, /images/2018/10/api-gateway-usage-plan1@610w.png 610w, /images/2018/10/api-gateway-usage-plan1@610w2x.png 1220w, /images/2018/10/api-gateway-usage-plan1@450w.png 450w, /images/2018/10/api-gateway-usage-plan1@450w2x.png 900w, /images/2018/10/api-gateway-usage-plan1@330w.png 330w, /images/2018/10/api-gateway-usage-plan1@330w2x.png 660w, /images/2018/10/api-gateway-usage-plan1@545w.png 545w, /images/2018/10/api-gateway-usage-plan1@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/10/api-gateway-usage-plan1.png" alt="Creating a Usage Plan: Step 1/3" title="Creating a Usage Plan: Step 1/3"></picture></li><li>Push the <strong>Create</strong> button</li><li>Set a <strong>Name</strong></li><li>Imagine that each API Key can save future requests to an account. The <strong>Rate</strong> determines how many future requests are saved per second.</li><li>The <strong>Burst</strong> value determines the maximum amount of future requests that an API Key can save. In other words: In the long run, an API Key cannot make more than <strong>Rate</strong> requests per second. However, short term, an API Key can make <strong>Burst</strong> requests per second.</li><li>Disable quota</li><li>Click <strong>Next</strong> to go to the next step</li><li>Click <strong>Add API Stage</strong><br><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/10/api-gateway-usage-plan2@730w.webp 730w, /images/2018/10/api-gateway-usage-plan2@730w2x.webp 1460w, /images/2018/10/api-gateway-usage-plan2@610w.webp 610w, /images/2018/10/api-gateway-usage-plan2@610w2x.webp 1220w, /images/2018/10/api-gateway-usage-plan2@450w.webp 450w, /images/2018/10/api-gateway-usage-plan2@450w2x.webp 900w, /images/2018/10/api-gateway-usage-plan2@330w.webp 330w, /images/2018/10/api-gateway-usage-plan2@330w2x.webp 660w, /images/2018/10/api-gateway-usage-plan2@545w.webp 545w, /images/2018/10/api-gateway-usage-plan2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/10/api-gateway-usage-plan2@730w.png 730w, /images/2018/10/api-gateway-usage-plan2@730w2x.png 1460w, /images/2018/10/api-gateway-usage-plan2@610w.png 610w, /images/2018/10/api-gateway-usage-plan2@610w2x.png 1220w, /images/2018/10/api-gateway-usage-plan2@450w.png 450w, /images/2018/10/api-gateway-usage-plan2@450w2x.png 900w, /images/2018/10/api-gateway-usage-plan2@330w.png 330w, /images/2018/10/api-gateway-usage-plan2@330w2x.png 660w, /images/2018/10/api-gateway-usage-plan2@545w.png 545w, /images/2018/10/api-gateway-usage-plan2@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/10/api-gateway-usage-plan2.png" alt="Creating a Usage Plan: Step 2/3" title="Creating a Usage Plan: Step 2/3"></picture></li><li>Select the <strong>API</strong> and <strong>Stage</strong></li><li>Click on the OK icon to save and <strong>Next</strong> to go to the next step</li><li>Click on <strong>Add API Key to Usage Plan</strong><br><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/10/api-gateway-usage-plan3@730w.webp 730w, /images/2018/10/api-gateway-usage-plan3@730w2x.webp 1460w, /images/2018/10/api-gateway-usage-plan3@610w.webp 610w, /images/2018/10/api-gateway-usage-plan3@610w2x.webp 1220w, /images/2018/10/api-gateway-usage-plan3@450w.webp 450w, /images/2018/10/api-gateway-usage-plan3@450w2x.webp 900w, /images/2018/10/api-gateway-usage-plan3@330w.webp 330w, /images/2018/10/api-gateway-usage-plan3@330w2x.webp 660w, /images/2018/10/api-gateway-usage-plan3@545w.webp 545w, /images/2018/10/api-gateway-usage-plan3@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/10/api-gateway-usage-plan3@730w.png 730w, /images/2018/10/api-gateway-usage-plan3@730w2x.png 1460w, /images/2018/10/api-gateway-usage-plan3@610w.png 610w, /images/2018/10/api-gateway-usage-plan3@610w2x.png 1220w, /images/2018/10/api-gateway-usage-plan3@450w.png 450w, /images/2018/10/api-gateway-usage-plan3@450w2x.png 900w, /images/2018/10/api-gateway-usage-plan3@330w.png 330w, /images/2018/10/api-gateway-usage-plan3@330w2x.png 660w, /images/2018/10/api-gateway-usage-plan3@545w.png 545w, /images/2018/10/api-gateway-usage-plan3@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/10/api-gateway-usage-plan3.png" alt="Creating a Usage Plan: Step 3/3" title="Creating a Usage Plan: Step 3/3"></picture></li><li>Select the <strong>API Key</strong></li><li>Click on the OK icon to save and <strong>Done</strong> to finish</li></ol><p>Now, if you send a <code>GET</code> request to the resource <code>/550e8400-e29b-11d4-a716-446655440000/task</code>, you get a <code>200</code> success. If you send more than 1 request per second for some time, you get a <code>429</code> error with the message <code>Too Many Requests</code>. Just wait a few seconds, and you can continue with a new request that works.</p><h2 id="Pitfalls"><a href="#Pitfalls" class="headerlink" title="Pitfalls"></a>Pitfalls</h2><ol><li>You have to keep the API Keys on sync with your client database. Whenever you add a new client, don’t forget to create an API Key with the correct Usage Plan mapping as well.</li><li>An API Key has a name, id, and value. The user of your API provides the API Key value while API Gateway cares about the id (e.g., to delete an API Key).</li><li>If you run multiple APIs in a single region, make sure that your API Key values do not overlap. They all share the same namespace.</li><li>SNS HTTPS subscriptions do not retry on 429. Only on 5XX responses. If you send requests to an API Gateway using SNS, that’s a problem. Workaround: You can configure API Gateway to return 5XX errors instead of 429 (which might or might not be a solution).</li></ol><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>You can configure API Gateway to rate limit requests based on a parameter in the path with some tricks. The benefit of Usage Plans is that API Gateway itself protects your system without involving any datastore from your end. You don’t have to worry about an overloaded rate limiting system.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>EC2 Network Performance of t3</title>
      <link>https://cloudonaut.io/ec2-network-performance-of-t3/</link>
      <description>
        <![CDATA[<p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/10/ec2-network-performance-t3@730w.webp 730w, /images/2018/10/e]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <guid isPermaLink="true">https://cloudonaut.io/ec2-network-performance-of-t3/</guid>
      <pubDate>Fri, 12 Oct 2018 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/10/ec2-network-performance-t3@730w.webp 730w, /images/2018/10/ec2-network-performance-t3@730w2x.webp 1460w, /images/2018/10/ec2-network-performance-t3@610w.webp 610w, /images/2018/10/ec2-network-performance-t3@610w2x.webp 1220w, /images/2018/10/ec2-network-performance-t3@450w.webp 450w, /images/2018/10/ec2-network-performance-t3@450w2x.webp 900w, /images/2018/10/ec2-network-performance-t3@330w.webp 330w, /images/2018/10/ec2-network-performance-t3@330w2x.webp 660w, /images/2018/10/ec2-network-performance-t3@545w.webp 545w, /images/2018/10/ec2-network-performance-t3@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/10/ec2-network-performance-t3@730w.png 730w, /images/2018/10/ec2-network-performance-t3@730w2x.png 1460w, /images/2018/10/ec2-network-performance-t3@610w.png 610w, /images/2018/10/ec2-network-performance-t3@610w2x.png 1220w, /images/2018/10/ec2-network-performance-t3@450w.png 450w, /images/2018/10/ec2-network-performance-t3@450w2x.png 900w, /images/2018/10/ec2-network-performance-t3@330w.png 330w, /images/2018/10/ec2-network-performance-t3@330w2x.png 660w, /images/2018/10/ec2-network-performance-t3@545w.png 545w, /images/2018/10/ec2-network-performance-t3@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/10/ec2-network-performance-t3.png" alt="EC2 Network Performance t3" title="EC2 Network Performance t3"></picture></p><p>AWS <a href="https://aws.amazon.com/about-aws/whats-new/2018/08/introducing-amazon-ec2-t3-instances/" target="_blank" rel="noopener">introduced the <code>t3</code> instance type</a> in August. Time to update the <a href="/ec2-network-performance-cheat-sheet/">EC2 Network Performance Cheat Sheet</a>. The following table shows the network capabilities of <code>t3</code> instances. Compared to the last generation <code>t2</code> there is no difference in the baseline throughput. However, the burst throughput is more than 5 times higher compared to <code>t2</code> instance types.</p><table class="table">  <thead>    <tr>      <th>INSTANCE TYPE</th>      <th class="text-right">Baseline (Gbit/s)</th>      <th class="text-right">Burst (Gbit/s)</th>    </tr>  </thead>  <tbody>    <tr>      <td>t3.nano</td>      <td class="text-right">0.03</td>      <td class="text-right">5.06</td>    </tr>    <tr>      <td>t3.micro</td>      <td class="text-right">0.06</td>      <td class="text-right">5.09</td>    </tr>    <tr>      <td>t3.small</td>      <td class="text-right">0.13</td>      <td class="text-right">5.11</td>    </tr>    <tr>      <td>t3.medium</td>      <td class="text-right">0.25</td>      <td class="text-right">4.98</td>    </tr>    <tr>      <td>t3.large</td>      <td class="text-right">0.51</td>      <td class="text-right">5.11</td>    </tr>    <tr>      <td>t3.xlarge</td>      <td class="text-right">1.02</td>      <td class="text-right">5.11</td>    </tr>    <tr>      <td>t3.2xlarge</td>      <td class="text-right">2.04</td>      <td class="text-right">5.11</td>    </tr>  </tbody></table><p>Looking for other instance types? See <a href="/ec2-network-performance-cheat-sheet/">EC2 Network Performance Cheat Sheet</a>.</p><h2 id="Methodology"><a href="#Methodology" class="headerlink" title="Methodology"></a>Methodology</h2><p>I have run the network performance benchmark in <code>us-east-1</code> on September 25th, 2018 using <code>iperf3</code>. Each benchmark run took 60 minutes, and a data point was recorded every minute. A fresh <code>c5.18xlarge</code> was used as the counterparty for the network benchmark of each instance type. I have published the source code of the network benchmark at <a href="https://github.com/widdix/ec2-network-benchmark" target="_blank" rel="noopener">widdix&#x2F;ec2-network-benchmark</a>.</p><h2 id="Explanation"><a href="#Explanation" class="headerlink" title="Explanation"></a>Explanation</h2><p>The data analysis in the table shows the burst and baseline throughput of each instance type. The burst throughput is defined as the 95th percentile, which means the throughput was at least reached for 3 minutes. The baseline throughput is defined as the 10th percentile, which means the throughput was reached at least for 54 minutes.</p><h2 id="Disclaimer"><a href="#Disclaimer" class="headerlink" title="Disclaimer"></a>Disclaimer</h2><p>Please note, that the network benchmark was just a random sample and does not claim to be 100% accurate. Therefore, you should treat the EC2 Network Performance Cheat Sheet as the first guide when choosing an instance type based on your network throughput needs. If network throughput is critical to your infrastructure, you should run network benchmarks yourself to verify my results.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Hot off the press: Amazon Web Services in Action Second Edition</title>
      <link>https://cloudonaut.io/hot-off-the-press-amazon-web-services-in-action-second-edition/</link>
      <description>
        <![CDATA[<p>We wrote and published the first edition of Amazon Web Services in Action in 2015. Our book quickly became a bestseller. We are still ama]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/hot-off-the-press-amazon-web-services-in-action-second-edition/</guid>
      <pubDate>Tue, 02 Oct 2018 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>We wrote and published the first edition of Amazon Web Services in Action in 2015. Our book quickly became a bestseller. We are still amazed by the positive feedback we receive from our readers every day. Thanks for that!</p><p>Nevertheless, AWS is changing fast and announces new features every week. So we got back to the keyboard and started updating and extending our book.</p><p>Today, we are happy to announce that the <a href="http://bit.ly/amazon-web-services-in-action-2nd-edition" target="_blank" rel="noopener">2nd edition of AWS in Action</a> is available as printed book and ebook (PDF). Other ebook formats will follow shortly. By the way, if you buy the printed book, you’ll get access to the ebook for free as well.</p><p align="center"><img src="/images/2018/09/aws-in-action-2nd.png" alt="Amazon Web Services in Action Second Edition" style="max-width: 300px"></p><p><strong>Buy <a href="http://bit.ly/amazon-web-services-in-action-2nd-edition" target="_blank" rel="noopener">Amazon Web Services in Action Second Edition</a> and use promo code <code>nlaws2e</code> to get 20% off now!</strong></p><h2 id="What-is-the-focus-of-the-book"><a href="#What-is-the-focus-of-the-book" class="headerlink" title="What is the focus of the book?"></a>What is the focus of the book?</h2><p>You will learn about the fundamental parts and concepts of AWS when reading our book. Our book covers virtual machines (EC2), storage systems (EBS, EFS, S3), databases (RDS, DynamoDB), networking (VPC). On top of that, you will learn how to deploy your applications to AWS by using Elastic Beanstalk, OpsWorks or CloudFormation.</p><p>We strongly believe in the power of automating every piece of managing your cloud infrastructure. In our opinion, automation helps you to increases efficiency, quality, and conformity in an unprecedented way. That is why we are introducing CloudFormation, allowing you to manage your infrastructure as code, in the first part of our book. Also, most of our examples are using CloudFormation to deploy the needed resources. Consequently, after reading our book and working through the examples, you will be able to automate your cloud infrastructure with CloudFormation.</p><p>Besides automation, we also focus on scalable, highly available, and fault tolerant architectures (ELB, SQS, Auto Scaling). And last but not least, you will learn about how to secure each part of your cloud infrastructure (IAM, Security Groups, …).</p><h2 id="Which-topics-are-out-of-scope"><a href="#Which-topics-are-out-of-scope" class="headerlink" title="Which topics are out of scope?"></a>Which topics are out of scope?</h2><p>The service portfolio AWS is offering is enormous. We love to build with all the building blocks AWS is offering, from virtual machines to big data analytics, containers, and machine learning. Unfortunately, the maximum size of a book is limited. Therefore, we had to skip some exciting topics entirely. For example, we are not covering containers, big data, machine learning, mobile applications at all. If you are interested in these topics, please let us know to motivate us to write a separate book on that.</p><h2 id="What-changed-in-the-2nd-edition"><a href="#What-changed-in-the-2nd-edition" class="headerlink" title="What changed in the 2nd edition?"></a>What changed in the 2nd edition?</h2><p>We have completely revised and updated all chapters. As AWS is iterating fast a lot has changed since 2015.</p><p>Just a few examples:</p><ul><li>AWS introduced much simpler ways to save costs with reserved and spot instances.</li><li>New Load Balancer types: Application Load Balancer and Network Load Balancer.</li><li>Additional options are available for Auto Scaling.</li></ul><p>On top of that, we have rewritten all the CloudFormation templates used in our book in YAML instead of JSON, which increases readability a lot, in our opinion.</p><h2 id="What-is-new-in-the-2nd-edition"><a href="#What-is-new-in-the-2nd-edition" class="headerlink" title="What is new in the 2nd edition?"></a>What is new in the 2nd edition?</h2><p>We have not only updated all previous findings we have also added three completely new chapters to the 2nd edition.</p><h2 id="Sharing-data-volumes-between-machines-Amazon-Elastic-File-System-EFS"><a href="#Sharing-data-volumes-between-machines-Amazon-Elastic-File-System-EFS" class="headerlink" title="Sharing data volumes between machines: Amazon Elastic File System (EFS)"></a>Sharing data volumes between machines: Amazon Elastic File System (EFS)</h2><p>Many legacy applications store state in files on disk. Therefore, using Amazon S3, an object store is not possible by default. Using block storage might be an option, but does not allow you to access files from multiple machines in parallel. Hence you need a way to share the files between virtual machines. With Elastic File System (EFS), you can share data between multiple EC2 instances and your data is replicated between multiple availability zones (AZ). EFS is based on the NFSv4.1 protocol, so you can mount it like any other file system. In this chapter, you learn how to set up EFS, tweak performance, and back your data.</p><h3 id="Caching-data-in-memory-Amazon-ElastiCache"><a href="#Caching-data-in-memory-Amazon-ElastiCache" class="headerlink" title="Caching data in memory: Amazon ElastiCache"></a>Caching data in memory: Amazon ElastiCache</h3><p>In this chapter, you will learn how to manage and use ElastiCache to increase the read performance of your applications.</p><p>Imagine a relational database being used for a popular mobile game where players’ scores and ranks are updated and read frequently. The read and write pressure to the database will be extremely high, especially when ranking scores across millions of players. Mitigating that pressure by scaling the database may help with load, but not necessarily the latency or cost. Also, relational databases tend to be more expensive than caching data stores. A proven solution used by many gaming companies is leveraging an in-memory data store such as Redis for both caching and ranking player and game metadata. Instead of reading and sorting the leaderboard directly from the relational database, they store an in-memory game leaderboard in Redis, commonly using a Redis Sorted Set, which will sort the data automatically when it’s inserted based on the score parameter. The score value may consist of the actual player ranking or player score in the game.</p><h3 id="Automating-operational-tasks-AWS-Lambda"><a href="#Automating-operational-tasks-AWS-Lambda" class="headerlink" title="Automating operational tasks: AWS Lambda"></a>Automating operational tasks: AWS Lambda</h3><p>This chapter is about adding a new tool to your toolbox. The tool we’re talking about, AWS Lambda, is as flexible as a swiss army knife. You don’t need a virtual machine to run your own code anymore, as AWS Lambda offers execution environments for Java, Node.js, C#, Python, and Go. All you have to do is to implement a function, upload your code, and configure the execution environment. Afterward, your code is executed within a fully-managed computing environment. AWS Lambda is well-integrated with all parts of AWS, enabling you to easily automate operations tasks within your infrastructure. We use AWS to automate our infrastructure regularly. For example, we use it to add and remove instances to a container cluster based on a custom algorithm, and to process and analyze log files.</p><p>In our first example, you will create a Lambda function that performs periodic health checks for your website. This will teach you how to use the Management Console and offer a blueprint for getting started with AWS Lambda quickly. In our second example, you will learn how to write your own Python code and deploy a Lambda function in an automated way using CloudFormation. Your Lambda function will automatically add a tag to newly launched EC2 instances. At the end of the chapter, we will show you additional use cases like building web applications, IoT backends, or processing data with AWS Lambda.</p><h2 id="Thanks"><a href="#Thanks" class="headerlink" title="Thanks"></a>Thanks</h2><p>We want to thank all readers who bought the MEAP version of our book. If you ordered the printed book as well, it should be already on it’s way to your postbox. Big thanks to all reviewers who helped us a lot to improve and fine tune the second edition.</p><h2 id="Special-Offer"><a href="#Special-Offer" class="headerlink" title="Special Offer"></a>Special Offer</h2><p>All others, please go to <a href="http://bit.ly/amazon-web-services-in-action-2nd-edition" target="_blank" rel="noopener">Amazon Web Services in Action Second Edition</a> and use promo code <code>nlaws2e</code> to get 20% off when buying our book.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Comparing Serverless offerings from Alibaba Cloud and AWS</title>
      <link>https://cloudonaut.io/comparing-serverless-offerings-from-alibaba-cloud-and-aws/</link>
      <description>
        <![CDATA[<p>Last weekend, I had the chance to play around with <a href="https://www.alibabacloud.com/" target="_blank" rel="noopener">Alibaba Cloud</]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/comparing-serverless-offerings-from-alibaba-cloud-and-aws/</guid>
      <pubDate>Wed, 26 Sep 2018 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Last weekend, I had the chance to play around with <a href="https://www.alibabacloud.com/" target="_blank" rel="noopener">Alibaba Cloud</a> at the <a href="https://www.meetup.com/de-DE/devops-stuttgart/events/252683739/" target="_blank" rel="noopener">DevOps Meetup</a> in my city. An Alibaba Cloud Solution Architect introduced the platform and was well trained on AWS as well, so he could compare both platforms for us. I also spent some time to figure out the details and finally, I ended up with a detailed comparison of the Serverless offering of both platforms.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/09/comparing-serverless-aws-alibaba@730w.webp 730w, /images/2018/09/comparing-serverless-aws-alibaba@730w2x.webp 1460w, /images/2018/09/comparing-serverless-aws-alibaba@610w.webp 610w, /images/2018/09/comparing-serverless-aws-alibaba@610w2x.webp 1220w, /images/2018/09/comparing-serverless-aws-alibaba@450w.webp 450w, /images/2018/09/comparing-serverless-aws-alibaba@450w2x.webp 900w, /images/2018/09/comparing-serverless-aws-alibaba@330w.webp 330w, /images/2018/09/comparing-serverless-aws-alibaba@330w2x.webp 660w, /images/2018/09/comparing-serverless-aws-alibaba@545w.webp 545w, /images/2018/09/comparing-serverless-aws-alibaba@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/09/comparing-serverless-aws-alibaba@730w.jpg 730w, /images/2018/09/comparing-serverless-aws-alibaba@730w2x.jpg 1460w, /images/2018/09/comparing-serverless-aws-alibaba@610w.jpg 610w, /images/2018/09/comparing-serverless-aws-alibaba@610w2x.jpg 1220w, /images/2018/09/comparing-serverless-aws-alibaba@450w.jpg 450w, /images/2018/09/comparing-serverless-aws-alibaba@450w2x.jpg 900w, /images/2018/09/comparing-serverless-aws-alibaba@330w.jpg 330w, /images/2018/09/comparing-serverless-aws-alibaba@330w2x.jpg 660w, /images/2018/09/comparing-serverless-aws-alibaba@545w.jpg 545w, /images/2018/09/comparing-serverless-aws-alibaba@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/09/comparing-serverless-aws-alibaba.jpg" alt="Comparing Serverless offerings from Alibaba Cloud and AWS" title="Comparing Serverless offerings from Alibaba Cloud and AWS"></picture></p><blockquote><p>When I talk about Alibaba Cloud I refer to the international version which misses features compared to the domestic (Chinese) version. Features are translated at a fast pace to close the gap between the domestic and international version.</p></blockquote><h2 id="FaaS-Runtimes"><a href="#FaaS-Runtimes" class="headerlink" title="FaaS Runtimes"></a>FaaS Runtimes</h2><p>AWS and Alibaba Cloud both offer a Function as a Service offering. When we look at the runtime, the platforms differentiate only in support for PHP, .NET Core, and Go.</p><table class="table table-striped table-responsive"><thead><tr><th>Language</th><th>AWS Lambda</th><th>Alibaba Cloud Function Compute</th></tr></thead><tbody><tr><td>Java</td><td>8</td><td>8</td></tr><tr><td>Node.js</td><td>8.10, 6.10, 4.3</td><td>8.9, 6.10</td></tr><tr><td>Python</td><td>3.6, 2.7</td><td>3.6, 2.7</td></tr><tr><td>PHP</td><td>-</td><td>7</td></tr><tr><td>.NET Core</td><td>2.1, 2.0, 1.0.1</td><td>-</td></tr><tr><td>Go</td><td>1.x</td><td>-</td></tr></tbody></table><h2 id="Features"><a href="#Features" class="headerlink" title="Features"></a>Features</h2><p>Alibaba Cloud provides a way to use a shared file system in your function which might be interesting to port legacy code. You can also group functions into a service which helps to keep functions (and also configuration) separated. To my surprise, the authorization concept is very similar to AWS, using policy-based access control.</p><table class="table table-striped table-responsive"><thead><tr><th>Feature</th><th>AWS</th><th>Alibaba Cloud</th></tr></thead><tbody><tr><td>Max timeout</td><td>15 min</td><td>10 min</td></tr><tr><td>Temporary disk space (/tmp)</td><td>512 MB</td><td>1024 MB</td></tr><tr><td>Metrics</td><td>some (CloudWatch Metrics)</td><td>many</td></tr><tr><td>Centralized logs</td><td>CloudWatch Logs</td><td>Log Service</td></tr><tr><td>Fine-grained authorization</td><td>IAM</td><td>RAM</td></tr><tr><td>Group functions</td><td>no</td><td>&quot;Service&quot;</td></tr><tr><td>Included libraries</td><td>AWS SDK</td><td>Alibaba Cloud SDK and more (list for <a href="https://www.alibabacloud.com/help/doc-detail/58011.htm#using-modules">Node.js</a>, <a href="https://www.alibabacloud.com/help/doc-detail/56316.htm#using-modules">Python</a>, <a href="https://www.alibabacloud.com/help/doc-detail/89032.htm#using-modules">PHP</a>)</td></tr><tr><td>Max. code package size (compressed)</td><td>50 MB</td><td>100 MB</td></tr><tr><td>Online IDE</td><td>rich (Cloud9)</td><td>minimal</td></tr><tr><td>Local deployment tool</td><td><a href="https://github.com/awslabs/serverless-application-model">SAM</a></td><td><a href="https://github.com/aliyun/fun/blob/master/README.md">Fun</a>, <a href="https://www.alibabacloud.com/help/doc-detail/52995.htm">fcli</a></td></tr><tr><td>Edge capabilities</td><td>yes</td><td>no</td></tr><tr><td>VPC support</td><td>yes</td><td>yes</td></tr><tr><td>Shared file system support</td><td>no</td><td>yes</td></tr><tr><td>Disable Internet access explicitly (non VPC)</td><td>no</td><td>yes</td></tr><tr><td>Initializer hook</td><td>no</td><td><a href="https://www.alibabacloud.com/help/doc-detail/94670.htm">yes</a></td></tr></tbody></table><h2 id="Triggers-and-Integrations"><a href="#Triggers-and-Integrations" class="headerlink" title="Triggers and Integrations"></a>Triggers and Integrations</h2><p>Most importantly, a Serverless architecture benefits from a variety of event sources.</p><table class="table table-striped table-responsive"><thead><tr><th>Source</th><th>AWS</th><th>Alibaba Cloud</th></tr></thead><tbody><tr><td>On-Demand</td><td>yes</td><td>yes</td></tr><tr><td>Object Storage</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-s3">S3</a></td><td><a href="https://www.alibabacloud.com/product/oss">OSS</a></td></tr><tr><td>Distributed log</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-kinesis-streams">Kinesis Data Streams</a></td><td><a href="https://www.alibabacloud.com/product/log-service">Log service</a></td></tr><tr><td>Log event aggregation</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-kinesis-firehose">Kinesis Data Firehose</a></td><td>-</td></tr><tr><td>Cronjob</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-scheduled-events">CloudWatch Events</a></td><td>Time trigger</td></tr><tr><td>Webhook</td><td>Somehow with API Gateway</td><td>HTTP trigger</td></tr><tr><td>REST API</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-api-gateway">API Gateway</a></td><td><a href="https://www.alibabacloud.com/help/doc-detail/54788.htm">API Gateway</a></td></tr><tr><td>CDN Edge</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-cloudfront">Lambda@Edge</a></td><td>-</td></tr><tr><td>NoSQL database</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-dynamo-db">DynamoDB streams</a></td><td><a href="https://www.alibabacloud.com/product/table-store">Table Store</a><sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup></td></tr><tr><td>Pub/Sub</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-sns">SNS</a></td><td><a href="https://www.alibabacloud.com/help/doc-detail/97032.htm">MNS</a></td></tr><tr><td>Incoming E-Mail</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-ses">SES</a></td><td>-</td></tr><tr><td>Queue</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-sqs">SQS</a></td><td>-</td></tr><tr><td>Logs</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-cloudwatch-logs">CloudWatch Logs</a></td><td><a href="https://www.alibabacloud.com/product/log-service">Log service</a></td></tr><tr><td>Git</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-codecommit">CodeCommit</a></td><td>-</td></tr><tr><td>Authentication</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-cognito">Cognito</a></td><td>-</td></tr><tr><td>Platform events</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-cloudwatch-events">CloudWatch Events</a></td><td>-</td></tr><tr><td>Platform governance</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-config">Config</a></td><td>-</td></tr><tr><td>Voice assistant</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-echo">Alexa</a></td><td>-</td></tr><tr><td>Conversational Interfaces</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-lex">Lex</a></td><td>-</td></tr><tr><td>Infrastructure as Code</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-cloudformation">CloudFormation</a></td><td>-</td></tr><tr><td>Programmable button</td><td><a href="https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html#supported-event-source-iot-button">IoT Button</a></td><td>-</td></tr></tbody></table><h2 id="Billing"><a href="#Billing" class="headerlink" title="Billing"></a>Billing</h2><p>The same.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>To my surprise, Alibaba Cloud is not that different compared to AWS. Both platforms work in a similar way (which is not the case for GCP or Azure in my opinion). AWS provides more Triggers and Integrations but Alibaba comes with some unique features such as a shared file system and PHP support.</p><p><em>Thanks to Oliver Arafat (Senior Solution Architect Alibaba Cloud) for reviewing this article.</em></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>A neglected serverless data store: Cloud Directory</title>
      <link>https://cloudonaut.io/a-neglected-serverless-data-store-cloud-directory/</link>
      <description>
        <![CDATA[<p>Lately, I’ve been having much fun with <a href="https://aws.amazon.com/cloud-directory/" target="_blank" rel="noopener">Amazon Cloud Dire]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/cloud-directory/">cloud-directory</category>
      <guid isPermaLink="true">https://cloudonaut.io/a-neglected-serverless-data-store-cloud-directory/</guid>
      <pubDate>Tue, 18 Sep 2018 14:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Lately, I’ve been having much fun with <a href="https://aws.amazon.com/cloud-directory/" target="_blank" rel="noopener">Amazon Cloud Directory</a>. Three months before, Cloud Directory was all new to me. Today, I am convinced that Cloud Directory is a neglected Serverless data store that deserves much more attention. Let me explain what Cloud Directory is and why it is an excellent choice in Serverless architectures.</p><h2 id="A-short-introduction-to-Cloud-Directory"><a href="#A-short-introduction-to-Cloud-Directory" class="headerlink" title="A short introduction to Cloud Directory"></a>A short introduction to Cloud Directory</h2><p>Cloud Directory is a fully managed, hierarchical data store on AWS. Let’s talk about hierarchical data first.</p><h3 id="Introducing-hierarchical-data-and-Cloud-Directory-terminology"><a href="#Introducing-hierarchical-data-and-Cloud-Directory-terminology" class="headerlink" title="Introducing hierarchical data and Cloud Directory terminology"></a>Introducing hierarchical data and Cloud Directory terminology</h3><p>In this post, I model a Slack like chat system using a hierarchical data model. There are two <em><strong>team</strong></em>s. <em><strong>Team</strong></em> <em>widdix</em> use the <em>general</em> <em><strong>channel</strong></em> to discuss work-related topics, and they discuss AWS announcements in the <em>news</em> <em><strong>channel</strong></em>. The <em>AWS</em> <em><strong>team</strong></em> is busy planning re:Invent in the <em>reinvent</em> <em><strong>channel</strong></em>, and they use the <em>general</em> <em><strong>channel</strong></em> to discuss other topics. The following figure shows the chat system data hierarchy.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/09/cloud-directory-hierarchy@730w.webp 730w, /images/2018/09/cloud-directory-hierarchy@730w2x.webp 1460w, /images/2018/09/cloud-directory-hierarchy@610w.webp 610w, /images/2018/09/cloud-directory-hierarchy@610w2x.webp 1220w, /images/2018/09/cloud-directory-hierarchy@450w.webp 450w, /images/2018/09/cloud-directory-hierarchy@450w2x.webp 900w, /images/2018/09/cloud-directory-hierarchy@330w.webp 330w, /images/2018/09/cloud-directory-hierarchy@330w2x.webp 660w, /images/2018/09/cloud-directory-hierarchy@545w.webp 545w, /images/2018/09/cloud-directory-hierarchy@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/09/cloud-directory-hierarchy@730w.png 730w, /images/2018/09/cloud-directory-hierarchy@730w2x.png 1460w, /images/2018/09/cloud-directory-hierarchy@610w.png 610w, /images/2018/09/cloud-directory-hierarchy@610w2x.png 1220w, /images/2018/09/cloud-directory-hierarchy@450w.png 450w, /images/2018/09/cloud-directory-hierarchy@450w2x.png 900w, /images/2018/09/cloud-directory-hierarchy@330w.png 330w, /images/2018/09/cloud-directory-hierarchy@330w2x.png 660w, /images/2018/09/cloud-directory-hierarchy@545w.png 545w, /images/2018/09/cloud-directory-hierarchy@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/09/cloud-directory-hierarchy.png" alt="Cloud Directory Hierarchy" title="Cloud Directory Hierarchy"></picture></p><p>In Cloud Directory, a <strong>Directory</strong> always comes with an implicit single <strong>Root Node</strong> object. When you create a <strong>Node</strong> object, you have to specify the parent (<strong>Root Node</strong> object or <strong>Node</strong> object). Every <strong>Node</strong> object has exactly one parent, and a <strong>Child Link</strong> is automatically created for you. Therefore, every <strong>Node</strong> object has <code>[0...N]</code> children.</p><h3 id="Accessing-a-Node"><a href="#Accessing-a-Node" class="headerlink" title="Accessing a Node"></a>Accessing a Node</h3><p>You can access a <strong>Node</strong> object in the <strong>Directory</strong> with a <strong>Selector</strong> that can be a Cloud Directory generated <strong>Object Identifier</strong> or a <strong>Path</strong> that you can control. The <strong>Selector</strong> is a string where the first character is important.</p><ul><li><strong>Selector</strong>s that start with a <code>$</code> point to an <strong>Object Identifier</strong>, e.g., <code>$287d</code>. The value is generated by Cloud Directory when you create an object.</li><li><strong>Selector</strong>s that start with a <code>/</code> point to a <strong>Path</strong>, e.g., <code>/aws/general</code>. The path is a concatenation of the <strong>Child Link</strong>s <strong>Link Name</strong>.</li></ul><p>Let’s look at the <strong>Path</strong> more closely. As you already learned, when you <a href="https://docs.aws.amazon.com/directoryservice/latest/APIReference/API_CreateObject.html" target="_blank" rel="noopener">create</a> a <strong>Node</strong>, a <strong>Child Link</strong> is automatically created for you that points to the parent. The <strong>Child Link</strong> has a <strong>Link Name</strong> that you have to supply, and it must be unique for all nodes sharing the same parent. The following figure shows the <strong>Link Name</strong>s and <strong>Object Identifier</strong>s of the chat system.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/09/cloud-directory-paths-ids@730w.webp 730w, /images/2018/09/cloud-directory-paths-ids@730w2x.webp 1460w, /images/2018/09/cloud-directory-paths-ids@610w.webp 610w, /images/2018/09/cloud-directory-paths-ids@610w2x.webp 1220w, /images/2018/09/cloud-directory-paths-ids@450w.webp 450w, /images/2018/09/cloud-directory-paths-ids@450w2x.webp 900w, /images/2018/09/cloud-directory-paths-ids@330w.webp 330w, /images/2018/09/cloud-directory-paths-ids@330w2x.webp 660w, /images/2018/09/cloud-directory-paths-ids@545w.webp 545w, /images/2018/09/cloud-directory-paths-ids@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/09/cloud-directory-paths-ids@730w.png 730w, /images/2018/09/cloud-directory-paths-ids@730w2x.png 1460w, /images/2018/09/cloud-directory-paths-ids@610w.png 610w, /images/2018/09/cloud-directory-paths-ids@610w2x.png 1220w, /images/2018/09/cloud-directory-paths-ids@450w.png 450w, /images/2018/09/cloud-directory-paths-ids@450w2x.png 900w, /images/2018/09/cloud-directory-paths-ids@330w.png 330w, /images/2018/09/cloud-directory-paths-ids@330w2x.png 660w, /images/2018/09/cloud-directory-paths-ids@545w.png 545w, /images/2018/09/cloud-directory-paths-ids@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/09/cloud-directory-paths-ids.png" alt="Cloud Directory Paths and Ids" title="Cloud Directory Paths and Ids"></picture></p><p>The path <code>/widdix/general</code> uniquely identifies the <em>general</em> <em><strong>channel</strong></em> of <em><strong>team</strong></em> <em>widdix</em> while the path <code>/aws/general</code> uniquely identifies the <em>general</em> <em><strong>channel</strong></em> of <em><strong>team</strong></em> <em>AWS</em>. The <strong>Link Name</strong> <code>general</code> can be used multiple times in the <strong>Directory</strong> but not for the same parent.</p><p>For each <strong>Node</strong>, you can get:</p><ul><li><a href="https://docs.aws.amazon.com/directoryservice/latest/APIReference/API_ListObjectChildren.html" target="_blank" rel="noopener">children</a> (<code>[0..N]</code> children for a <strong>Node</strong>)</li><li><a href="https://docs.aws.amazon.com/directoryservice/latest/APIReference/API_ListObjectParents.html" target="_blank" rel="noopener">parents</a> (always a single parent for a <strong>Node</strong>)</li><li><a href="https://docs.aws.amazon.com/directoryservice/latest/APIReference/API_ListObjectParentPaths.html" target="_blank" rel="noopener">paths to Root Node</a> (always a single path for a <strong>Node</strong>)</li></ul><h3 id="Storing-data-based-on-a-schema"><a href="#Storing-data-based-on-a-schema" class="headerlink" title="Storing data based on a schema"></a>Storing data based on a schema</h3><p>So far, I have not talked about the ability to store data besides the hierarchy. This changes now. A <strong>Facet</strong> defines a collection of <strong>Attribute</strong>s. Compared to the relational database world, you can think of a <strong>Facet</strong> as a table and an <strong>Attribute</strong> as a column for now. An <strong>Attribute</strong> is defined by:</p><ul><li>type (<code>STRING</code>, <code>NUMBER</code>, <code>BINARY</code>, <code>BOOLEAN</code>, <code>DATETIME</code>)</li><li>required or optional</li><li>mutable or immutable</li><li>default value</li><li><a href="https://docs.aws.amazon.com/clouddirectory/latest/developerguide/schemas_attributerules.html" target="_blank" rel="noopener">validation rules</a><ul><li>String length</li><li>Binary length</li><li>String from set</li><li>Number comparison</li></ul></li></ul><p>A <strong>Node</strong> can be associated with one or many <strong>Facet</strong>s. The following figure shows the <strong>Facet</strong>s of the chat system (each Node is associated only with a single Facet).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/09/cloud-directory-facets@730w.webp 730w, /images/2018/09/cloud-directory-facets@730w2x.webp 1460w, /images/2018/09/cloud-directory-facets@610w.webp 610w, /images/2018/09/cloud-directory-facets@610w2x.webp 1220w, /images/2018/09/cloud-directory-facets@450w.webp 450w, /images/2018/09/cloud-directory-facets@450w2x.webp 900w, /images/2018/09/cloud-directory-facets@330w.webp 330w, /images/2018/09/cloud-directory-facets@330w2x.webp 660w, /images/2018/09/cloud-directory-facets@545w.webp 545w, /images/2018/09/cloud-directory-facets@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/09/cloud-directory-facets@730w.png 730w, /images/2018/09/cloud-directory-facets@730w2x.png 1460w, /images/2018/09/cloud-directory-facets@610w.png 610w, /images/2018/09/cloud-directory-facets@610w2x.png 1220w, /images/2018/09/cloud-directory-facets@450w.png 450w, /images/2018/09/cloud-directory-facets@450w2x.png 900w, /images/2018/09/cloud-directory-facets@330w.png 330w, /images/2018/09/cloud-directory-facets@330w2x.png 660w, /images/2018/09/cloud-directory-facets@545w.png 545w, /images/2018/09/cloud-directory-facets@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/09/cloud-directory-facets.png" alt="Cloud Directory Facets" title="Cloud Directory Facets"></picture></p><p>A <strong>Schema</strong> is a collection of <strong>Facet</strong>s. <strong>Schema</strong>s are a bit tricky. When you create a new <strong>Schema</strong>, this is called a <strong>Development Schema</strong>, and you can modify it (create facets, change facets, delete facets). When you are happy with the <strong>Development Schema</strong>, you can publish it, and a <strong>Published Schema</strong> is created for you that is versioned. The <strong>Published Schema</strong> cannot be changed. It can only be <a href="https://docs.aws.amazon.com/directoryservice/latest/APIReference/API_UpgradePublishedSchema.html" target="_blank" rel="noopener">upgraded</a> in a backward compatible way (add a facet, add an optional attribute to an existing facet). When you create a <strong>Directory</strong>, you have to select a <strong>Published Schema</strong> which is then copied and becomes an <strong>Applied Schema</strong>. The <strong>Applied Schema</strong> can also be <a href="https://docs.aws.amazon.com/directoryservice/latest/APIReference/API_UpgradeAppliedSchema.html" target="_blank" rel="noopener">upgraded</a> in a backward compatible way. The following table summaries the different types of <strong>Schema</strong>s.</p><table class="table table-striped table-responsive"><thead><tr><th>Schema type</th><th>Mutable</th><th>Versioned</th><th>Backward compatible Upgrade</th></tr></thead><tbody><tr><td>Development</td><td>yes</td><td>no</td><td>-</td></tr><tr><td>Published</td><td>no</td><td>yes</td><td>yes</td></tr><tr><td>Applied</td><td>no</td><td>yes</td><td>yes</td></tr></tbody></table><p>You can now store a hierarchy together with attributes. Let’s look at one more feature: Linking nodes.</p><h3 id="Linking-nodes-Typed-Links"><a href="#Linking-nodes-Typed-Links" class="headerlink" title="Linking nodes: Typed Links"></a>Linking nodes: Typed Links</h3><p>Besides the <strong>Child Link</strong>, Cloud Directory supports a <strong>Typed Link</strong> that goes from one <strong>Node</strong> to another <strong>Node</strong>. The cool thing about the <strong>Typed Link</strong> is that you can attach <strong>Attribute</strong>s to it as well defined in a <strong>Typed Link Facet</strong>. Some (or all) of the <strong>Attribute</strong>s are used as the identity of the <strong>Typed Link</strong>.</p><blockquote><p>You can not use a Path to navigate with Typed Links.</p></blockquote><p>The following figure shows how a <strong>Typed Link</strong> can be used in the chat system to share channels between teams. The <em>AWS</em> <em><strong>team</strong></em> now has access the to news <em><strong>channel</strong></em> of the <em>widdix</em> <em><strong>team</strong></em>, and it appears as <em>widdix-news</em> in the <em>AWS</em> <em><strong>team</strong></em>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/09/cloud-directory-typed-links@730w.webp 730w, /images/2018/09/cloud-directory-typed-links@730w2x.webp 1460w, /images/2018/09/cloud-directory-typed-links@610w.webp 610w, /images/2018/09/cloud-directory-typed-links@610w2x.webp 1220w, /images/2018/09/cloud-directory-typed-links@450w.webp 450w, /images/2018/09/cloud-directory-typed-links@450w2x.webp 900w, /images/2018/09/cloud-directory-typed-links@330w.webp 330w, /images/2018/09/cloud-directory-typed-links@330w2x.webp 660w, /images/2018/09/cloud-directory-typed-links@545w.webp 545w, /images/2018/09/cloud-directory-typed-links@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/09/cloud-directory-typed-links@730w.png 730w, /images/2018/09/cloud-directory-typed-links@730w2x.png 1460w, /images/2018/09/cloud-directory-typed-links@610w.png 610w, /images/2018/09/cloud-directory-typed-links@610w2x.png 1220w, /images/2018/09/cloud-directory-typed-links@450w.png 450w, /images/2018/09/cloud-directory-typed-links@450w2x.png 900w, /images/2018/09/cloud-directory-typed-links@330w.png 330w, /images/2018/09/cloud-directory-typed-links@330w2x.png 660w, /images/2018/09/cloud-directory-typed-links@545w.png 545w, /images/2018/09/cloud-directory-typed-links@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/09/cloud-directory-typed-links.png" alt="Cloud Directory Typed Links" title="Cloud Directory Typed Links"></picture></p><p>For each <strong>Node</strong>, you can get:</p><ul><li><a href="https://docs.aws.amazon.com/directoryservice/latest/APIReference/API_ListIncomingTypedLinks.html" target="_blank" rel="noopener">incoming Typed Links</a></li><li><a href="https://docs.aws.amazon.com/directoryservice/latest/APIReference/API_ListOutgoingTypedLinks.html" target="_blank" rel="noopener">outgoing Typed Links</a></li></ul><p>That is the end of the short introduction to Cloud Directory. Let’s look at how Cloud Directory fits into the Serverless space next.</p><h2 id="How-does-Cloud-Directory-fit-into-the-Serverless-space"><a href="#How-does-Cloud-Directory-fit-into-the-Serverless-space" class="headerlink" title="How does Cloud Directory fit into the Serverless space?"></a>How does Cloud Directory fit into the Serverless space?</h2><p>Cloud Directory is truly Serverless. There is zero configuration.</p><ul><li>Cloud Directory Requires no management of machines or software</li><li>You pay per request and used storage</li><li>Scales automatically</li><li>Is fault tolerant&#x2F;highly available</li></ul><p>Cloud Directory can be used from Lamba with the AWS SDK.</p><h2 id="Additional-Feature"><a href="#Additional-Feature" class="headerlink" title="Additional Feature"></a>Additional Feature</h2><p>Besides the features you learned about so far, Cloud Directory also has support for:</p><ul><li>Transactions</li><li>Multiple consistency levels</li><li>Policies with efficient retrieval</li><li>Leaf nodes (can have <code>[0..N]</code>parents)</li><li>Indexes</li></ul><blockquote><p>Missing features: No backup&#x2F;restore capability and no visual explorer of your directory.</p></blockquote><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>If you use Cognito or Organizations, your data is already stored in Cloud Directory under the covers. Cloud Directory is a serverless hierarchical data store on AWS. It is a niche service that deserves more attention because it is easy to operate (zero configuration) and comes with transactional support. </p><p><em>Thanks to <a href="https://x.com/hoegertn" target="_blank" rel="noopener">Thorsten Höger</a> for reviewing this article.</em></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>DevOps on AWS Radio</title>
      <link>https://cloudonaut.io/devops-on-aws-radio/</link>
      <description>
        <![CDATA[<p>Paul Duvall (CTO at <a href="https://stelligent.com/" target="_blank" rel="noopener">stelligent</a>) interviewed Andreas and me for the D]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/devops-on-aws-radio/</guid>
      <pubDate>Wed, 05 Sep 2018 19:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Paul Duvall (CTO at <a href="https://stelligent.com/" target="_blank" rel="noopener">stelligent</a>) interviewed Andreas and me for the DevOps on AWS Radio.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/09/devops-on-aws-radio@730w.webp 730w, /images/2018/09/devops-on-aws-radio@730w2x.webp 1460w, /images/2018/09/devops-on-aws-radio@610w.webp 610w, /images/2018/09/devops-on-aws-radio@610w2x.webp 1220w, /images/2018/09/devops-on-aws-radio@450w.webp 450w, /images/2018/09/devops-on-aws-radio@450w2x.webp 900w, /images/2018/09/devops-on-aws-radio@330w.webp 330w, /images/2018/09/devops-on-aws-radio@330w2x.webp 660w, /images/2018/09/devops-on-aws-radio@545w.webp 545w, /images/2018/09/devops-on-aws-radio@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/09/devops-on-aws-radio@730w.png 730w, /images/2018/09/devops-on-aws-radio@730w2x.png 1460w, /images/2018/09/devops-on-aws-radio@610w.png 610w, /images/2018/09/devops-on-aws-radio@610w2x.png 1220w, /images/2018/09/devops-on-aws-radio@450w.png 450w, /images/2018/09/devops-on-aws-radio@450w2x.png 900w, /images/2018/09/devops-on-aws-radio@330w.png 330w, /images/2018/09/devops-on-aws-radio@330w2x.png 660w, /images/2018/09/devops-on-aws-radio@545w.png 545w, /images/2018/09/devops-on-aws-radio@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/09/devops-on-aws-radio.png" alt="DevOps on AWS Radio" title="DevOps on AWS Radio"></picture></p><p>We talked about:</p><ul><li>Our book Amazon Web Services in Action and what changed in the second edition</li><li>Our top 3 recommendations for enterprises on what to do to be most successful on AWS </li><li>Our top 3 recommendations for enterprises to avoid when moving to AWS</li><li>Tips for affecting large-scale change to AWS in enterprises</li></ul><p>Read the <a href="https://stelligent.com/2018/09/04/devops-on-aws-radio-aws-in-action-michael-and-andreas-wittig/" target="_blank" rel="noopener">show notes</a> for additional information.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Encrypting sensitive data stored on S3</title>
      <link>https://cloudonaut.io/encrypting-sensitive-data-stored-on-s3/</link>
      <description>
        <![CDATA[<p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/08/encrypting-sensitive-data-stored-on-s3@730w.webp 730w, /imag]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <guid isPermaLink="true">https://cloudonaut.io/encrypting-sensitive-data-stored-on-s3/</guid>
      <pubDate>Tue, 14 Aug 2018 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/08/encrypting-sensitive-data-stored-on-s3@730w.webp 730w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@730w2x.webp 1460w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@610w.webp 610w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@610w2x.webp 1220w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@450w.webp 450w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@450w2x.webp 900w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@330w.webp 330w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@330w2x.webp 660w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@545w.webp 545w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/08/encrypting-sensitive-data-stored-on-s3@730w.jpg 730w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@730w2x.jpg 1460w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@610w.jpg 610w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@610w2x.jpg 1220w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@450w.jpg 450w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@450w2x.jpg 900w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@330w.jpg 330w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@330w2x.jpg 660w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@545w.jpg 545w, /images/2018/08/encrypting-sensitive-data-stored-on-s3@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/08/encrypting-sensitive-data-stored-on-s3.jpg" alt="Encrypting sensitive data stored on S3" title="Encrypting sensitive data stored on S3"></picture></p><p>S3 comes with a bunch of features to encrypt your data at rest. Data at rest means inactive data stored physically on disk. Before we dive into encrypting data at rest, I want to highlight that there is also data in use and data in transit. If the data is in memory, it is in use. If the data is on the network, it is in transit. If you transfer data to S3, it is TLS encrypted by default. This blog post will guide you through all ways to encrypt your S3 data at rest.</p><h2 id="Comparing-options"><a href="#Comparing-options" class="headerlink" title="Comparing options"></a>Comparing options</h2><p>S3 offers a bunch of options to encrypt your data at rest. The fundamental questions to compare the options are:</p><ul><li>Who en&#x2F;decrypts the data? Data encryption can happen either on your side (client-side encryption) or on AWS (server-side encryption or SSE). When you encrypt data on your side, the data transferred to S3 is already encrypted. S3 never sees the raw data. Server-side encryption is different because you send the raw data to S3 where it is encrypted.</li><li>Who stores the secret? Imagine you encrypted all your pictures and uploaded them to S3. You store the secret used for encryption on your USB stick. A few months later, you want to look at your pictures. Unfortunately, the USB stick where your stored the secret broke. The loss of the USB stick is a catastrophe. You are no longer able to decrypt your pictures. They are gone forever.</li><li>Who manages the secret? Data encryption makes no sense if everyone can access your secret. Managing access to the secret is a great responsibility.</li></ul><p>The following table summaries the available options on S3 to encrypt your data at rest.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th>Who en/decrypts the data</th><th>Who stores the secret</th><th>Who manages the secret</th></tr></thead><tbody><tr><td>SSE-AES</td><td>AWS</td><td>AWS</td><td>AWS</td></tr><tr><td>SSE-KMS (AWS managed CMK)</td><td>AWS</td><td>AWS</td><td>AWS</td></tr><tr><td>SSE-KMS (customer managed CMK)</td><td>AWS</td><td>AWS</td><td>you</td></tr><tr><td>SSE-C</td><td>AWS</td><td>you</td><td>you</td></tr><tr><td>AWS SDK + KMS (AWS managed CMK)</td><td>you</td><td>AWS</td><td>AWS</td></tr><tr><td>AWS SDK + KMS (customer managed CMK)</td><td>you</td><td>AWS</td><td>you</td></tr><tr><td>AWS SDK + self-managed secret</td><td>you</td><td>you</td><td>you</td></tr></tbody></table><p>Let’s dive into the details of each option.</p><h2 id="Server-side-encryption"><a href="#Server-side-encryption" class="headerlink" title="Server-side encryption"></a>Server-side encryption</h2><p>Server-side encryption means that you send unencrypted raw data to AWS. On the AWS infrastructure, the raw data is encrypted and finally stored on disk. When you retrieve data, AWS reads the encrypted data from the disk, decrypts the data, and sends raw data back to you. The en&#x2F;decryption is transparent to the AWS user.</p><h3 id="SSE-AES"><a href="#SSE-AES" class="headerlink" title="SSE-AES"></a>SSE-AES</h3><p><code>SSE-AES</code> is a straightforward approach. AWS handles encryption and decryption for you on the server-side using the <a href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard" target="_blank" rel="noopener">aes256</a> algorithm. AWS also controls the secret key that is used for encryption&#x2F;decryption.</p><p>To upload a file and store it encrypted, run:</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">aws s3 cp <span class="type">path</span>/<span class="keyword">to</span>/<span class="keyword">local</span>.file s3://bucket-<span class="type">name</span>/sse-aes <span class="comment">--sse AES256</span></span><br></pre></td></tr></table></figure><p>To download the decrypted file, run:</p><figure class="highlight armasm"><table><tr><td class="code"><pre><span class="line"><span class="symbol">aws</span> <span class="built_in">s3</span> <span class="meta">cp</span> <span class="built_in">s3</span>:<span class="comment">//bucket-name/sse-aes path/to/local.file</span></span><br></pre></td></tr></table></figure><h3 id="SSE-KMS-AWS-managed-CMK"><a href="#SSE-KMS-AWS-managed-CMK" class="headerlink" title="SSE-KMS (AWS managed CMK)"></a>SSE-KMS (AWS managed CMK)</h3><p><code>SSE-KMS</code> is very similar to <code>SSE-AES</code>. The only difference is that the secret key (aka AWS managed Customer Master Key (CMK)) is provided by the KMS service and not by S3.</p><p>To upload a file and store it encrypted, run:</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">aws s3 cp <span class="type">path</span>/<span class="keyword">to</span>/<span class="keyword">local</span>.file s3://bucket-<span class="type">name</span>/sse-kms <span class="comment">--sse aws:kms</span></span><br></pre></td></tr></table></figure><p>To download the decrypted file, run:</p><figure class="highlight armasm"><table><tr><td class="code"><pre><span class="line"><span class="symbol">aws</span> <span class="built_in">s3</span> <span class="meta">cp</span> <span class="built_in">s3</span>:<span class="comment">//bucket-name/sse-kms path/to/local.file</span></span><br></pre></td></tr></table></figure><p>The AWS managed CMK comes with the following default key policy that you can not modify. The default key policy allows:</p><ol><li>The S3 service is called from the same AWS account to encrypt&#x2F;decrypt using the CMK</li><li>IAM in the same AWS account to use authorize read-only API actions</li></ol><p>This is the policy in its full length:</p><figure class="highlight n1ql"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;Version&quot;: &quot;2012-10<span class="number">-17</span><span class="string">&quot;,</span></span><br><span class="line"><span class="string">  &quot;</span>Id<span class="string">&quot;: &quot;</span>auto-s3<span class="number">-2</span><span class="string">&quot;,</span></span><br><span class="line"><span class="string">  &quot;</span>Statement<span class="string">&quot;: [</span></span><br><span class="line"><span class="string">    &#123;</span></span><br><span class="line"><span class="string">      &quot;</span>Sid<span class="string">&quot;: &quot;</span>Allow access through S3 <span class="keyword">for</span> <span class="keyword">all</span> principals <span class="keyword">in</span> the account that are authorized <span class="keyword">to</span> <span class="keyword">use</span> S3<span class="string">&quot;,</span></span><br><span class="line"><span class="string">      &quot;</span>Effect<span class="string">&quot;: &quot;</span>Allow<span class="string">&quot;,</span></span><br><span class="line"><span class="string">      &quot;</span>Principal<span class="string">&quot;: &#123;</span></span><br><span class="line"><span class="string">        &quot;</span>AWS<span class="string">&quot;: &quot;</span>*<span class="string">&quot;</span></span><br><span class="line"><span class="string">      &#125;,</span></span><br><span class="line"><span class="string">      &quot;</span>Action<span class="string">&quot;: [</span></span><br><span class="line"><span class="string">        &quot;</span>kms:Encrypt<span class="string">&quot;,</span></span><br><span class="line"><span class="string">        &quot;</span>kms:Decrypt<span class="string">&quot;,</span></span><br><span class="line"><span class="string">        &quot;</span>kms:ReEncrypt*<span class="string">&quot;,</span></span><br><span class="line"><span class="string">        &quot;</span>kms:GenerateDataKey*<span class="string">&quot;,</span></span><br><span class="line"><span class="string">        &quot;</span>kms:DescribeKey<span class="string">&quot;</span></span><br><span class="line"><span class="string">      ],</span></span><br><span class="line"><span class="string">      &quot;</span>Resource<span class="string">&quot;: &quot;</span>*<span class="string">&quot;,</span></span><br><span class="line"><span class="string">      &quot;</span>Condition<span class="string">&quot;: &#123;</span></span><br><span class="line"><span class="string">        &quot;</span>StringEquals<span class="string">&quot;: &#123;</span></span><br><span class="line"><span class="string">          &quot;</span>kms:ViaService<span class="string">&quot;: &quot;</span>s3.us-east<span class="number">-1.</span>amazonaws.com<span class="string">&quot;,</span></span><br><span class="line"><span class="string">          &quot;</span>kms:CallerAccount<span class="string">&quot;: &quot;</span>ACCOUNT_ID<span class="string">&quot;</span></span><br><span class="line"><span class="string">        &#125;</span></span><br><span class="line"><span class="string">      &#125;</span></span><br><span class="line"><span class="string">    &#125;,</span></span><br><span class="line"><span class="string">    &#123;</span></span><br><span class="line"><span class="string">      &quot;</span>Sid<span class="string">&quot;: &quot;</span>Allow direct access <span class="keyword">to</span> <span class="keyword">key</span> metadata <span class="keyword">to</span> the account<span class="string">&quot;,</span></span><br><span class="line"><span class="string">      &quot;</span>Effect<span class="string">&quot;: &quot;</span>Allow<span class="string">&quot;,</span></span><br><span class="line"><span class="string">      &quot;</span>Principal<span class="string">&quot;: &#123;</span></span><br><span class="line"><span class="string">        &quot;</span>AWS<span class="string">&quot;: &quot;</span>arn:aws:iam::ACCOUNT_ID:root<span class="string">&quot;</span></span><br><span class="line"><span class="string">      &#125;,</span></span><br><span class="line"><span class="string">      &quot;</span>Action<span class="string">&quot;: [</span></span><br><span class="line"><span class="string">        &quot;</span>kms:<span class="keyword">Describe</span>*<span class="string">&quot;,</span></span><br><span class="line"><span class="string">        &quot;</span>kms:Get*<span class="string">&quot;,</span></span><br><span class="line"><span class="string">        &quot;</span>kms:List*<span class="string">&quot;</span></span><br><span class="line"><span class="string">      ],</span></span><br><span class="line"><span class="string">      &quot;</span>Resource<span class="string">&quot;: &quot;</span>*<span class="string">&quot;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">  ]</span></span><br><span class="line"><span class="string">&#125;</span></span><br></pre></td></tr></table></figure><p>You can not delete or restrict the AWS managed CMK used by S3!</p><h3 id="SSE-KMS-customer-managed-CMK"><a href="#SSE-KMS-customer-managed-CMK" class="headerlink" title="SSE-KMS (customer managed CMK)"></a>SSE-KMS (customer managed CMK)</h3><p>Alternatively, you can manage the secret key (aka Customer managed Customer Master Key) using the KMS service. You create a Customer Master Key (CMK) and reference that key for encryption&#x2F;decryption. At any time, you can delete the CMK to make all data useless. You also have full control over the CMK by customizing the key policy.</p><p>To create a basic CMK, run:</p><figure class="highlight gauss"><table><tr><td class="code"><pre><span class="line">aws kms <span class="keyword">create</span>-<span class="built_in">key</span></span><br></pre></td></tr></table></figure><p>The key policy will allow access from all IAM entities in your AWS account (as long as the IAM policy allows it).</p><p>Your output will look similar:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;KeyMetadata&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;Origin&quot;</span><span class="punctuation">:</span> <span class="string">&quot;AWS_KMS&quot;</span><span class="punctuation">,</span> </span><br><span class="line">    <span class="attr">&quot;KeyId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;858d8d36-c87b-4b48-9a41-b69b7ad9d4e2&quot;</span><span class="punctuation">,</span> </span><br><span class="line">    <span class="attr">&quot;Description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span> </span><br><span class="line">    <span class="attr">&quot;KeyManager&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CUSTOMER&quot;</span><span class="punctuation">,</span> </span><br><span class="line">    <span class="attr">&quot;Enabled&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span> </span><br><span class="line">    <span class="attr">&quot;KeyUsage&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ENCRYPT_DECRYPT&quot;</span><span class="punctuation">,</span> </span><br><span class="line">    <span class="attr">&quot;KeyState&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Enabled&quot;</span><span class="punctuation">,</span> </span><br><span class="line">    <span class="attr">&quot;CreationDate&quot;</span><span class="punctuation">:</span> <span class="number">1534164269.969</span><span class="punctuation">,</span> </span><br><span class="line">    <span class="attr">&quot;Arn&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:kms:us-east-1:ACCOUNT_ID:key/858d8d36-c87b-4b48-9a41-b69b7ad9d4e2&quot;</span><span class="punctuation">,</span> </span><br><span class="line">    <span class="attr">&quot;AWSAccountId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ACCOUNT_ID&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Remember somewhere the <code>KeyId</code> value (e.g., <code>858d8d36-c87b-4b48-9a41-b69b7ad9d4e2</code>).</p><p>To upload a file and store it encrypted using your newly created CMK, run and replace <code>KMS_KEY_ID</code> with the <code>KeyId</code> value:</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">aws s3 cp <span class="type">path</span>/<span class="keyword">to</span>/<span class="keyword">local</span>.file s3://bucket-<span class="type">name</span>/sse-kms-cmk <span class="comment">--sse aws:kms --sse-kms-key-id KMS_KEY_ID</span></span><br></pre></td></tr></table></figure><p>To download the decrypted file, run:</p><figure class="highlight armasm"><table><tr><td class="code"><pre><span class="line"><span class="symbol">aws</span> <span class="built_in">s3</span> <span class="meta">cp</span> <span class="built_in">s3</span>:<span class="comment">//bucket-name/sse-kms-cmk path/to/local.file</span></span><br></pre></td></tr></table></figure><p>Now, disable the CMK:</p><figure class="highlight gauss"><table><tr><td class="code"><pre><span class="line">aws kms <span class="keyword">disable</span>-<span class="built_in">key</span> --<span class="built_in">key</span>-id KMS_KEY_ID</span><br></pre></td></tr></table></figure><p>And try to download the file again and you will run into an error (dKMS.DisabledException). That’s the difference compared with the AWS managed CMK that you can not control.</p><p>Finally, mark the CMK for deletion to avoid future costs:</p><figure class="highlight gauss"><table><tr><td class="code"><pre><span class="line">aws kms schedule-<span class="built_in">key</span>-deletion --<span class="built_in">key</span>-id KMS_KEY_ID</span><br></pre></td></tr></table></figure><p>You will never be able to retrieve the file from S3 once you delete the CMK!</p><h3 id="SSE-C"><a href="#SSE-C" class="headerlink" title="SSE-C"></a>SSE-C</h3><p>With <code>SSE-C</code>, you are in charge of the secret key while AWS still cares about encryption&#x2F;decryption. Every time you call the S3 API, you also have to attach the secret key.</p><p>To generate a random 32 bytes (256 bits) secret key, run:</p><figure class="highlight q"><table><tr><td class="code"><pre><span class="line">openssl <span class="built_in">rand</span> -out sse-c.<span class="built_in">key</span> <span class="number">32</span></span><br></pre></td></tr></table></figure><p>To upload a file and store it encrypted, run:</p><figure class="highlight llvm"><table><tr><td class="code"><pre><span class="line">aws s<span class="number">3</span> cp path/<span class="keyword">to</span>/local.file s<span class="number">3</span>://bucket-name/sse-<span class="keyword">c</span> --sse-<span class="keyword">c</span> AES<span class="number">256</span> --sse-<span class="keyword">c</span>-key fileb://sse-<span class="keyword">c</span>.key</span><br></pre></td></tr></table></figure><p>The big difference comes when you want to download the file again. Now you also have to provide the secret key.</p><figure class="highlight llvm"><table><tr><td class="code"><pre><span class="line">aws s<span class="number">3</span> cp s<span class="number">3</span>://bucket-name/sse-<span class="keyword">c</span> path/<span class="keyword">to</span>/local.file --sse-<span class="keyword">c</span> AES<span class="number">256</span> --sse-<span class="keyword">c</span>-key fileb://sse-<span class="keyword">c</span>.key</span><br></pre></td></tr></table></figure><p>If you lose the key, you can not retrieve the data from S3 anymore!</p><h2 id="Client-side-encryption"><a href="#Client-side-encryption" class="headerlink" title="Client-side encryption"></a>Client-side encryption</h2><p>Client-side encryption means that you encrypt the data before you send it to AWS. It also means that you decrypt the data that you retrieve from AWS. Usually, client-side encryption needs to be deeply embedded into your application.</p><h3 id="AWS-SDK-KMS"><a href="#AWS-SDK-KMS" class="headerlink" title="AWS SDK + KMS"></a>AWS SDK + KMS</h3><p>You can use the AWS SDK to upload&#x2F;download files from S3. The KMS service can generate data keys that you can use for encryption&#x2F;decryption. The data key itself is encrypted using the KMS Customer Master Key. If you want to use the encrypted data key, you have to send the encrypted data to the KMS service and ask for decryption. The decrypted data key is only returned if the CMK is still available and you have permissions to use it.</p><p>I implemented client-side encryption using the AWS SDK for Node.js. The encrypted key is uploaded together with the object to reduce the risk of losing the data key for a particular object. Keep in mind that the implementation is not very efficient since you will make a call to the KMS service for every encrypt and decrypt operation. It could make sense to keep the data keys in memory and reuse them for multiple objects.</p><p>You can find the full source code on <a href="https://github.com/widdix/s3-at-rest-encryption" target="_blank" rel="noopener">GitHub</a>.</p><p>Let’s dive into the code.</p><p>First, we need a way to create an encrypted data key. The encrypted data key is stored on disk as a performance optimization. Multiple encrypt calls reuse the data key as long as you do not remove or override the file <code>data.key</code> in your current working directory.</p><figure class="highlight javascript"><figcaption><span>create</span><a href="https://github.com/widdix/s3-at-rest-encryption/blob/master/kms.js">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> util = <span class="built_in">require</span>(<span class="string">&#x27;util&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> kms = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title function_">KMS</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2014-11-01&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">TEMP_DATA_KEY_FILE_NAME</span> = <span class="string">&#x27;data.key&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> writeFile = util.<span class="title function_">promisify</span>(fs.<span class="property">writeFile</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">create</span> = <span class="title function_">async</span> (kmsKeyId) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> data = <span class="keyword">await</span> kms.<span class="title function_">generateDataKey</span>(&#123;</span><br><span class="line">    <span class="title class_">KeyId</span>: kmsKeyId,</span><br><span class="line">    <span class="title class_">KeySpec</span>: <span class="string">&#x27;AES_256&#x27;</span></span><br><span class="line">  &#125;).<span class="title function_">promise</span>();</span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">writeFile</span>(<span class="variable constant_">TEMP_DATA_KEY_FILE_NAME</span>, data.<span class="property">CiphertextBlob</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="variable constant_">TEMP_DATA_KEY_FILE_NAME</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>You created a data key, but it is encrypted. Before you can use the data key, you have to decrypt it first.</p><figure class="highlight javascript"><figcaption><span>getDecryptedDataKeyBuffer</span><a href="https://github.com/widdix/s3-at-rest-encryption/blob/master/kms.js">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line">  <span class="keyword">const</span> <span class="title function_">getDecryptedDataKeyBuffer</span> = <span class="keyword">async</span> (<span class="params">encryptedKeyBuffer</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> data = <span class="keyword">await</span> kms.<span class="title function_">decrypt</span>(&#123;<span class="title class_">CiphertextBlob</span>: encryptedKeyBuffer&#125;).<span class="title function_">promise</span>();</span><br><span class="line">  <span class="keyword">return</span> data.<span class="property">Plaintext</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>The AES algorithm that you will use for encryption relies on an <a href="https://en.wikipedia.org/wiki/Initialization_vector" target="_blank" rel="noopener">initialization vector (IV)</a>. The IV is generated randomly and ensures that similar data results in very different ciphertext. The IV is also needed when decrypting the ciphertext.</p><figure class="highlight javascript"><figcaption><span>generateIVBuffer</span><a href="https://github.com/widdix/s3-at-rest-encryption/blob/master/kms.js">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> crypto = <span class="built_in">require</span>(<span class="string">&#x27;crypto&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">IV_LENGTH</span> = <span class="number">8</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">generateIVBuffer</span> = (<span class="params">keyBuffer</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> salt = crypto.<span class="title function_">randomBytes</span>(<span class="number">16</span>);</span><br><span class="line">  <span class="keyword">const</span> iv = crypto.<span class="title function_">pbkdf2Sync</span>(keyBuffer, salt, <span class="number">100000</span>, <span class="variable constant_">IV_LENGTH</span>, <span class="string">&#x27;sha512&#x27;</span>);</span><br><span class="line">  <span class="keyword">return</span> iv;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>Last but not least, we need a way to parse S3 URIs (e.g., <code>s3://bucket-name/key</code>) that are used to specify the location on S3.</p><figure class="highlight javascript"><figcaption><span>parseS3Uri</span><a href="https://github.com/widdix/s3-at-rest-encryption/blob/master/kms.js">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> url = <span class="built_in">require</span>(<span class="string">&#x27;url&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">parseS3Uri</span> = (<span class="params">uri</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> u = <span class="keyword">new</span> url.<span class="title function_">URL</span>(uri);</span><br><span class="line">  <span class="keyword">if</span> (u.<span class="property">protocol</span> !== <span class="string">&#x27;s3:&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;invalid S3 URI&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="title class_">Bucket</span>: u.<span class="property">hostname</span>,</span><br><span class="line">    <span class="title class_">Key</span>: u.<span class="property">pathname</span></span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>You might be impatient to see the implementation of the encryption. The encrypted data key is stored together with the IV and the file’s content on S3. You also add a small (8 bytes) header at the beginning of the file to add some metadata that you need for decryption. The idea to store the encrypted data key together with the encrypted data is called envelope encryption.</p><figure class="highlight javascript"><figcaption><span>encrypt</span><a href="https://github.com/widdix/s3-at-rest-encryption/blob/master/kms.js">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> s3 = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title function_">S3</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2006-03-01&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">HEADER_LENGTH</span> = <span class="number">8</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> readFile = util.<span class="title function_">promisify</span>(fs.<span class="property">readFile</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">encrypt</span> = <span class="title function_">async</span> (inputFile, s3Uri) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> encryptedKeyBuffer = <span class="keyword">await</span> <span class="title function_">readFile</span>(<span class="variable constant_">TEMP_DATA_KEY_FILE_NAME</span>);</span><br><span class="line">  <span class="keyword">const</span> decryptedKeyBuffer = <span class="keyword">await</span> <span class="title function_">getDecryptedDataKeyBuffer</span>(encryptedKeyBuffer);</span><br><span class="line">  <span class="keyword">const</span> plainBuffer = <span class="keyword">await</span> <span class="title function_">readFile</span>(inputFile);</span><br><span class="line">  <span class="keyword">const</span> ivBuffer = <span class="title function_">generateIVBuffer</span>(decryptedKeyBuffer);</span><br><span class="line">  <span class="keyword">const</span> cipher = crypto.<span class="title function_">createCipheriv</span>(<span class="string">&#x27;aes256&#x27;</span>, decryptedKeyBuffer, ivBuffer.<span class="title function_">toString</span>(<span class="string">&#x27;hex&#x27;</span>));</span><br><span class="line">  <span class="keyword">const</span> headerBuffer = <span class="title class_">Buffer</span>.<span class="title function_">alloc</span>(<span class="variable constant_">HEADER_LENGTH</span>);</span><br><span class="line">  headerBuffer.<span class="title function_">writeUInt8</span>(<span class="number">1</span>, <span class="number">0</span>); <span class="comment">// header version</span></span><br><span class="line">  headerBuffer.<span class="title function_">writeUInt8</span>(<span class="number">0</span>, <span class="number">1</span>); <span class="comment">// reserved for future use</span></span><br><span class="line">  headerBuffer.<span class="title function_">writeUInt8</span>(<span class="number">0</span>, <span class="number">2</span>); <span class="comment">// reserved for future use</span></span><br><span class="line">  headerBuffer.<span class="title function_">writeUInt8</span>(<span class="number">0</span>, <span class="number">3</span>); <span class="comment">// reserved for future use</span></span><br><span class="line">  headerBuffer.<span class="title function_">writeUInt32LE</span>(encryptedKeyBuffer.<span class="property">length</span>, <span class="number">4</span>); <span class="comment">// length of encrypted data key</span></span><br><span class="line">  <span class="keyword">const</span> bodyBuffer = <span class="title class_">Buffer</span>.<span class="title function_">concat</span>([headerBuffer, encryptedKeyBuffer, ivBuffer, cipher.<span class="title function_">update</span>(plainBuffer), cipher.<span class="title function_">final</span>()]);</span><br><span class="line">  <span class="keyword">const</span> params = <span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123;&#125;, <span class="title function_">parseS3Uri</span>(s3Uri), &#123;<span class="title class_">Body</span>: bodyBuffer&#125;);</span><br><span class="line">  <span class="keyword">await</span> s3.<span class="title function_">putObject</span>(params).<span class="title function_">promise</span>();</span><br><span class="line">  <span class="keyword">return</span> s3Uri;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>A encrypted object can not be uploaded to S3. Let’s look at the reverse operation:</p><figure class="highlight javascript"><figcaption><span>decrypt</span><a href="https://github.com/widdix/s3-at-rest-encryption/blob/master/kms.js">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">exports</span>.<span class="property">decrypt</span> = <span class="title function_">async</span> (s3Uri, outputFile) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> params = <span class="title function_">parseS3Uri</span>(s3Uri);</span><br><span class="line">  <span class="keyword">const</span> object = <span class="keyword">await</span> s3.<span class="title function_">getObject</span>(params).<span class="title function_">promise</span>();</span><br><span class="line">  <span class="keyword">const</span> bodyBuffer = object.<span class="property">Body</span>;</span><br><span class="line">  <span class="keyword">const</span> headerBuffer = bodyBuffer.<span class="title function_">slice</span>(<span class="number">0</span>, <span class="variable constant_">HEADER_LENGTH</span>);</span><br><span class="line">  <span class="keyword">const</span> headerVersion = headerBuffer.<span class="title function_">readUInt8</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="keyword">if</span> (headerVersion !== <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;Unsupported header version&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> encryptedKeyLength = headerBuffer.<span class="title function_">readUInt32LE</span>(<span class="number">4</span>);</span><br><span class="line">  <span class="keyword">const</span> encryptedKeyBuffer = bodyBuffer.<span class="title function_">slice</span>(<span class="number">8</span>, <span class="number">8</span> + encryptedKeyLength);</span><br><span class="line">  <span class="keyword">const</span> decryptedKeyBuffer = <span class="keyword">await</span> <span class="title function_">getDecryptedDataKeyBuffer</span>(encryptedKeyBuffer);</span><br><span class="line">  <span class="keyword">const</span> ivBuffer = bodyBuffer.<span class="title function_">slice</span>(<span class="number">8</span> + encryptedKeyLength, <span class="number">8</span> + encryptedKeyLength + <span class="variable constant_">IV_LENGTH</span>);</span><br><span class="line">  <span class="keyword">const</span> decipher = crypto.<span class="title function_">createDecipheriv</span>(<span class="string">&#x27;aes256&#x27;</span>, decryptedKeyBuffer, ivBuffer.<span class="title function_">toString</span>(<span class="string">&#x27;hex&#x27;</span>));</span><br><span class="line">  <span class="keyword">const</span> decryptedBuffer = <span class="title class_">Buffer</span>.<span class="title function_">concat</span>([decipher.<span class="title function_">update</span>(bodyBuffer.<span class="title function_">slice</span>(<span class="number">8</span> + encryptedKeyLength + <span class="variable constant_">IV_LENGTH</span>)) , decipher.<span class="title function_">final</span>()]);</span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">writeFile</span>(outputFile, decryptedBuffer);</span><br><span class="line">  <span class="keyword">return</span> outputFile;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>You can find the full source code on <a href="https://github.com/widdix/s3-at-rest-encryption" target="_blank" rel="noopener">GitHub</a>.</p><h3 id="AWS-SDK-self-managed-secret"><a href="#AWS-SDK-self-managed-secret" class="headerlink" title="AWS SDK + self-managed secret"></a>AWS SDK + self-managed secret</h3><p>You likely need an HSM device could be <a href="https://aws.amazon.com/cloudhsm/" target="_blank" rel="noopener">AWS CloudHSM</a> implementing <a href="https://en.wikipedia.org/wiki/PKCS_11" target="_blank" rel="noopener">PKCS11</a> to implement the same idea as described above using AWS KMS.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>S3 offers a bunch of options to encrypt your data at rest. Usually, the criticality of your data determines the options you can choose from. Is it okay if AWS technically sees your raw data? If yes, server-side encryption is the right option for you. If not, go with client-side encryption. Keep in mind that client-side encryption requires know-how and is more effort to implement compared to server-side encryption. The <a href="https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/introduction.html" target="_blank" rel="noopener">AWS Encryption SDKs</a> (Java and python) might help to implement client-side encryption.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Restricting Access to EC2 Instances Based on Tags</title>
      <link>https://cloudonaut.io/restricting-access-to-ec2-instances-based-on-tags/</link>
      <description>
        <![CDATA[<p>The <a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege" target="_blank" rel="noopener">principle of least privilege</a>]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <category domain="https://cloudonaut.io/tag/kms/">kms</category>
      <guid isPermaLink="true">https://cloudonaut.io/restricting-access-to-ec2-instances-based-on-tags/</guid>
      <pubDate>Tue, 07 Aug 2018 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The <a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege" target="_blank" rel="noopener">principle of least privilege</a> is key when it comes to securing your infrastructure on AWS. For example, an engineer should only be able to control EC2 instances that are in scope for her day-to-day work. But how do you make sure an engineer is only allowed to … </p><ul><li>start, stop, and terminate specific instance?</li><li>create, attach and delete specific volumes?</li><li>create, restore and delete specific snapshots?</li></ul><p>As illustrated in the following figure you can restrict access to EC2 instances, EBS volumes, and EBS snapshots by making use of …</p><ul><li>tags attached to EC2 instances, EBS volumes, and EBS snapshots.</li><li>an IAM role or user to authenticate an engineer.</li><li>an IAM policy restricting access to the EC2 instances, EBS volumes, and EBS snapshots based on tags.</li><li>a customer-managed CMK (KMS) to encrypt and decrypt data stored on EBS volumes and snapshots.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/08/restricting-access-to-ec2-instances-based-on-tags@730w.webp 730w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@730w2x.webp 1460w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@610w.webp 610w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@610w2x.webp 1220w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@450w.webp 450w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@450w2x.webp 900w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@330w.webp 330w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@330w2x.webp 660w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@545w.webp 545w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/08/restricting-access-to-ec2-instances-based-on-tags@730w.png 730w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@730w2x.png 1460w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@610w.png 610w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@610w2x.png 1220w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@450w.png 450w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@450w2x.png 900w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@330w.png 330w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@330w2x.png 660w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@545w.png 545w, /images/2018/08/restricting-access-to-ec2-instances-based-on-tags@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/08/restricting-access-to-ec2-instances-based-on-tags.png" alt="Restricting Access to EC2 Instances Based on Tags" title="Restricting Access to EC2 Instances Based on Tags"></picture></p><p>Attach the following IAM policy to a user or role to restrict access to instances, volumes, and snapshots based on a tag.</p><p>Replace <code>&lt;TAG_KEY&gt;</code> with the key you want to use for the tag (e.g. <code>owner</code>). Replace <code>&lt;TAG_VALUE&gt;</code> with an identifier of the user or role (e.g. <code>andreas</code>). Replace <code>&lt;KMS_KEY_ARN&gt;</code> with the ARN of the customer-managed CMK which only the user or role is allowed to use.</p><p>** Note: I’ve added <code># comments</code> to the IAM policy to explain the details. Please remove all <code># comments</code> before making use of the policy. The JSON is not valid otherwise.**</p><figure class="highlight applescript"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2012-10-17&quot;</span>,</span><br><span class="line">    <span class="string">&quot;Statement&quot;</span>: [</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment"># Allow all actions we don&#x27;t want to or cannot restrict by a tag.</span></span><br><span class="line">            <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Action&quot;</span>: [</span><br><span class="line">                <span class="string">&quot;ec2:AcceptReservedInstancesExchangeQuote&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AcceptVpcEndpointConnections&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AcceptVpcPeeringConnection&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AllocateAddress&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AllocateHosts&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AssignIpv6Addresses&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AssignPrivateIpAddresses&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AssociateAddress&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AssociateDhcpOptions&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AssociateIamInstanceProfile&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AssociateRouteTable&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AssociateSubnetCidrBlock&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AssociateVpcCidrBlock&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AttachClassicLinkVpc&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AttachInternetGateway&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AttachNetworkInterface&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AttachVpnGateway&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AuthorizeSecurityGroupEgress&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AuthorizeSecurityGroupIngress&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:BundleInstance&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CancelBundleTask&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CancelConversionTask&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CancelExportTask&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CancelImportTask&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CancelReservedInstancesListing&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CancelSpotFleetRequests&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CancelSpotInstanceRequests&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ConfirmProductInstance&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CopyFpgaImage&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CopyImage&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CopySnapshot&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateCustomerGateway&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateDefaultSubnet&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateDefaultVpc&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateDhcpOptions&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateEgressOnlyInternetGateway&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateFlowLogs&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateFpgaImage&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateImage&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateInstanceExportTask&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateInternetGateway&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateKeyPair&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateLaunchTemplate&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateLaunchTemplateVersion&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateNatGateway&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateNetworkAcl&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateNetworkAclEntry&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateNetworkInterface&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateNetworkInterfacePermission&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreatePlacementGroup&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateReservedInstancesListing&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateRoute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateRouteTable&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateSecurityGroup&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateSpotDatafeedSubscription&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateSubnet&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateVpc&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateVpcEndpoint&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateVpcEndpointConnectionNotification&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateVpcEndpointServiceConfiguration&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateVpcPeeringConnection&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateVpnConnection&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateVpnConnectionRoute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateVpnGateway&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteCustomerGateway&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteDhcpOptions&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteEgressOnlyInternetGateway&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteFlowLogs&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteFpgaImage&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteInternetGateway&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteKeyPair&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteLaunchTemplate&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteLaunchTemplateVersions&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteNatGateway&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteNetworkAcl&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteNetworkAclEntry&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteNetworkInterface&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteNetworkInterfacePermission&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeletePlacementGroup&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteRoute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteRouteTable&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteSecurityGroup&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteSpotDatafeedSubscription&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteSubnet&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteVpc&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteVpcEndpointConnectionNotifications&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteVpcEndpointServiceConfigurations&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteVpcEndpoints&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteVpcPeeringConnection&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteVpnConnection&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteVpnConnectionRoute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteVpnGateway&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeregisterImage&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeAccountAttributes&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeAddresses&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeAvailabilityZones&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeBundleTasks&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeClassicLinkInstances&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeConversionTasks&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeCustomerGateways&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeDhcpOptions&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeEgressOnlyInternetGateways&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeElasticGpus&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeExportTasks&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeFlowLogs&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeFpgaImageAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeFpgaImages&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeHostReservationOfferings&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeHostReservations&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeHosts&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeIamInstanceProfileAssociations&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeIdFormat&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeIdentityIdFormat&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeImageAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeImages&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeImportImageTasks&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeImportSnapshotTasks&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeInstanceAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeInstanceCreditSpecifications&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeInstanceStatus&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeInstances&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeInternetGateways&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeKeyPairs&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeLaunchTemplateVersions&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeLaunchTemplates&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeMovingAddresses&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeNatGateways&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeNetworkAcls&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeNetworkInterfaceAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeNetworkInterfacePermissions&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeNetworkInterfaces&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribePlacementGroups&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribePrefixLists&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeRegions&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeReservedInstances&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeReservedInstancesListings&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeReservedInstancesModifications&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeReservedInstancesOfferings&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeRouteTables&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeScheduledInstanceAvailability&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeScheduledInstances&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeSecurityGroupReferences&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeSecurityGroups&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeSnapshotAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeSnapshots&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeSpotDatafeedSubscription&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeSpotFleetInstances&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeSpotFleetRequestHistory&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeSpotFleetRequests&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeSpotInstanceRequests&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeSpotPriceHistory&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeStaleSecurityGroups&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeSubnets&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeTags&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVolumeAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVolumeStatus&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVolumes&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVolumesModifications&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVpcAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVpcClassicLink&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVpcClassicLinkDnsSupport&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVpcEndpointConnectionNotifications&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVpcEndpointConnections&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVpcEndpointServiceConfigurations&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVpcEndpointServicePermissions&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVpcEndpointServices&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVpcEndpoints&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVpcPeeringConnections&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVpcs&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVpnConnections&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DescribeVpnGateways&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DetachClassicLinkVpc&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DetachInternetGateway&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DetachNetworkInterface&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DetachVpnGateway&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DisableVgwRoutePropagation&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DisableVpcClassicLink&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DisableVpcClassicLinkDnsSupport&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DisassociateAddress&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DisassociateIamInstanceProfile&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DisassociateRouteTable&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DisassociateSubnetCidrBlock&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DisassociateVpcCidrBlock&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:EnableVgwRoutePropagation&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:EnableVolumeIO&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:EnableVpcClassicLink&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:EnableVpcClassicLinkDnsSupport&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:GetConsoleOutput&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:GetConsoleScreenshot&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:GetHostReservationPurchasePreview&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:GetLaunchTemplateData&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:GetPasswordData&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:GetReservedInstancesExchangeQuote&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ImportImage&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ImportInstance&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ImportKeyPair&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ImportSnapshot&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ImportVolume&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyFpgaImageAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyHosts&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyIdFormat&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyIdentityIdFormat&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyImageAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyInstanceAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyInstanceCreditSpecification&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyInstancePlacement&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyLaunchTemplate&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyNetworkInterfaceAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyReservedInstances&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifySnapshotAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifySpotFleetRequest&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifySubnetAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyVolume&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyVolumeAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyVpcAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyVpcEndpoint&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyVpcEndpointConnectionNotification&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyVpcEndpointServiceConfiguration&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyVpcEndpointServicePermissions&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyVpcPeeringConnectionOptions&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ModifyVpcTenancy&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:MonitorInstances&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:MoveAddressToVpc&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:PurchaseHostReservation&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:PurchaseReservedInstancesOffering&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:PurchaseScheduledInstances&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:RegisterImage&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:RejectVpcEndpointConnections&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:RejectVpcPeeringConnection&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ReleaseAddress&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ReleaseHosts&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ReplaceIamInstanceProfileAssociation&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ReplaceNetworkAclAssociation&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ReplaceNetworkAclEntry&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ReplaceRoute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ReplaceRouteTableAssociation&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ReportInstanceStatus&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:RequestSpotFleet&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:RequestSpotInstances&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ResetFpgaImageAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ResetImageAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ResetInstanceAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ResetNetworkInterfaceAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:ResetSnapshotAttribute&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:RestoreAddressToClassic&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:RevokeSecurityGroupEgress&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:RevokeSecurityGroupIngress&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:RunScheduledInstances&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:UnassignIpv6Addresses&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:UnassignPrivateIpAddresses&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:UnmonitorInstances&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:UpdateSecurityGroupRuleDescriptionsEgress&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:UpdateSecurityGroupRuleDescriptionsIngress&quot;</span></span><br><span class="line">            ],</span><br><span class="line">            <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;*&quot;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment"># Allow launching an instance, but only if the tag is set.</span></span><br><span class="line">            <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Action&quot;</span>: <span class="string">&quot;ec2:RunInstances&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Resource&quot;</span>: [</span><br><span class="line">                <span class="string">&quot;arn:aws:ec2:*:*:volume/*&quot;</span>,</span><br><span class="line">                <span class="string">&quot;arn:aws:ec2:*:*:instance/*&quot;</span></span><br><span class="line">            ],</span><br><span class="line">            <span class="string">&quot;Condition&quot;</span>: &#123;</span><br><span class="line">                <span class="string">&quot;StringEquals&quot;</span>: &#123;</span><br><span class="line">                    <span class="string">&quot;aws:RequestTag/&lt;TAG_KEY&gt;&quot;</span>: <span class="string">&quot;&lt;TAG_VALUE&gt;&quot;</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment"># Allow actions we can&#x27;t restrict by using tags.</span></span><br><span class="line">            <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Action&quot;</span>: <span class="string">&quot;ec2:RunInstances&quot;</span>,</span><br><span class="line">            <span class="string">&quot;NotResource&quot;</span>: [</span><br><span class="line">                <span class="string">&quot;arn:aws:ec2:*:*:volume/*&quot;</span>,</span><br><span class="line">                <span class="string">&quot;arn:aws:ec2:*:*:instance/*&quot;</span></span><br><span class="line">            ]</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment"># Allow creating a volume, but only if the tag is set.</span></span><br><span class="line">            <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Action&quot;</span>: <span class="string">&quot;ec2:CreateVolume&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;*&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Condition&quot;</span>: &#123;</span><br><span class="line">                <span class="string">&quot;StringEquals&quot;</span>: &#123;</span><br><span class="line">                    <span class="string">&quot;aws:RequestTag/&lt;TAG_KEY&gt;&quot;</span>: <span class="string">&quot;&lt;TAG_VALUE&gt;&quot;</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment"># Allow creating tags only when creating a volume, launching an instance, or creating a snapshot.</span></span><br><span class="line">            <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Action&quot;</span>: [</span><br><span class="line">                <span class="string">&quot;ec2:CreateTags&quot;</span></span><br><span class="line">            ],</span><br><span class="line">            <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;*&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Condition&quot;</span>: &#123;</span><br><span class="line">                <span class="string">&quot;StringEquals&quot;</span>: &#123;</span><br><span class="line">                    <span class="string">&quot;ec2:CreateAction&quot;</span>: [</span><br><span class="line">                        <span class="string">&quot;CreateVolume&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;RunInstances&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;CreateSnapshot&quot;</span></span><br><span class="line">                    ]</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment"># Allow actions when the resource is tagged.</span></span><br><span class="line">            <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Action&quot;</span>: [</span><br><span class="line">                <span class="string">&quot;ec2:StartInstances&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:StopInstances&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:RebootInstances&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:TerminateInstances&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:CreateTags&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteTags&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:AttachVolume&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DetachVolume&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteVolume&quot;</span>,</span><br><span class="line">                <span class="string">&quot;ec2:DeleteSnapshot&quot;</span></span><br><span class="line">            ],</span><br><span class="line">            <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;*&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Condition&quot;</span>: &#123;</span><br><span class="line">                <span class="string">&quot;StringEquals&quot;</span>: &#123;</span><br><span class="line">                    <span class="string">&quot;ec2:ResourceTag/Owner&quot;</span>: <span class="string">&quot;&lt;TAG_VALUE&gt;&quot;</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment"># Allow creating a snapshot, but only if the tag is set.</span></span><br><span class="line">            <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Action&quot;</span>: <span class="string">&quot;ec2:CreateSnapshot&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Resource&quot;</span>: [</span><br><span class="line">                <span class="string">&quot;arn:aws:ec2:*:*:snapshot/*&quot;</span></span><br><span class="line">            ],</span><br><span class="line">            <span class="string">&quot;Condition&quot;</span>: &#123;</span><br><span class="line">                <span class="string">&quot;StringEquals&quot;</span>: &#123;</span><br><span class="line">                    <span class="string">&quot;aws:RequestTag/&lt;TAG_KEY&gt;&quot;</span>: <span class="string">&quot;&lt;TAG_VALUE&gt;&quot;</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment"># Allow creating a snapshot, but only if volume matches the tag.</span></span><br><span class="line">            <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Action&quot;</span>: <span class="string">&quot;ec2:CreateSnapshot&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Resource&quot;</span>: [</span><br><span class="line">                <span class="string">&quot;arn:aws:ec2:*:*:volume/*&quot;</span></span><br><span class="line">            ],</span><br><span class="line">            <span class="string">&quot;Condition&quot;</span>: &#123;</span><br><span class="line">                <span class="string">&quot;StringEquals&quot;</span>: &#123;</span><br><span class="line">                    <span class="string">&quot;ec2:ResourceTag/Owner&quot;</span>: <span class="string">&quot;&lt;TAG_VALUE&gt;&quot;</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment"># Grant access to the customer-managed CMK.</span></span><br><span class="line">            <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">            <span class="string">&quot;Action&quot;</span>: [</span><br><span class="line">                <span class="string">&quot;kms:Encrypt&quot;</span>,</span><br><span class="line">                <span class="string">&quot;kms:Decrypt&quot;</span>,</span><br><span class="line">                <span class="string">&quot;kms:ReEncrypt*&quot;</span>,</span><br><span class="line">                <span class="string">&quot;kms:GenerateDataKey*&quot;</span>,</span><br><span class="line">                <span class="string">&quot;kms:DescribeKey&quot;</span></span><br><span class="line">            ],</span><br><span class="line">            <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;&lt;KMS_KEY_ARN&gt;&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>As the IAM policy references a tag and a customer-managed CMK, it is necessary to use a separate policy per user or role.</p><blockquote><p>Please note, AWS extended the IAM conditions for EC2 heavily during the past months and years. The following information is no longer correct.</p></blockquote><p>Why do we need to use a customer-managed CMK? Because it is not possible to restrict who can restore an EBS snapshot by using tags. Therefore, any engineer could access the data stored on any snapshot. Encrypting all volumes and snapshots with a customer-managed CMK (KMS) is a workaround allowing you to control very fine-granular which engineers can access data stored on volumes and snapshots.</p><p>It is also important to note, that it is not all action support conditions by tags. For example, it is not possible to restrict engineers from attaching a networking interface (<code>ec2:AttachNetworkInterface</code>) to any of the instances. If you want to be able to isolate your resources without any limitations, choose a multi-account strategy instead.</p><p>For details about IAM policies for EC2 go to the official <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-policies-for-amazon-ec2.html" target="_blank" rel="noopener">AWS Documentation</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Analyzing CloudTrail with Athena</title>
      <link>https://cloudonaut.io/analyzing-cloudtrail-with-athena/</link>
      <description>
        <![CDATA[<p>Which IAM users have been active within your AWS account within the last 30 days? Are all of the 999 IAM roles still in use, or can you r]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/cloudtrail/">cloudtrail</category>
      <category domain="https://cloudonaut.io/tag/athena/">athena</category>
      <guid isPermaLink="true">https://cloudonaut.io/analyzing-cloudtrail-with-athena/</guid>
      <pubDate>Fri, 27 Jul 2018 17:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Which IAM users have been active within your AWS account within the last 30 days? Are all of the 999 IAM roles still in use, or can you remove some of them to clean up your infrastructure? Is it safe to remove the action <code>s3:GetObject</code> from the IAM policy, or will something on the EC2 instance <code>i-abcdefg</code> break? Is your boss still using the root account to access your AWS account, even if you have told him to lock away the credentials?</p><p>In this post, you learn to answer these and other security and compliance related questions with the help of CloudTrail and Athena as shown in this post. As shown in the following figure CloudTrail is recording every - or almost every - request to the AWS API. For example, if an engineer launches a new EC2 instance, deletes an S3 bucket, or changes the Security Group of an RDS instance, CloudTrail records this. CloudTrail can push the recorded events to CloudWatch Logs and S3 buckets, as well. Even better, you can use a single S3 bucket to collect CloudTrail events from multiple AWS accounts. However, how to make sense out of that data? Athena is a query service allowing you to query JSON files stored on S3 easily.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/07/analyzing-cloudtrail-with-athena@730w.webp 730w, /images/2018/07/analyzing-cloudtrail-with-athena@730w2x.webp 1460w, /images/2018/07/analyzing-cloudtrail-with-athena@610w.webp 610w, /images/2018/07/analyzing-cloudtrail-with-athena@610w2x.webp 1220w, /images/2018/07/analyzing-cloudtrail-with-athena@450w.webp 450w, /images/2018/07/analyzing-cloudtrail-with-athena@450w2x.webp 900w, /images/2018/07/analyzing-cloudtrail-with-athena@330w.webp 330w, /images/2018/07/analyzing-cloudtrail-with-athena@330w2x.webp 660w, /images/2018/07/analyzing-cloudtrail-with-athena@545w.webp 545w, /images/2018/07/analyzing-cloudtrail-with-athena@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/07/analyzing-cloudtrail-with-athena@730w.png 730w, /images/2018/07/analyzing-cloudtrail-with-athena@730w2x.png 1460w, /images/2018/07/analyzing-cloudtrail-with-athena@610w.png 610w, /images/2018/07/analyzing-cloudtrail-with-athena@610w2x.png 1220w, /images/2018/07/analyzing-cloudtrail-with-athena@450w.png 450w, /images/2018/07/analyzing-cloudtrail-with-athena@450w2x.png 900w, /images/2018/07/analyzing-cloudtrail-with-athena@330w.png 330w, /images/2018/07/analyzing-cloudtrail-with-athena@330w2x.png 660w, /images/2018/07/analyzing-cloudtrail-with-athena@545w.png 545w, /images/2018/07/analyzing-cloudtrail-with-athena@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/07/analyzing-cloudtrail-with-athena.png" alt="Analyzing CloudTrail with Athena" title="Analyzing CloudTrail with Athena"></picture></p><p>I show you the necessary steps to query CloudTrail events with the help of Athena in the following. Before you start, make sure you have <a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-create-and-update-a-trail.html" target="_blank" rel="noopener">created a trail that is sending log files to S3</a>. We offer a <a href="https://templates.cloudonaut.io/en/stable/security/#cloudtrail-across-all-regions" target="_blank" rel="noopener">CloudFormation template to setup CloudTrail</a> as well.</p><h2 id="Creating-a-table-and-partitioning-data"><a href="#Creating-a-table-and-partitioning-data" class="headerlink" title="Creating a table and partitioning data"></a>Creating a table and partitioning data</h2><p>First, open <a href="https://console.aws.amazon.com/athena/home" target="_blank" rel="noopener">Athena</a> in the Management Console. Next, double check if you have switched to the region of the S3 bucket containing the CloudTrail logs to avoid unnecessary data transfer costs. Afterward, execute the following query to create a table. Make sure you are replacing <code>&lt;BUCKET&gt;</code> with the name of your S3 bucket. The query derives from the <a href="https://docs.aws.amazon.com/athena/latest/ug/cloudtrail-logs.html" target="_blank" rel="noopener">AWS documentation</a> with some advancements as discussed later.</p><figure class="highlight php"><table><tr><td class="code"><pre><span class="line">CREATE EXTERNAL TABLE <span class="title function_ invoke__">cloudtrail_logs</span> (</span><br><span class="line">eventversion STRING,</span><br><span class="line">useridentity STRUCT&lt;</span><br><span class="line">               <span class="attr">type</span>:STRING,</span><br><span class="line">               <span class="attr">principalid</span>:STRING,</span><br><span class="line">               <span class="attr">arn</span>:STRING,</span><br><span class="line">               <span class="attr">accountid</span>:STRING,</span><br><span class="line">               <span class="attr">invokedby</span>:STRING,</span><br><span class="line">               <span class="attr">accesskeyid</span>:STRING,</span><br><span class="line">               <span class="attr">userName</span>:STRING,</span><br><span class="line"><span class="attr">sessioncontext</span>:STRUCT&lt;</span><br><span class="line"><span class="attr">attributes</span>:STRUCT&lt;</span><br><span class="line">               <span class="attr">mfaauthenticated</span>:STRING,</span><br><span class="line">               <span class="attr">creationdate</span>:STRING&gt;,</span><br><span class="line"><span class="attr">sessionissuer</span>:STRUCT&lt;  </span><br><span class="line">               <span class="attr">type</span>:STRING,</span><br><span class="line">               <span class="attr">principalId</span>:STRING,</span><br><span class="line">               <span class="attr">arn</span>:STRING, </span><br><span class="line">               <span class="attr">accountId</span>:STRING,</span><br><span class="line">               <span class="attr">userName</span>:STRING&gt;&gt;&gt;,</span><br><span class="line">eventtime STRING,</span><br><span class="line">eventsource STRING,</span><br><span class="line">eventname STRING,</span><br><span class="line">awsregion STRING,</span><br><span class="line">sourceipaddress STRING,</span><br><span class="line">useragent STRING,</span><br><span class="line">errorcode STRING,</span><br><span class="line">errormessage STRING,</span><br><span class="line">requestparameters STRING,</span><br><span class="line">responseelements STRING,</span><br><span class="line">additionaleventdata STRING,</span><br><span class="line">requestid STRING,</span><br><span class="line">eventid STRING,</span><br><span class="line">resources ARRAY&lt;STRUCT&lt;</span><br><span class="line">               <span class="attr">ARN</span>:STRING,</span><br><span class="line">               <span class="attr">accountId</span>:STRING,</span><br><span class="line">               <span class="attr">type</span>:STRING&gt;&gt;,</span><br><span class="line">eventtype STRING,</span><br><span class="line">apiversion STRING,</span><br><span class="line"><span class="keyword">readonly</span> STRING,</span><br><span class="line">recipientaccountid STRING,</span><br><span class="line">serviceeventdetails STRING,</span><br><span class="line">sharedeventid STRING,</span><br><span class="line">vpcendpointid STRING</span><br><span class="line">)</span><br><span class="line">PARTITIONED <span class="title function_ invoke__">BY</span> (account <span class="keyword">string</span>, region <span class="keyword">string</span>, year <span class="keyword">string</span>)</span><br><span class="line">ROW FORMAT SERDE <span class="string">&#x27;com.amazon.emr.hive.serde.CloudTrailSerde&#x27;</span></span><br><span class="line">STORED AS INPUTFORMAT <span class="string">&#x27;com.amazon.emr.cloudtrail.CloudTrailInputFormat&#x27;</span></span><br><span class="line">OUTPUTFORMAT <span class="string">&#x27;org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat&#x27;</span></span><br><span class="line">LOCATION <span class="string">&#x27;s3://&lt;BUCKET&gt;/AWSLogs/&#x27;</span>;</span><br></pre></td></tr></table></figure><p>In general, Athena needs to process all data stored in your S3 bucket to be able to answer your queries. It is essential to reduce the processed data per query to keep costs and response times low.</p><p>CloudTrail uses the following key aka. directory structure to store log files in your S3 bucket.</p><figure class="highlight 1c"><table><tr><td class="code"><pre><span class="line"><span class="string">|-- AWSLogs</span></span><br><span class="line">    <span class="string">|-- 111111111111</span></span><br><span class="line">        <span class="string">|-- CloudTrail</span></span><br><span class="line">            <span class="string">|-- us-east-1</span></span><br><span class="line">                <span class="string">|-- 2018</span></span><br><span class="line">                    <span class="string">|-- 06</span></span><br><span class="line">                        <span class="string">|-- 01</span></span><br><span class="line">                            <span class="string">|-- 111111111111_CloudTrail_....json.gz</span></span><br><span class="line">                        <span class="string">|-- 02</span></span><br><span class="line">                        <span class="string">|-- ...</span></span><br><span class="line">                    <span class="string">|-- 07</span></span><br><span class="line">                    <span class="string">|-- ...</span></span><br><span class="line">            <span class="string">|-- us-east-2</span></span><br><span class="line">            <span class="string">|-- ...</span></span><br><span class="line">    <span class="string">|-- 222222222222</span></span><br><span class="line">    <span class="string">|-- ...</span></span><br></pre></td></tr></table></figure><p>That is why the table you have created is partitioned by the following attributes:</p><ul><li><code>account</code>: the AWS account ID (e.g., <code>111111111111</code>)</li><li><code>region</code>: the region (e.g., <code>us-east-1</code>)</li><li><code>year</code>: the year (e.g., <code>2018</code>)</li></ul><p>To be able to query data from your table, you need to add partitions. Do so, by executing the following query for each partition you want to add. Replace <code>&lt;BUCKET&gt;</code> with the name of your S3 bucket, <code>&lt;ACCOUNT&gt;</code> with the AWS account ID, <code>&lt;REGION&gt;</code> with the region, and <code>&lt;YEAR&gt;</code> with the year of the partition.</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">ALTER TABLE cloudtrail_logs </span><br><span class="line"><span class="built_in">ADD</span> PARTITION (<span class="attribute">account</span>=<span class="string">&#x27;&lt;ACCOUNT&gt;&#x27;</span>, <span class="attribute">region</span>=<span class="string">&#x27;&lt;REGION&gt;&#x27;</span>, <span class="attribute">year</span>=<span class="string">&#x27;&lt;YEAR&gt;&#x27;</span>) </span><br><span class="line">LOCATION <span class="string">&#x27;s3://&lt;BUCKET&gt;/AWSLogs/&lt;ACCOUNT&gt;/CloudTrail/&lt;REGION&gt;/&lt;YEAR&gt;/&#x27;</span>;</span><br></pre></td></tr></table></figure><p>Repeat the process of adding partitions to your table until the data for all accounts, regions, and years is accessible through the table. Have a look at <a href="https://medium.com/@alsmola/partitioning-cloudtrail-logs-in-athena-29add93ee070" target="_blank" rel="noopener">Partitioning CloudTrail Logs in Athena</a> if you are looking for a way to automate that process.</p><p>If the amount of data stored in your S3 bucket is below 1 GB you could also create a table without partitions. To do so remove the line <code>PARTITIONED BY (account string, region string, year string)</code> from the <code>CREATE EXTERNAL TABLE</code> statement.</p><h2 id="Querying-CloudTrail-logs"><a href="#Querying-CloudTrail-logs" class="headerlink" title="Querying CloudTrail logs"></a>Querying CloudTrail logs</h2><p>Finally, you are ready to analyze the data. The following queries demonstrate how you can query CloudTrail events. Replace <code>&lt;ACCOUNT&gt;</code> with an AWS account ID you and <code>&lt;YEAR&gt;</code> with the current year.</p><h3 id="Which-IAM-roles-have-been-active-within-the-last-7-days"><a href="#Which-IAM-roles-have-been-active-within-the-last-7-days" class="headerlink" title="Which IAM roles have been active within the last 7 days?"></a>Which IAM roles have been active within the last 7 days?</h3><p>Execute the following query to get a list of IAM roles (ARN) that have been sending requests to the AWS API within the last 7 days. Replace <code>&lt;ACCOUNT&gt;</code> with an AWS account ID you and <code>&lt;YEAR&gt;</code> with the current year.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">DISTINCT</span> useridentity.sessioncontext.sessionissuer.arn </span><br><span class="line"><span class="keyword">FROM</span> cloudtrail_logs </span><br><span class="line"><span class="keyword">WHERE</span> year = <span class="string">&#x27;&lt;YEAR&gt;&#x27;</span> <span class="keyword">AND</span> account = <span class="string">&#x27;&lt;ACCOUNT&gt;&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> useridentity.<span class="keyword">type</span> = <span class="string">&#x27;AssumedRole&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> from_iso8601_timestamp(eventtime) &gt; date_add(<span class="string">&#x27;day&#x27;</span>, <span class="number">-7</span>, now());</span><br></pre></td></tr></table></figure><h3 id="Which-IAM-users-have-been-active-within-the-last-30-days"><a href="#Which-IAM-users-have-been-active-within-the-last-30-days" class="headerlink" title="Which IAM users have been active within the last 30 days?"></a>Which IAM users have been active within the last 30 days?</h3><p>A similar query allows you to find out which IAM users have been active in an account within the last 30 days. Replace <code>&lt;ACCOUNT&gt;</code> with an AWS account ID you and <code>&lt;YEAR&gt;</code> with the current year.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">DISTINCT</span> useridentity.arn </span><br><span class="line"><span class="keyword">FROM</span> cloudtrail_logs </span><br><span class="line"><span class="keyword">WHERE</span> year = <span class="string">&#x27;&lt;YEAR&gt;&#x27;</span> <span class="keyword">AND</span> account = <span class="string">&#x27;&lt;ACCOUNT&gt;&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> useridentity.<span class="keyword">type</span> = <span class="string">&#x27;IAMUser&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> useridentity.arn <span class="keyword">IS</span> <span class="keyword">NOT</span> <span class="keyword">NULL</span> </span><br><span class="line"><span class="keyword">AND</span> from_iso8601_timestamp(eventtime) &gt; date_add(<span class="string">&#x27;day&#x27;</span>, <span class="number">-30</span>, now());</span><br></pre></td></tr></table></figure><h3 id="Was-the-root-user-accessing-the-account-within-the-last-7-days"><a href="#Was-the-root-user-accessing-the-account-within-the-last-7-days" class="headerlink" title="Was the root user accessing the account within the last 7 days?"></a>Was the root user accessing the account within the last 7 days?</h3><p>You should not use the root user for day to day work due to increase security. If the following query results with a <code>0</code> you are fine. If the result is larger than zero, you should find out who is using your root user credentials. Replace <code>&lt;ACCOUNT&gt;</code> with an AWS account ID you and <code>&lt;YEAR&gt;</code> with the current year.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">DISTINCT</span> COUNT(*) <span class="keyword">FROM</span> cloudtrail_logs </span><br><span class="line"><span class="keyword">WHERE</span> year = <span class="string">&#x27;&lt;YEAR&gt;&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> account = <span class="string">&#x27;&lt;ACCOUNT&gt;&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> useridentity.<span class="keyword">type</span> = <span class="string">&#x27;Root&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> useridentity.invokedby != <span class="string">&#x27;support.amazonaws.com&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> from_iso8601_timestamp(eventtime) &gt; date_add(<span class="string">&#x27;day&#x27;</span>, <span class="number">-7</span>, now());</span><br></pre></td></tr></table></figure><h3 id="Which-services-and-actions-is-an-IAM-role-accessing"><a href="#Which-services-and-actions-is-an-IAM-role-accessing" class="headerlink" title="Which services and actions is an IAM role accessing?"></a>Which services and actions is an IAM role accessing?</h3><p>Following the least privilege principle when creating IAM roles and attaching policies is another crucial security best practice on AWS. However, how do you know if you can remove an action from the policy attached to a role? That’s simple. The following query returns the services and actions accessed by an IAM role. Replace <code>&lt;ACCOUNT&gt;</code> with an AWS account ID you, <code>&lt;YEAR&gt;</code> with the current year, and <ROLE_ARN> with the ARN of the IAM role.</p><figure class="highlight n1ql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">DISTINCT</span> eventsource, eventname <span class="keyword">FROM</span> cloudtrail_logs </span><br><span class="line"><span class="keyword">WHERE</span> year = <span class="string">&#x27;&lt;YEAR&gt;&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> account = <span class="string">&#x27;&lt;ACCOUNT&gt;&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> (useridentity.sessioncontext.sessionissuer.arn = <span class="string">&#x27;&lt;ROLE_ARN&gt;&#x27;</span>) </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> eventsource, eventname;</span><br></pre></td></tr></table></figure><h3 id="Which-IAM-users-have-logged-into-the-Management-Console-within-the-last-30-days"><a href="#Which-IAM-users-have-logged-into-the-Management-Console-within-the-last-30-days" class="headerlink" title="Which IAM users have logged into the Management Console within the last 30 days?"></a>Which IAM users have logged into the Management Console within the last 30 days?</h3><p>Do you want to find out which users have logged into the Management Console within the last 30 days? Here is your query. Replace <code>&lt;ACCOUNT&gt;</code> with an AWS account ID you and <code>&lt;YEAR&gt;</code> with the current year.</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">DISTINCT</span> useridentity.arn <span class="keyword">FROM</span> cloudtrail_logs </span><br><span class="line"><span class="keyword">WHERE</span> <span class="keyword">year</span> <span class="operator">=</span> <span class="string">&#x27;&lt;YEAR&gt;&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> account <span class="operator">=</span> <span class="string">&#x27;&lt;ACCOUNT&gt;&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> eventsource <span class="operator">=</span> <span class="string">&#x27;signin.amazonaws.com&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> eventname <span class="operator">=</span> <span class="string">&#x27;ConsoleLogin&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> responseelements <span class="keyword">NOT</span> <span class="keyword">LIKE</span> <span class="string">&#x27;%ConsoleLogin%Failure%&#x27;</span> </span><br><span class="line"><span class="keyword">AND</span> from_iso8601_timestamp(eventtime) <span class="operator">&gt;</span> date_add(<span class="string">&#x27;day&#x27;</span>, <span class="number">-30</span>, now());</span><br></pre></td></tr></table></figure><p>In conclusion, querying CloudTrail events with Athena is a mighty way to answer security and compliance related questions. You should have the Athena table set up and be familiar with the possible queries to be able to answer such questions quickly.</p><p>Are there any other questions you have answered with the help of CloudTrail events queried by Athena? I’m curious about the examples you send me. </p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Dead man's switch with CloudWatch</title>
      <link>https://cloudonaut.io/dead-mans-switch-with-cloudwatch/</link>
      <description>
        <![CDATA[<p>While writing this article, I’m traveling from Frankfurt to Stuttgart by high-speed train (ICE) with a top speed of 280 km&#x2F;h. It is]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <guid isPermaLink="true">https://cloudonaut.io/dead-mans-switch-with-cloudwatch/</guid>
      <pubDate>Tue, 17 Jul 2018 17:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>While writing this article, I’m traveling from Frankfurt to Stuttgart by high-speed train (ICE) with a top speed of 280 km&#x2F;h. It is reassuring to know that a dead man’s switch stops the train immediately if the train driver becomes incapacitated, such as through death, loss of consciousness, or being bodily removed from control.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/07/dead-mans-switch-with-cloudwatch@730w.webp 730w, /images/2018/07/dead-mans-switch-with-cloudwatch@730w2x.webp 1460w, /images/2018/07/dead-mans-switch-with-cloudwatch@610w.webp 610w, /images/2018/07/dead-mans-switch-with-cloudwatch@610w2x.webp 1220w, /images/2018/07/dead-mans-switch-with-cloudwatch@450w.webp 450w, /images/2018/07/dead-mans-switch-with-cloudwatch@450w2x.webp 900w, /images/2018/07/dead-mans-switch-with-cloudwatch@330w.webp 330w, /images/2018/07/dead-mans-switch-with-cloudwatch@330w2x.webp 660w, /images/2018/07/dead-mans-switch-with-cloudwatch@545w.webp 545w, /images/2018/07/dead-mans-switch-with-cloudwatch@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/07/dead-mans-switch-with-cloudwatch@730w.jpg 730w, /images/2018/07/dead-mans-switch-with-cloudwatch@730w2x.jpg 1460w, /images/2018/07/dead-mans-switch-with-cloudwatch@610w.jpg 610w, /images/2018/07/dead-mans-switch-with-cloudwatch@610w2x.jpg 1220w, /images/2018/07/dead-mans-switch-with-cloudwatch@450w.jpg 450w, /images/2018/07/dead-mans-switch-with-cloudwatch@450w2x.jpg 900w, /images/2018/07/dead-mans-switch-with-cloudwatch@330w.jpg 330w, /images/2018/07/dead-mans-switch-with-cloudwatch@330w2x.jpg 660w, /images/2018/07/dead-mans-switch-with-cloudwatch@545w.jpg 545w, /images/2018/07/dead-mans-switch-with-cloudwatch@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/07/dead-mans-switch-with-cloudwatch.jpg" alt="Dead man's switch with CloudWatch" title="Dead man's switch with CloudWatch"></picture></p><p>Even though you are typically using CloudWatch alarms to make sure a metric does not exceed a threshold, it is also possible to build a dead man’s switch with CloudWatch. Doing so allows you to monitor the health of processes and jobs. A few examples for typical failures to monitor with a dead man’s switch often called heartbeat monitoring as well:</p><ul><li>A daily backup did not complete.</li><li>It was not possible to generate a daily report.</li><li>An recurring import job failed.</li></ul><p>The following example guides you through how to monitor a job backing up the <code>home</code>  directory of an EC2 instance to S3 every 4 hours. You will learn how to create a dead man’s switch consisting of the following building blocks:</p><ol><li>A CloudWatch custom metric collecting heartbeats from the backup job.</li><li>A CloudWatch alarm is monitoring the metric for missing heartbeats.</li></ol><h2 id="Collecting-heartbeats"><a href="#Collecting-heartbeats" class="headerlink" title="Collecting heartbeats"></a>Collecting heartbeats</h2><p>An EC2 instance publishes CloudWatch metrics like the CPU utilization, the number of  read operations on disk, or the number of bytes sent out. Almost every other AWS service is publishing metrics as well. On top of that, you can publish a heartbeat to a custom metric as well.</p><p>The following snippet shows a backup script triggered by a <code>cronjob</code> every four hours.</p><ol><li>Synchronize the folder <code>/home</code>  to S3.</li><li>Send a heartbeat to a custom metric.</li></ol><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash -e</span></span><br><span class="line">aws s3 <span class="built_in">sync</span> /home s3://my-company-backup/home</span><br><span class="line">aws cloudwatch put-metric-data --namespace custom/backup --metric-data <span class="string">&#x27;MetricName=heartbeat,Dimensions=[&#123;Name=source,Value=home&#125;],Value=1&#x27;</span></span><br></pre></td></tr></table></figure><p>How does publish a heartbeat to CloudWatch work? </p><ol><li><code>aws cloudwatch put-metric-data</code> sends data to a custom metric.</li><li><code>custom/backup</code> is the namespace used for this example.</li><li>The name of the custom metric is set to <code>MetricName=heartbeat</code>.</li><li>The backup source (the <code>home</code> directory) is used as dimension: <code>Dimensions=[{Name=source,Value=home}]</code></li></ol><p>Learn more about <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/publishingMetrics.html" target="_blank" rel="noopener">custom metrics</a>. Of course, it is also possible to publish heartbeats by using one of the <a href="https://aws.amazon.com/tools/" target="_blank" rel="noopener">AWS SDKs</a> directly from within your application.</p><p>Next, to get notified whenever the backup job does not succeed anymore you only need to create a CloudWatch alarm.</p><h2 id="Monitoring-heartbeats"><a href="#Monitoring-heartbeats" class="headerlink" title="Monitoring heartbeats"></a>Monitoring heartbeats</h2><p>A CloudWatch alarm monitors a metric and triggers actions. For example, you can use a CloudWatch alarm to notify you whenever the CPU utilization of an EC2 instance is above 80% for more than 60 minutes. However, it is also possible to implement a dead man’s switch with the help of a CloudWatch alarm as described next.</p><p>As illustrated in the following figure the following steps are necessary to start creating a new CloudWatch alarm:</p><ol><li>Open the <a href="https://console.aws.amazon.com/cloudwatch/home" target="_blank" rel="noopener">CloudWatch</a> service within the AWS Management Console.</li><li>Select <em>Alarms</em> from the sub-navigation.</li><li>Click the <em>Create Alarm</em> button.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/07/deadmanswitch-1@730w.webp 730w, /images/2018/07/deadmanswitch-1@730w2x.webp 1460w, /images/2018/07/deadmanswitch-1@610w.webp 610w, /images/2018/07/deadmanswitch-1@610w2x.webp 1220w, /images/2018/07/deadmanswitch-1@450w.webp 450w, /images/2018/07/deadmanswitch-1@450w2x.webp 900w, /images/2018/07/deadmanswitch-1@330w.webp 330w, /images/2018/07/deadmanswitch-1@330w2x.webp 660w, /images/2018/07/deadmanswitch-1@545w.webp 545w, /images/2018/07/deadmanswitch-1@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/07/deadmanswitch-1@730w.png 730w, /images/2018/07/deadmanswitch-1@730w2x.png 1460w, /images/2018/07/deadmanswitch-1@610w.png 610w, /images/2018/07/deadmanswitch-1@610w2x.png 1220w, /images/2018/07/deadmanswitch-1@450w.png 450w, /images/2018/07/deadmanswitch-1@450w2x.png 900w, /images/2018/07/deadmanswitch-1@330w.png 330w, /images/2018/07/deadmanswitch-1@330w2x.png 660w, /images/2018/07/deadmanswitch-1@545w.png 545w, /images/2018/07/deadmanswitch-1@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/07/deadmanswitch-1.png" alt="Dead man's switch with CloudWatch (1/3)" title="Dead man's switch with CloudWatch (1/3)"></picture></p><p>The following figure shows how to select the custom metric.</p><ol><li>Choose the namespace <code>custom/backup</code>.</li><li>Select the metric with the metric name <code>heartbeat</code> and source <code>home</code>.</li><li>Click the <code>Next</code> button.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/07/deadmanswitch-2@730w.webp 730w, /images/2018/07/deadmanswitch-2@730w2x.webp 1460w, /images/2018/07/deadmanswitch-2@610w.webp 610w, /images/2018/07/deadmanswitch-2@610w2x.webp 1220w, /images/2018/07/deadmanswitch-2@450w.webp 450w, /images/2018/07/deadmanswitch-2@450w2x.webp 900w, /images/2018/07/deadmanswitch-2@330w.webp 330w, /images/2018/07/deadmanswitch-2@330w2x.webp 660w, /images/2018/07/deadmanswitch-2@545w.webp 545w, /images/2018/07/deadmanswitch-2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/07/deadmanswitch-2@730w.png 730w, /images/2018/07/deadmanswitch-2@730w2x.png 1460w, /images/2018/07/deadmanswitch-2@610w.png 610w, /images/2018/07/deadmanswitch-2@610w2x.png 1220w, /images/2018/07/deadmanswitch-2@450w.png 450w, /images/2018/07/deadmanswitch-2@450w2x.png 900w, /images/2018/07/deadmanswitch-2@330w.png 330w, /images/2018/07/deadmanswitch-2@330w2x.png 660w, /images/2018/07/deadmanswitch-2@545w.png 545w, /images/2018/07/deadmanswitch-2@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/07/deadmanswitch-2.png" alt="Dead man's switch with CloudWatch (2/3)" title="Dead man's switch with CloudWatch (2/3)"></picture></p><p>The last step is to configure the alarm as illustrated in the following figure.</p><ol><li>Type in <code>deadmanswitch-backup-home</code> as the <em>Name</em> and a <em>Description</em> for the alarm.</li><li>Select <code>&lt; 0</code> as the threshold for the alarm …</li><li>… for <code>1 out of 1</code> data points.</li><li>Most importantly, set <em>treat missing data as</em> to <code>bad</code>.</li><li>Select a timeframe of 6 hours.</li><li>Select the statistic method <code>Sum</code>.</li><li>Define an <code>ALARM</code> action.</li><li>Create a new list and enter your email address.</li><li>Don’t forget to press the <em>Create Alarm</em> button.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/07/deadmanswitch-3@730w.webp 730w, /images/2018/07/deadmanswitch-3@730w2x.webp 1460w, /images/2018/07/deadmanswitch-3@610w.webp 610w, /images/2018/07/deadmanswitch-3@610w2x.webp 1220w, /images/2018/07/deadmanswitch-3@450w.webp 450w, /images/2018/07/deadmanswitch-3@450w2x.webp 900w, /images/2018/07/deadmanswitch-3@330w.webp 330w, /images/2018/07/deadmanswitch-3@330w2x.webp 660w, /images/2018/07/deadmanswitch-3@545w.webp 545w, /images/2018/07/deadmanswitch-3@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/07/deadmanswitch-3@730w.png 730w, /images/2018/07/deadmanswitch-3@730w2x.png 1460w, /images/2018/07/deadmanswitch-3@610w.png 610w, /images/2018/07/deadmanswitch-3@610w2x.png 1220w, /images/2018/07/deadmanswitch-3@450w.png 450w, /images/2018/07/deadmanswitch-3@450w2x.png 900w, /images/2018/07/deadmanswitch-3@330w.png 330w, /images/2018/07/deadmanswitch-3@330w2x.png 660w, /images/2018/07/deadmanswitch-3@545w.png 545w, /images/2018/07/deadmanswitch-3@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/07/deadmanswitch-3.png" alt="Dead man's switch with CloudWatch (3/3)" title="Dead man's switch with CloudWatch (3/3)"></picture></p><p>By default, a CloudWatch alarm is entering the state <code>INSUFFICIENT_DATA</code> when there are no data points within the specified timeframe, which is 6 hours in our example. As we are configuring the alarm to treat missing data as bad, the alarm will enter the state <code>ALARM</code> instead of <code>INSUFFICIENT_DATA</code>. Learn more about <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#alarms-and-missing-data" target="_blank" rel="noopener">how alarms treat missing data</a>.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Creating a dead man’s switch with the help of CloudWatch allows you to monitor if jobs are working as expected. I’ve used this approach to monitor an agent responsible for synchronizing data from an on-premises database to DynamoDB, for example.</p><p><em>Thanks a lot, Josh. Your feedback improved the bash script and dead man’s switch.</em></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Rich Social Sharing with single page applications hosted on S3 and delivered via CloudFront</title>
      <link>https://cloudonaut.io/rich-social-sharing-with-single-page-applications-hosted-on-s3-and-delivered-via-cloudfront/</link>
      <description>
        <![CDATA[<p>You undoubtedly heard about single page applications (SPA) written with frameworks like Angular or React. One of the benefits of this app]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/cloudfront/">cloudfront</category>
      <guid isPermaLink="true">https://cloudonaut.io/rich-social-sharing-with-single-page-applications-hosted-on-s3-and-delivered-via-cloudfront/</guid>
      <pubDate>Fri, 06 Jul 2018 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>You undoubtedly heard about single page applications (SPA) written with frameworks like Angular or React. One of the benefits of this approach is the possibility to host the static files (HTML, js, CSS, etc.) on a simple storage solution like S3 and put a CDN like CloudFront in front of it (<a href="/using-s3-for-static-web-hosting/">learn more about using S3 for static web hosting</a>).</p><p>Rich social sharing means that if you include an URL in a text that you share, the social network (e.g. on Facebook) fetches additional data like an image and the title of the URL and displays them instead of the raw URL. This blog post contains the following meta tags to allow a rich social sharing experience:</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">property</span>=<span class="string">&quot;og:title&quot;</span> <span class="attr">content</span>=<span class="string">&quot;Rich Social Sharing with single page applications hosted on S3 and delivered via CloudFront&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">property</span>=<span class="string">&quot;og:description&quot;</span> <span class="attr">content</span>=<span class="string">&quot;You undoubtedly heard about [...]&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">property</span>=<span class="string">&quot;og:image&quot;</span> <span class="attr">content</span>=<span class="string">&quot;https://cloudonaut.io/images/2018/07/facebook.png&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>If you want to share your SPA on Facebook or Twitter, you will face a problem. Your meta tags are dynamically filled with JavaScript. So the initial HTML looks like this:</p><figure class="highlight html"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">property</span>=<span class="string">&quot;og:title&quot;</span> <span class="attr">content</span>=<span class="string">&quot;&#123;&#123;pageTitle&#125;&#125;&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">property</span>=<span class="string">&quot;og:description&quot;</span> <span class="attr">content</span>=<span class="string">&quot;&#123;&#123;pageDescription&#125;&#125;&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">property</span>=<span class="string">&quot;og:image&quot;</span> <span class="attr">content</span>=<span class="string">&quot;&#123;&#123;pageImage&#125;&#125;&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><p>Facebook (and other social networks) will present the placeholders in the preview. The rich experience is broken.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/07/facebook@730w.webp 730w, /images/2018/07/facebook@730w2x.webp 1460w, /images/2018/07/facebook@610w.webp 610w, /images/2018/07/facebook@610w2x.webp 1220w, /images/2018/07/facebook@450w.webp 450w, /images/2018/07/facebook@450w2x.webp 900w, /images/2018/07/facebook@330w.webp 330w, /images/2018/07/facebook@330w2x.webp 660w, /images/2018/07/facebook@545w.webp 545w, /images/2018/07/facebook@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/07/facebook@730w.png 730w, /images/2018/07/facebook@730w2x.png 1460w, /images/2018/07/facebook@610w.png 610w, /images/2018/07/facebook@610w2x.png 1220w, /images/2018/07/facebook@450w.png 450w, /images/2018/07/facebook@450w2x.png 900w, /images/2018/07/facebook@330w.png 330w, /images/2018/07/facebook@330w2x.png 660w, /images/2018/07/facebook@545w.png 545w, /images/2018/07/facebook@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/07/facebook.png" alt="Facebook" title="Facebook"></picture></p><p>The problem here is that social networks just read the HTML source but do not execute the JavaScript. To make this work, you have to</p><ol><li>Render the pages as static HTML files where placeholders are replaced with the actual values.</li><li>When a social bot is crawling your page, deliver the static HTML file instead of the SPA</li></ol><p>For pre-rendering, you can use <a href="https://prerender.io/" target="_blank" rel="noopener">prerender.io</a> (or others) which you can hook into your web server like nginx or Apache. The caveat here is that with S3 you do not have a web server.</p><h2 id="Lambda-Edge-for-the-rescue"><a href="#Lambda-Edge-for-the-rescue" class="headerlink" title="Lambda@Edge for the rescue"></a>Lambda@Edge for the rescue</h2><p>Fortunately, Lambda functions can be hooked into web page delivered via CloudFront. When a social bot is requesting your SPA, the request should be forwarded to prerender.io instead of your S3 bucket. The forwarding can be achieved with a Lambda@Edge function that inspects the <code>User-Agent</code> header.</p><p>The Lambda function checks if the user agent is one of the known social bots and decided whether to serve the original website or the pre-rendered version by overriding the configuration of the origin request.</p><figure class="highlight javascript"><figcaption><span>Lambda source</span><a href="https://gist.github.com/hoegertn/13b6b8c3adfeab1168326345ded4938f">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">exports</span>.<span class="property">handler</span> = <span class="function">(<span class="params">event, context, callback</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="string">&#x27;use strict&#x27;</span>;</span><br><span class="line">  <span class="keyword">const</span> request = event.<span class="property">Records</span>[<span class="number">0</span>].<span class="property">cf</span>.<span class="property">request</span>;</span><br><span class="line">  <span class="keyword">const</span> userAgent = request.<span class="property">headers</span>[<span class="string">&#x27;user-agent&#x27;</span>][<span class="number">0</span>].<span class="property">value</span>;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">let</span> prerender = <span class="number">0</span>;</span><br><span class="line">  <span class="comment">// user agent is known social bot</span></span><br><span class="line">  <span class="keyword">if</span> (userAgent.<span class="title function_">match</span>(<span class="regexp">/baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator/i</span>)) &#123;</span><br><span class="line">    prerender = <span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// rendered page is requested by special query string</span></span><br><span class="line">  <span class="keyword">if</span> (request.<span class="property">querystring</span>.<span class="title function_">match</span>(<span class="regexp">/_escaped_fragment_/</span>)) &#123;</span><br><span class="line">    prerender = <span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// request is coming from prerender.io, so serve original page</span></span><br><span class="line">  <span class="keyword">if</span> (userAgent.<span class="title function_">match</span>(<span class="regexp">/Prerender/</span>)) &#123;</span><br><span class="line">    prerender = <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// request targets non-html files</span></span><br><span class="line">  <span class="keyword">if</span> (request.<span class="property">uri</span>.<span class="title function_">match</span>(<span class="regexp">/\.(js|css|xml|less|png|jpg|jpeg|gif|pdf|doc|txt|ico|rss|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent|ttf|woff|svg|eot)/i</span>)) &#123;</span><br><span class="line">    prerender = <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (prerender === <span class="number">1</span>) &#123;</span><br><span class="line">    request.<span class="property">origin</span> = &#123;</span><br><span class="line">      <span class="attr">custom</span>: &#123;</span><br><span class="line">        <span class="attr">protocol</span>: <span class="string">&#x27;http&#x27;</span>,</span><br><span class="line">        <span class="attr">domainName</span>: <span class="string">&#x27;service.prerender.io&#x27;</span>,</span><br><span class="line">        <span class="attr">port</span>: <span class="number">80</span>,</span><br><span class="line">        <span class="attr">path</span>: <span class="string">&#x27;/https://www.example.de&#x27;</span>,</span><br><span class="line">        <span class="attr">sslProtocols</span>: [<span class="string">&#x27;TLSv1&#x27;</span>, <span class="string">&#x27;TLSv1.1&#x27;</span>],</span><br><span class="line">        <span class="attr">readTimeout</span>: <span class="number">30</span>,</span><br><span class="line">        <span class="attr">keepaliveTimeout</span>: <span class="number">30</span>,</span><br><span class="line">        <span class="attr">customHeaders</span>: &#123;</span><br><span class="line">          <span class="string">&#x27;x-prerender-token&#x27;</span>: [</span><br><span class="line">            &#123;</span><br><span class="line">              <span class="string">&quot;key&quot;</span>: <span class="string">&quot;X-Prerender-Token&quot;</span>,</span><br><span class="line">              <span class="string">&quot;value&quot;</span>: <span class="string">&quot;yourPrerenderIOToken&quot;</span></span><br><span class="line">            &#125;</span><br><span class="line">          ]</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">callback</span>(<span class="literal">null</span>, request);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="CloudFront-configuration"><a href="#CloudFront-configuration" class="headerlink" title="CloudFront configuration"></a>CloudFront configuration</h2><p>To configure CloudFront to call the Lambda@Edge function:</p><ol><li>Select your distribution and click on the Edit button<br><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/07/distribution@730w.webp 730w, /images/2018/07/distribution@730w2x.webp 1460w, /images/2018/07/distribution@610w.webp 610w, /images/2018/07/distribution@610w2x.webp 1220w, /images/2018/07/distribution@450w.webp 450w, /images/2018/07/distribution@450w2x.webp 900w, /images/2018/07/distribution@330w.webp 330w, /images/2018/07/distribution@330w2x.webp 660w, /images/2018/07/distribution@545w.webp 545w, /images/2018/07/distribution@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/07/distribution@730w.png 730w, /images/2018/07/distribution@730w2x.png 1460w, /images/2018/07/distribution@610w.png 610w, /images/2018/07/distribution@610w2x.png 1220w, /images/2018/07/distribution@450w.png 450w, /images/2018/07/distribution@450w2x.png 900w, /images/2018/07/distribution@330w.png 330w, /images/2018/07/distribution@330w2x.png 660w, /images/2018/07/distribution@545w.png 545w, /images/2018/07/distribution@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/07/distribution.png" alt="CloudFront step 1" title="CloudFront step 1"></picture></li><li>Whitelist the <code>User-Agent</code> header and configure the Lambda@Edge function for the <code>origin-Request</code> event type<br><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/07/behavior@730w.webp 730w, /images/2018/07/behavior@730w2x.webp 1460w, /images/2018/07/behavior@610w.webp 610w, /images/2018/07/behavior@610w2x.webp 1220w, /images/2018/07/behavior@450w.webp 450w, /images/2018/07/behavior@450w2x.webp 900w, /images/2018/07/behavior@330w.webp 330w, /images/2018/07/behavior@330w2x.webp 660w, /images/2018/07/behavior@545w.webp 545w, /images/2018/07/behavior@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/07/behavior@730w.png 730w, /images/2018/07/behavior@730w2x.png 1460w, /images/2018/07/behavior@610w.png 610w, /images/2018/07/behavior@610w2x.png 1220w, /images/2018/07/behavior@450w.png 450w, /images/2018/07/behavior@450w2x.png 900w, /images/2018/07/behavior@330w.png 330w, /images/2018/07/behavior@330w2x.png 660w, /images/2018/07/behavior@545w.png 545w, /images/2018/07/behavior@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/07/behavior.png" alt="CloudFront step 2" title="CloudFront step 2"></picture></li></ol><p>After updating the CloudFront distribution, which could take up to 40 minutes, you can share your web page using social media. </p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Enable a rich social sharing experience for SPAs hosted on CloudFront + S3 with Lambda@Edge.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Rapid CloudFormation: cfn-modules</title>
      <link>https://cloudonaut.io/easy-going-aws-cloudformation-cfn-modules/</link>
      <description>
        <![CDATA[<p>Today, we release a new open source project to make your CloudFormation live easier. We promise rapid CloudFormation with <a href="https:]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/easy-going-aws-cloudformation-cfn-modules/</guid>
      <pubDate>Fri, 06 Jul 2018 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Today, we release a new open source project to make your CloudFormation live easier. We promise rapid CloudFormation with <a href="https://github.com/cfn-modules" target="_blank" rel="noopener">cfn-modules</a>. Our modules provide common building blocks to automate your infrastructure with plain CloudFormation templates.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/07/cfn-modules@730w.webp 730w, /images/2018/07/cfn-modules@730w2x.webp 1460w, /images/2018/07/cfn-modules@610w.webp 610w, /images/2018/07/cfn-modules@610w2x.webp 1220w, /images/2018/07/cfn-modules@450w.webp 450w, /images/2018/07/cfn-modules@450w2x.webp 900w, /images/2018/07/cfn-modules@330w.webp 330w, /images/2018/07/cfn-modules@330w2x.webp 660w, /images/2018/07/cfn-modules@545w.webp 545w, /images/2018/07/cfn-modules@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/07/cfn-modules@730w.png 730w, /images/2018/07/cfn-modules@730w2x.png 1460w, /images/2018/07/cfn-modules@610w.png 610w, /images/2018/07/cfn-modules@610w2x.png 1220w, /images/2018/07/cfn-modules@450w.png 450w, /images/2018/07/cfn-modules@450w2x.png 900w, /images/2018/07/cfn-modules@330w.png 330w, /images/2018/07/cfn-modules@330w2x.png 660w, /images/2018/07/cfn-modules@545w.png 545w, /images/2018/07/cfn-modules@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/07/cfn-modules.png" alt="cfn-modules" title="cfn-modules"></picture></p><h2 id="Why-cfn-modules"><a href="#Why-cfn-modules" class="headerlink" title="Why cfn-modules?"></a>Why cfn-modules?</h2><p>We started with <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">aws-cf-templates</a> in 2015. Three years later, we believe that we have learned enough to come up with a new approach to use CloudFormation more efficient.</p><h3 id="Modular"><a href="#Modular" class="headerlink" title="Modular"></a>Modular</h3><p>Reusing CloudFormation templates is hard. Most often, templates are initially copied and then modified.</p><p>Two problems arise. First, updates to the copy are not applied to the original. Second, updates to the original are not applied to the copy. <strong>In essence: we do not learn from each other!</strong></p><p>By using an easy to use package manager (<a href="https://www.npmjs.com/" target="_blank" rel="noopener">npm</a>) you can install and update <code>cfn-modules</code> to spin up complex infrastructure in minuted that just works.</p><h3 id="Production-ready"><a href="#Production-ready" class="headerlink" title="Production ready"></a>Production ready</h3><p>All modules are production-ready. If no other limitations are documented, they are:</p><ul><li>Highly available<ul><li>no single point of failure</li></ul></li><li>Scalable<ul><li>increase or decrease the capacity based on utilization</li></ul></li><li>Secure<ul><li>using the latest operating systems and software components</li><li>follow the least privilege principle (e.g., IAM policies and Security Groups)</li><li>backups enabled</li><li>encryption at-rest enabled</li><li>encryption in-transit enabled and preferred</li></ul></li><li>Operations friendly<ul><li>logging enabled</li><li>alerting enabled</li><li>updatable</li></ul></li></ul><h3 id="Open-source"><a href="#Open-source" class="headerlink" title="Open source"></a>Open source</h3><p>All modules are licensed under Apache-2.0. Commercial use is allowed.</p><h2 id="Prerequisites"><a href="#Prerequisites" class="headerlink" title="Prerequisites"></a>Prerequisites</h2><ul><li>AWS CLI installed (<a href="https://docs.aws.amazon.com/cli/latest/userguide/installing.html" target="_blank" rel="noopener">install</a>)</li><li>npm 3.x installed (<a href="https://nodejs.org/en" target="_blank" rel="noopener">install Node.js 8.x</a>)</li></ul><h2 id="Getting-started"><a href="#Getting-started" class="headerlink" title="Getting started"></a>Getting started</h2><p><code>cfn-modules</code> are installed and updated with the package manager <a href="https://www.npmjs.com/" target="_blank" rel="noopener">npm</a>. The <a href="https://www.npmjs.com/org/cfn-modules" target="_blank" rel="noopener">module catalog</a> contains all available modules. Let’s start with a simple example: An EC2 instance launched into a VPC.</p><blockquote><p><a href="https://nodejs.org/en" target="_blank" rel="noopener">Install Node.js 8.x</a> if <code>npm</code> is not installed on your system </p></blockquote><p>Install the modules using npm:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm i @cfn-modules/vpc</span><br><span class="line">npm i @cfn-modules/ec2-instance-amazon-linux</span><br></pre></td></tr></table></figure><p>Use the modules as nested stacks in your CloudFormation template. The <a href="https://www.npmjs.com/package/@cfn-modules/vpc" target="_blank" rel="noopener">vpc</a> module comes with no required parameters. The <a href="https://www.npmjs.com/package/@cfn-modules/ec2-instance-amazon-linux" target="_blank" rel="noopener">ec2-instance-amazon-linux</a> module comes with the required <code>VpcModule</code> parameter to make the connection with the <code>vpc</code> module. The <code>UserData</code> <a href="https://www.npmjs.com/package/@cfn-modules/ec2-instance-amazon-linux#parameters" target="_blank" rel="noopener">parameter</a> is optional. Use it to install additional software like the Apache HTTP Server. Create a file named <code>example.yml</code> with the following content:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Vpc:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFormation::Stack&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Parameters:</span></span><br><span class="line">        <span class="attr">S3Endpoint:</span> <span class="string">&#x27;false&#x27;</span> <span class="comment"># speed up the example</span></span><br><span class="line">        <span class="attr">DynamoDBEndpoint:</span> <span class="string">&#x27;false&#x27;</span> <span class="comment"># speed up the example</span></span><br><span class="line">        <span class="attr">FlowLog:</span> <span class="string">&#x27;false&#x27;</span> <span class="comment"># speed up the example</span></span><br><span class="line">        <span class="attr">NatGateways:</span> <span class="string">&#x27;false&#x27;</span> <span class="comment"># speed up the example</span></span><br><span class="line">      <span class="attr">TemplateURL:</span> <span class="string">&#x27;./node_modules/@cfn-modules/vpc/module.yml&#x27;</span></span><br><span class="line">  <span class="attr">Instance:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudFormation::Stack&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Parameters:</span></span><br><span class="line">        <span class="attr">VpcModule:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Vpc.Outputs.StackName&#x27;</span> <span class="comment"># reference the vpc module</span></span><br><span class="line">        <span class="attr">UserData:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          yum install -y httpd24</span></span><br><span class="line"><span class="string">          service httpd start</span></span><br><span class="line"><span class="string">          echo &quot;cfn-modules&quot; &gt; /var/www/html/index.html</span></span><br><span class="line"><span class="string"></span>        <span class="attr">IngressTcpPort1:</span> <span class="string">&#x27;80&#x27;</span> <span class="comment"># open up port 80 to the world</span></span><br><span class="line">      <span class="attr">TemplateURL:</span> <span class="string">&#x27;./node_modules/@cfn-modules/ec2-instance-amazon-linux/module.yml&#x27;</span></span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">Url:</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Sub</span> <span class="string">&#x27;http://$&#123;Instance.Outputs.PublicIpAddress&#125;&#x27;</span></span><br></pre></td></tr></table></figure><p>Upload the CloudFormation template and the dependencies to S3 with the <code>aws cloudformation package</code> command.</p><blockquote><p><a href="https://docs.aws.amazon.com/cli/latest/userguide/installing.html" target="_blank" rel="noopener">Install AWS CLI</a> if <code>aws</code> is not installed on your system </p></blockquote><p>If you use <code>cfn-modules</code> the first time, create an S3 bucket to store the artifacts first (otherwise, skip this step). Choose a unique bucket name, e.g. <code>cfn-modules-$Name-$Region</code>.</p><p>In the following command, replace <code>$Name</code> with a unique name (e.g. your initials or company name), and replace <code>$Region</code> with your AWS default region (e.g. <code>us-east-1</code>) to create an S3 bucket:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws s3 mb s3://cfn-modules-<span class="variable">$Name</span>-<span class="variable">$Region</span></span><br></pre></td></tr></table></figure><p>Now you can upload all artifacts to S3:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws cloudformation package --template-file example.yml --s3-bucket cfn-modules-<span class="variable">$Name</span>-<span class="variable">$Region</span> --output-template-file packaged.yml</span><br></pre></td></tr></table></figure><p>Finally, you can create a CloudFormation stack with <code>aws cloudformation deploy</code>:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws cloudformation deploy --template-file packaged.yml --stack-name ec2-example --capabilities CAPABILITY_IAM</span><br></pre></td></tr></table></figure><p>Creating the stack will take ~10 minutes. You can find the URL to the demo page in the stack outputs:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws cloudformation describe-stacks --stack-name ec2-example --query <span class="string">&quot;Stacks[0].Outputs&quot;</span></span><br></pre></td></tr></table></figure><p>After you have finished testing delete the stack to avoid unwanted costs.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws cloudformation delete-stack --stack-name ec2-example</span><br><span class="line">aws cloudformation <span class="built_in">wait</span> stack-delete-complete --stack-name ec2-example</span><br></pre></td></tr></table></figure><p>Fin. Check out our examples next.</p><h2 id="Examples"><a href="#Examples" class="headerlink" title="Examples"></a>Examples</h2><ul><li>Auto Scaling Group (singleton)<ul><li><a href="https://github.com/cfn-modules/docs/blob/master/examples/asg-singleton-ssm/" target="_blank" rel="noopener">SSM</a></li></ul></li><li>EC2<ul><li><a href="https://github.com/cfn-modules/docs/blob/master/examples/ec2-ebs/" target="_blank" rel="noopener">Mount EBS volume</a></li><li><a href="https://github.com/cfn-modules/docs/blob/master/examples/ec2-efs/" target="_blank" rel="noopener">Mount EFS file system</a></li><li><a href="https://github.com/cfn-modules/docs/blob/master/examples/ec2-mysql/" target="_blank" rel="noopener">Connect to MySQL</a></li><li><a href="https://github.com/cfn-modules/docs/blob/master/examples/ec2-postgres/" target="_blank" rel="noopener">Connect to Postgres</a></li><li><a href="https://github.com/cfn-modules/docs/blob/master/examples/ec2-ssh-bastion/" target="_blank" rel="noopener">SSH bastion</a></li><li><a href="https://github.com/cfn-modules/docs/blob/master/examples/ec2-ssm/" target="_blank" rel="noopener">SSM</a></li></ul></li><li>Serverless<ul><li><a href="https://github.com/cfn-modules/docs/blob/master/examples/serverless-cron/" target="_blank" rel="noopener">Cron</a></li><li><a href="https://github.com/cfn-modules/docs/blob/master/examples/serverless-iam/" target="_blank" rel="noopener">Auto IAM policies</a></li><li><a href="https://github.com/cfn-modules/docs/blob/master/examples/serverless-image-resize/" target="_blank" rel="noopener">Image resize</a></li><li><a href="https://github.com/cfn-modules/docs/blob/master/examples/serverless-sqs-queue/" target="_blank" rel="noopener">SQS queue</a></li></ul></li></ul><p>Check out all <a href="https://github.com/cfn-modules/docs/blob/master/examples/" target="_blank" rel="noopener">examples</a> if you need more.</p><h2 id="Modules"><a href="#Modules" class="headerlink" title="Modules"></a>Modules</h2><ul><li><a href="https://github.com/cfn-modules/alerting" target="_blank" rel="noopener">alerting</a></li><li>Auto Scaling Group (singleton)<ul><li><a href="https://github.com/cfn-modules/asg-singleton-amazon-linux2" target="_blank" rel="noopener">asg-singleton-amazon-linux2</a></li></ul></li><li><a href="https://github.com/cfn-modules/client-sg" target="_blank" rel="noopener">client-sg</a></li><li><a href="https://github.com/cfn-modules/dynamodb-table" target="_blank" rel="noopener">dynamodb-table</a></li><li><a href="https://github.com/cfn-modules/ebs-volume" target="_blank" rel="noopener">ebs-volume</a></li><li>EC2 instance<ul><li><a href="https://github.com/cfn-modules/ec2-instance-amazon-linux" target="_blank" rel="noopener">ec2-instance-amazon-linux</a></li><li><a href="https://github.com/cfn-modules/ec2-instance-amazon-linux2" target="_blank" rel="noopener">ec2-instance-amazon-linux2</a></li></ul></li><li><a href="https://github.com/cfn-modules/efs-file-system" target="_blank" rel="noopener">efs-file-system</a></li><li><a href="https://github.com/cfn-modules/kms-key" target="_blank" rel="noopener">kms-key</a></li><li>Lambda<ul><li><a href="https://github.com/cfn-modules/lambda-event-source-cron" target="_blank" rel="noopener">lambda-event-source-cron</a></li><li><a href="https://github.com/cfn-modules/lambda-event-source-sqs-queue" target="_blank" rel="noopener">lambda-event-source-sqs-queue</a></li><li><a href="https://github.com/cfn-modules/lambda-function" target="_blank" rel="noopener">lambda-function</a></li></ul></li><li>RDS<ul><li><a href="https://github.com/cfn-modules/rds-mysql" target="_blank" rel="noopener">rds-mysql</a></li><li><a href="https://github.com/cfn-modules/rds-postgres" target="_blank" rel="noopener">rds-postgres</a></li></ul></li><li>Route53<ul><li><a href="https://github.com/cfn-modules/route53-hosted-zone-private" target="_blank" rel="noopener">route53-hosted-zone-private</a></li><li><a href="https://github.com/cfn-modules/route53-hosted-zone-public" target="_blank" rel="noopener">route53-hosted-zone-public</a></li></ul></li><li><a href="https://github.com/cfn-modules/s3-bucket" target="_blank" rel="noopener">s3-bucket</a></li><li><a href="https://github.com/cfn-modules/sqs-queue" target="_blank" rel="noopener">sqs-queue</a></li><li><a href="https://github.com/cfn-modules/ssh-bastion" target="_blank" rel="noopener">ssh-bastion</a></li><li><a href="https://github.com/cfn-modules/vpc" target="_blank" rel="noopener">vpc</a></li></ul><p>Check out the <a href="https://www.npmjs.com/org/cfn-modules" target="_blank" rel="noopener">module catalog</a> to browse all modules.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>EKS vs. ECS: orchestrating containers on AWS</title>
      <link>https://cloudonaut.io/eks-vs-ecs-orchestrating-containers-on-aws/</link>
      <description>
        <![CDATA[<p>AWS announced Kubernetes-as-a-Service at re:Invent in November 2017: Elastic Container Service for Kubernetes (EKS). Since yesterday, EKS]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <category domain="https://cloudonaut.io/tag/kubernetes/">kubernetes</category>
      <category domain="https://cloudonaut.io/tag/eks/">eks</category>
      <guid isPermaLink="true">https://cloudonaut.io/eks-vs-ecs-orchestrating-containers-on-aws/</guid>
      <pubDate>Wed, 06 Jun 2018 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>AWS announced Kubernetes-as-a-Service at re:Invent in November 2017: Elastic Container Service for Kubernetes (EKS). Since yesterday, EKS is generally available. I discussed <a href="/ecs-vs-kubernetes/">ECS vs. Kubernetes</a> before EKS was a thing. Therefore, I’d like to take a second attempt and compare EKS with ECS.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/06/container-ship@730w.webp 730w, /images/2018/06/container-ship@730w2x.webp 1460w, /images/2018/06/container-ship@610w.webp 610w, /images/2018/06/container-ship@610w2x.webp 1220w, /images/2018/06/container-ship@450w.webp 450w, /images/2018/06/container-ship@450w2x.webp 900w, /images/2018/06/container-ship@330w.webp 330w, /images/2018/06/container-ship@330w2x.webp 660w, /images/2018/06/container-ship@545w.webp 545w, /images/2018/06/container-ship@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/06/container-ship@730w.jpg 730w, /images/2018/06/container-ship@730w2x.jpg 1460w, /images/2018/06/container-ship@610w.jpg 610w, /images/2018/06/container-ship@610w2x.jpg 1220w, /images/2018/06/container-ship@450w.jpg 450w, /images/2018/06/container-ship@450w2x.jpg 900w, /images/2018/06/container-ship@330w.jpg 330w, /images/2018/06/container-ship@330w2x.jpg 660w, /images/2018/06/container-ship@545w.jpg 545w, /images/2018/06/container-ship@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/06/container-ship.jpg" alt="Container" title="Container"></picture></p><p>Before comparing the differences, let us start with what EKS and ECS have in common. Both solutions are managing containers distributed among a fleet of virtual machines. Managing containers includes:</p><ul><li>Monitoring and replacing failed containers.</li><li>Deploying new versions of your containers.</li><li>Scaling the number of containers based on load.</li></ul><p>What are the differences between EKS and ECS?</p><h2 id="Load-Balancing"><a href="#Load-Balancing" class="headerlink" title="Load Balancing"></a>Load Balancing</h2><p>Usually, a load balancer is as the entry point into your AWS infrastructure. Both EKS and ECS offer integrations with Elastic Load Balancing (ELB).</p><p>On the one hand, Kubernetes - and therefore EKS - offers an integration with the Classic Load Balancer. Support for the <a href="https://github.com/coreos/alb-ingress-controller" target="_blank" rel="noopener">Application Load Balancer</a> and <a href="https://aws.amazon.com/blogs/opensource/network-load-balancer-support-in-kubernetes-1-9/" target="_blank" rel="noopener">Network Load Balancer</a> are available as beta releases. When creating a service Kubernetes does also create or configure a Classic Load Balancer for you.</p><ol><li>The client sends a request to ELB.</li><li>ELB distributes the request to one of the nodes also known as EC2 instances.</li><li>A proxy running on the node is forwarding the request to one of the pods providing the service.</li></ol><p>On the other hand, ECS provides an integration with the Application Load Balancer (ALB), the Network Load Balancer (NLB) as well as the Classic Load Balancer (CLB). When using the ALB, the flow for each incoming request needs only two instead of three steps.</p><ol><li>The client sends a request to the ALB.</li><li>ALB forwards request to one of the tasks providing the service.</li></ol><p>The following figure illustrates the difference.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/06/eks-ecs-load-balacing@730w.webp 730w, /images/2018/06/eks-ecs-load-balacing@730w2x.webp 1460w, /images/2018/06/eks-ecs-load-balacing@610w.webp 610w, /images/2018/06/eks-ecs-load-balacing@610w2x.webp 1220w, /images/2018/06/eks-ecs-load-balacing@450w.webp 450w, /images/2018/06/eks-ecs-load-balacing@450w2x.webp 900w, /images/2018/06/eks-ecs-load-balacing@330w.webp 330w, /images/2018/06/eks-ecs-load-balacing@330w2x.webp 660w, /images/2018/06/eks-ecs-load-balacing@545w.webp 545w, /images/2018/06/eks-ecs-load-balacing@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/06/eks-ecs-load-balacing@730w.png 730w, /images/2018/06/eks-ecs-load-balacing@730w2x.png 1460w, /images/2018/06/eks-ecs-load-balacing@610w.png 610w, /images/2018/06/eks-ecs-load-balacing@610w2x.png 1220w, /images/2018/06/eks-ecs-load-balacing@450w.png 450w, /images/2018/06/eks-ecs-load-balacing@450w2x.png 900w, /images/2018/06/eks-ecs-load-balacing@330w.png 330w, /images/2018/06/eks-ecs-load-balacing@330w2x.png 660w, /images/2018/06/eks-ecs-load-balacing@545w.png 545w, /images/2018/06/eks-ecs-load-balacing@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/06/eks-ecs-load-balacing.png" alt="EKS vs. ECS: Load Balancing" title="EKS vs. ECS: Load Balancing"></picture></p><p>The proxy running on each node is distributing requests randomly or based on the round robin algorithm among all pods running in the cluster. Doing so increases the network traffic between EC2 instances and between AZs which consumes network capacity and adds latency.</p><p>In contrast, the tight integration between ECS and ALB does not require a third routing step and is, therefore, more efficient.</p><h2 id="VPC-and-ENI"><a href="#VPC-and-ENI" class="headerlink" title="VPC and ENI"></a>VPC and ENI</h2><p>Being able to integrate containers running on EKS or ECS into your VPC is excellent. Both EKS and ECS allow attaching an Elastic Network Interface (ENI) to containers. However, there is a slight difference between VPC mode with EKS and ECS.</p><p>As shown in the following figure EKS is attaching multiple ENIs per instance. Multiple private IP addresses are assigned to each ENI. EKS assigns each pod - a group of containers - a private IP address. However, some pods are sharing network interfaces with each other. That is different with ECS as each task - a group of containers - is assigned to a separate ENI.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/06/eks-ecs-vpc-eni@730w.webp 730w, /images/2018/06/eks-ecs-vpc-eni@730w2x.webp 1460w, /images/2018/06/eks-ecs-vpc-eni@610w.webp 610w, /images/2018/06/eks-ecs-vpc-eni@610w2x.webp 1220w, /images/2018/06/eks-ecs-vpc-eni@450w.webp 450w, /images/2018/06/eks-ecs-vpc-eni@450w2x.webp 900w, /images/2018/06/eks-ecs-vpc-eni@330w.webp 330w, /images/2018/06/eks-ecs-vpc-eni@330w2x.webp 660w, /images/2018/06/eks-ecs-vpc-eni@545w.webp 545w, /images/2018/06/eks-ecs-vpc-eni@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/06/eks-ecs-vpc-eni@730w.png 730w, /images/2018/06/eks-ecs-vpc-eni@730w2x.png 1460w, /images/2018/06/eks-ecs-vpc-eni@610w.png 610w, /images/2018/06/eks-ecs-vpc-eni@610w2x.png 1220w, /images/2018/06/eks-ecs-vpc-eni@450w.png 450w, /images/2018/06/eks-ecs-vpc-eni@450w2x.png 900w, /images/2018/06/eks-ecs-vpc-eni@330w.png 330w, /images/2018/06/eks-ecs-vpc-eni@330w2x.png 660w, /images/2018/06/eks-ecs-vpc-eni@545w.png 545w, /images/2018/06/eks-ecs-vpc-eni@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/06/eks-ecs-vpc-eni.png" alt="EKS vs. ECS: VPC and ENI" title="EKS vs. ECS: VPC and ENI"></picture></p><p>The number of ENIs per instance is <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-eni.html#AvailableIpPerENI" target="_blank" rel="noopener">limited from 2 to 15 depending on the instance type</a>. As EKS is sharing ENIs between pods, you can place up to 750 pods per instance. Much more than the maximum of 15 tasks you can place per instance with ECS.</p><p>But sharing ENIs between instances comes with a limitation as well. You are not able to restrict traffic with a security group per pod, as the ENI and therefore the security group is shared with multiple pods.</p><h2 id="IAM"><a href="#IAM" class="headerlink" title="IAM"></a>IAM</h2><p>ECS supports <a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html" target="_blank" rel="noopener">IAM Roles for Tasks</a> which is great to grant containers access to AWS resources. For example, to allow containers to access S3, DynamoDB, SQS, or SES at runtime. Unfortunately, EKS does not support IAM for pods out-of-the-box at the moment.</p><h2 id="Pricing"><a href="#Pricing" class="headerlink" title="Pricing"></a>Pricing</h2><p>Each EKS cluster costs you 0.20 USD per hour which is about 144 USD per month. ECS is free. For both, EKS and ECS you have to pay for the underlying EC2 instances and related resources.</p><h2 id="Compatibility"><a href="#Compatibility" class="headerlink" title="Compatibility"></a>Compatibility</h2><p>EKS offers Kubernetes-as-a-Service for AWS. However, Kubernetes is an option at other cloud providers, on-premises, or even on your developer machine. To put it in other words: Kubernetes offers you a layer of abstraction allowing you to deploy your applications on top of any infrastructure.</p><p>Whereas, ECS is only available on AWS.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>As for now, ECS offers a much deeper integration into the AWS infrastructure than EKS. A strong argument for EKS is the possibility to use the same technology at other cloud providers or on-premises.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Cleaning up an S3 bucket with the help of Athena</title>
      <link>https://cloudonaut.io/cleaning-up-an-s3-bucket-with-the-help-of-athena/</link>
      <description>
        <![CDATA[<p>Imagine your basement or attic would provide unlimited capacity for storing stuff. Sounds great? Maybe at first. But imagine how many stu]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/athena/">athena</category>
      <guid isPermaLink="true">https://cloudonaut.io/cleaning-up-an-s3-bucket-with-the-help-of-athena/</guid>
      <pubDate>Mon, 14 May 2018 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Imagine your basement or attic would provide unlimited capacity for storing stuff. Sounds great? Maybe at first. But imagine how many stuff would pile up over the years if you were not forced to clean up your stuff because of limited storage space every now and then. S3 (Simple Storage Service) offers unlimited storage capacity. And that is why data is piling up endlessly if you are not cleaning up unused data from time to time. You will learn how to detect unused objects within your S3 buckets in the following article.</p><p>S3 provides lifecycle management which allows you to define a lifetime for your objects. The life of an object starts with its upload and ends after the lifetime you have specified in days. In many use cases deleting files after a fixed period of time is not sufficient. For example, because some objects are downloaded regularly for years. Other objects are uploaded once and never downloaded again.</p><p>So how do you clean up files from your S3 bucket that have not been downloaded for a long period of time? By using the following building blocks:</p><ul><li><a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/ServerLogs.html" target="_blank" rel="noopener">S3 Access Logs</a>: writes every request to your S3 bucket into a log file. </li><li><a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/storage-inventory.html" target="_blank" rel="noopener">S3 Inventory</a>: provides a CSV or ORC file containing a list of all objects within a bucket daily.</li><li><a href="https://docs.aws.amazon.com/athena/latest/ug/what-is.html" target="_blank" rel="noopener">Athena</a>: allows you to query structured data stored on S3 ad-hoc.</li></ul><p>We will use Athena to query the access logs and inventory lists from S3 to find objects without any read requests within the last 90 days.</p><h2 id="Configuring-S3-Inventory"><a href="#Configuring-S3-Inventory" class="headerlink" title="Configuring S3 Inventory"></a>Configuring S3 Inventory</h2><p>First of all, you should create a separate S3 bucket to store access logs and inventories, which I have named <code>cloudonaut-io-s3-logs</code>. Next, create a new inventory configuration for the bucket you want to clean up, <code>cloudonaut-io-s3-cleanup</code> in my example. The following screenshot shows the most important configuration options.</p><ol><li>Type in <code>ORC</code> as inventory name.</li><li>Select the logs and inventories bucket, <code>cloudonaut-io-s3-logs</code> in my example.</li><li>Type in <code>inventory</code> as the key prefix.</li><li>Select <code>daily</code> as the delivery frequency for the inventory.</li><li>Choose output format <code>ORC</code> which is a perfect fit when analyzing data with Athena.</li><li>If versioning is not enabled for your bucket, choose <code>Current version only</code>.</li><li>Select all optional fields, better safe than sorry.</li><li>Server-side encryption with <code>AES-256</code> is selected by default, as you export the encryption status.</li></ol><p><a href="/images/2018/05/s3-inventory.png"><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/05/s3-inventory@730w.webp 730w, /images/2018/05/s3-inventory@730w2x.webp 1460w, /images/2018/05/s3-inventory@610w.webp 610w, /images/2018/05/s3-inventory@610w2x.webp 1220w, /images/2018/05/s3-inventory@450w.webp 450w, /images/2018/05/s3-inventory@450w2x.webp 900w, /images/2018/05/s3-inventory@330w.webp 330w, /images/2018/05/s3-inventory@330w2x.webp 660w, /images/2018/05/s3-inventory@545w.webp 545w, /images/2018/05/s3-inventory@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/05/s3-inventory@730w.png 730w, /images/2018/05/s3-inventory@730w2x.png 1460w, /images/2018/05/s3-inventory@610w.png 610w, /images/2018/05/s3-inventory@610w2x.png 1220w, /images/2018/05/s3-inventory@450w.png 450w, /images/2018/05/s3-inventory@450w2x.png 900w, /images/2018/05/s3-inventory@330w.png 330w, /images/2018/05/s3-inventory@330w2x.png 660w, /images/2018/05/s3-inventory@545w.png 545w, /images/2018/05/s3-inventory@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/05/s3-inventory.png" alt="Configuring S3 Inventory" title="Configuring S3 Inventory"></picture></a></p><p>Next, we need to set up the second data source: S3 Access Logs.</p><h2 id="Configuring-S3-Access-Logs"><a href="#Configuring-S3-Access-Logs" class="headerlink" title="Configuring S3 Access Logs"></a>Configuring S3 Access Logs</h2><p>Enable Access Logs for the bucket you want to clean up, <code>cloudonaut-io-s3-cleanup</code> in my example.</p><ul><li>Select the logs and inventories bucket as target bucket, which is <code>cloudonaut-io-s3-logs</code> for me.</li><li>Choose <code>accesslogs/</code> as the prefix for the log files.</li></ul><p><a href="/images/2018/05/s3-access-logs.png"><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/05/s3-access-logs@730w.webp 730w, /images/2018/05/s3-access-logs@730w2x.webp 1460w, /images/2018/05/s3-access-logs@610w.webp 610w, /images/2018/05/s3-access-logs@610w2x.webp 1220w, /images/2018/05/s3-access-logs@450w.webp 450w, /images/2018/05/s3-access-logs@450w2x.webp 900w, /images/2018/05/s3-access-logs@330w.webp 330w, /images/2018/05/s3-access-logs@330w2x.webp 660w, /images/2018/05/s3-access-logs@545w.webp 545w, /images/2018/05/s3-access-logs@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/05/s3-access-logs@730w.png 730w, /images/2018/05/s3-access-logs@730w2x.png 1460w, /images/2018/05/s3-access-logs@610w.png 610w, /images/2018/05/s3-access-logs@610w2x.png 1220w, /images/2018/05/s3-access-logs@450w.png 450w, /images/2018/05/s3-access-logs@450w2x.png 900w, /images/2018/05/s3-access-logs@330w.png 330w, /images/2018/05/s3-access-logs@330w2x.png 660w, /images/2018/05/s3-access-logs@545w.png 545w, /images/2018/05/s3-access-logs@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/05/s3-access-logs.png" alt="Configuring S3 Access Logs" title="Configuring S3 Access Logs"></picture></a></p><p>It will take up to 48 hours until access logs and the inventory are available. It makes sense to stop here and wait until the needed data is available before proceeding with the next steps. </p><h2 id="Creating-Athena-tables"><a href="#Creating-Athena-tables" class="headerlink" title="Creating Athena tables"></a>Creating Athena tables</h2><p>Athena is a query service which we will use to query the access logs as well as the inventory. Athena executes ad-hoc queries on data stored on S3. To be able to query data tables we need to define temporary tables.</p><p>Open Athena’s <a href="https://eu-west-1.console.aws.amazon.com/athena/home?force&region=eu-west-1#query" target="_blank" rel="noopener">Query Editor</a> and run the following query to create the <code>inventory</code> table. The table accesses the inventory files stored on S3 in ORC format. You have to replace the name of the S3 bucket <code>cloudonaut-s3-logs</code> with your bucket containing the logs and inventories.</p><figure class="highlight n1ql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> EXTERNAL TABLE inventory(</span><br><span class="line">  <span class="keyword">bucket</span> <span class="keyword">string</span>,</span><br><span class="line">  <span class="keyword">key</span> <span class="keyword">string</span>,</span><br><span class="line">  size bigint,</span><br><span class="line">  last_modified_date timestamp,</span><br><span class="line">  e_tag <span class="keyword">string</span>,</span><br><span class="line">  storage_class <span class="keyword">string</span>,</span><br><span class="line">  is_multipart_uploaded <span class="keyword">boolean</span>,</span><br><span class="line">  replication_status <span class="keyword">string</span>,</span><br><span class="line">  encryption_status <span class="keyword">string</span></span><br><span class="line">  )</span><br><span class="line">  PARTITIONED <span class="keyword">BY</span> (dt <span class="keyword">string</span>)</span><br><span class="line">  ROW FORMAT SERDE <span class="string">&#x27;org.apache.hadoop.hive.ql.io.orc.OrcSerde&#x27;</span></span><br><span class="line">  STORED <span class="keyword">AS</span> INPUTFORMAT <span class="string">&#x27;org.apache.hadoop.hive.ql.io.SymlinkTextInputFormat&#x27;</span></span><br><span class="line">  OUTPUTFORMAT  <span class="string">&#x27;org.apache.hadoop.hive.ql.io.IgnoreKeyTextOutputFormat&#x27;</span></span><br><span class="line">  LOCATION <span class="string">&#x27;s3://cloudonaut-io-s3-logs/inventory/cloudonaut-io-s3-cleanup/ORC/hive&#x27;</span>;</span><br></pre></td></tr></table></figure><p>The access logs are stored in CSV-alike files on S3. The following query will create the table containing the access logs. A regular expression is used to parse the S3 access log files with Athena. You have to replace the name of the S3 bucket <code>cloudonaut-s3-logs</code> with your bucket containing the logs and inventories.</p><figure class="highlight taggerscript"><table><tr><td class="code"><pre><span class="line">CREATE EXTERNAL TABLE accesslogs(</span><br><span class="line">  owner string,</span><br><span class="line">  bucket string,</span><br><span class="line">  time string,</span><br><span class="line">  remoteip string,</span><br><span class="line">  requester string,</span><br><span class="line">  requestid string,</span><br><span class="line">  operation string,</span><br><span class="line">  key string,</span><br><span class="line">  requesturi string,</span><br><span class="line">  httpstatus int,</span><br><span class="line">  errorcode string,</span><br><span class="line">  bytessent int,</span><br><span class="line">  objectsize int,</span><br><span class="line">  totaltime int,</span><br><span class="line">  torunaroundtime int,</span><br><span class="line">  referrer string,</span><br><span class="line">  useragent string,</span><br><span class="line">  versionid string)</span><br><span class="line">  ROW FORMAT SERDE &#x27;org.apache.hadoop.hive.serde2.RegexSerDe&#x27;</span><br><span class="line">  WITH SERDEPROPERTIES (</span><br><span class="line">    &#x27;serialization.format&#x27; = &#x27;1&#x27;,</span><br><span class="line">    &#x27;input.regex&#x27; = &#x27;^([^<span class="symbol">\\</span>s]*)<span class="symbol">\\</span>s([^<span class="symbol">\\</span>s]*)<span class="symbol">\\</span>s<span class="symbol">\\</span>[([^<span class="symbol">\\</span>]]*)<span class="symbol">\\</span>]<span class="symbol">\\</span>s([^<span class="symbol">\\</span>s]*)<span class="symbol">\\</span>s([^<span class="symbol">\\</span>s]*)<span class="symbol">\\</span>s([^<span class="symbol">\\</span>s]*)<span class="symbol">\\</span>s([^<span class="symbol">\\</span>s]*)<span class="symbol">\\</span>s([^<span class="symbol">\\</span>s]*)<span class="symbol">\\</span>s<span class="symbol">\\</span>&quot;([^<span class="symbol">\\</span>&quot;]*)<span class="symbol">\\</span>&quot;<span class="symbol">\\</span>s([^<span class="symbol">\\</span>s]*)<span class="symbol">\\</span>s([^<span class="symbol">\\</span>s]*)<span class="symbol">\\</span>s([^<span class="symbol">\\</span>s]*)<span class="symbol">\\</span>s([^<span class="symbol">\\</span>s]*)<span class="symbol">\\</span>s([^<span class="symbol">\\</span>s]*)<span class="symbol">\\</span>s([^<span class="symbol">\\</span>s]*)<span class="symbol">\\</span>s<span class="symbol">\\</span>&quot;([^<span class="symbol">\\</span>&quot;]*)<span class="symbol">\\</span>&quot;<span class="symbol">\\</span>s<span class="symbol">\\</span>&quot;([^<span class="symbol">\\</span>&quot;]*)<span class="symbol">\\</span>&quot;<span class="symbol">\\</span>s([^<span class="symbol">\\</span>s]*)$&#x27;</span><br><span class="line">  )</span><br><span class="line">  LOCATION &#x27;s3://cloudonaut-io-s3-logs/accesslogs/&#x27;;</span><br></pre></td></tr></table></figure><p>We have prepared everything we need to detect objects that have not been accessed for a long period of time.</p><h2 id="Detecting-unused-objects-with-Athena"><a href="#Detecting-unused-objects-with-Athena" class="headerlink" title="Detecting unused objects with Athena"></a>Detecting unused objects with Athena</h2><p>Before running any queries you need to update the partitions of the <code>inventory</code> table. Run the following query to do so.</p><figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">MSCK REPAIR TABLE inventory<span class="comment">;</span></span><br></pre></td></tr></table></figure><p>The <code>accesslogs</code> table is not partitioned by default. Therefore, you should think about limiting the number of access log files that Athena needs to scan. For example, by using a lifecycle policy to delete access logs after 90 days.</p><p>The following query lists the keys of all objects that haven’t been read within the last 90 days.</p><figure class="highlight sas"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">DISTINCT</span> i.<span class="keyword">key</span> <span class="keyword">AS</span> <span class="keyword">key</span> <span class="comment">/* select all object keys that have been recorded within the last 2 days */</span></span><br><span class="line">  <span class="keyword">FROM</span> inventory <span class="keyword">AS</span> i</span><br><span class="line">  <span class="keyword">WHERE</span> i.dt &gt; format_<span class="meta">datetime</span>(date_add(<span class="string">&#x27;day&#x27;</span>, -2, now()), <span class="string">&#x27;yyyy-MM-dd&#x27;</span>)</span><br><span class="line">EXCEPT</span><br><span class="line"><span class="keyword">SELECT</span> <span class="keyword">DISTINCT</span> s.<span class="keyword">key</span> <span class="keyword">AS</span> <span class="keyword">key</span> <span class="comment">/* select all object keys that have been accessed within the last 90 days */</span></span><br><span class="line">  <span class="keyword">FROM</span> accesslogs <span class="keyword">AS</span> s </span><br><span class="line">  <span class="keyword">WHERE</span> s.operation = <span class="string">&#x27;REST.GET.OBJECT&#x27;</span> </span><br><span class="line">  <span class="keyword">AND</span> parse_<span class="meta">datetime</span>(s.time, <span class="string">&#x27;dd/MMM/yyyy:HH:mm:ss ZZ&#x27;</span>) &gt; date_add(<span class="string">&#x27;day&#x27;</span>, -90, now())</span><br></pre></td></tr></table></figure><p>The output of the query gives you a list of objects which are not used anymore. You could use the list to delete all these objects manually or automatically.</p><p><a href="/images/2018/05/athena-query.png"><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/05/athena-query@730w.webp 730w, /images/2018/05/athena-query@730w2x.webp 1460w, /images/2018/05/athena-query@610w.webp 610w, /images/2018/05/athena-query@610w2x.webp 1220w, /images/2018/05/athena-query@450w.webp 450w, /images/2018/05/athena-query@450w2x.webp 900w, /images/2018/05/athena-query@330w.webp 330w, /images/2018/05/athena-query@330w2x.webp 660w, /images/2018/05/athena-query@545w.webp 545w, /images/2018/05/athena-query@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/05/athena-query@730w.png 730w, /images/2018/05/athena-query@730w2x.png 1460w, /images/2018/05/athena-query@610w.png 610w, /images/2018/05/athena-query@610w2x.png 1220w, /images/2018/05/athena-query@450w.png 450w, /images/2018/05/athena-query@450w2x.png 900w, /images/2018/05/athena-query@330w.png 330w, /images/2018/05/athena-query@330w2x.png 660w, /images/2018/05/athena-query@545w.png 545w, /images/2018/05/athena-query@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/05/athena-query.png" alt="Query Athena" title="Query Athena"></picture></a></p><p>As Athena allows you to query the access logs and inventory in a very flexible way you are able to adapt the query to your specific needs. For example, you could rest the lifetime of an object not only when the object is downloaded but also when a new version of the object is uploaded.</p><p>Go and spring-clean your S3 buckets!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Cloud adaption strategy: event-based data synchronization</title>
      <link>https://cloudonaut.io/cloud-adaption-strategy-event-based-data-synchronization/</link>
      <description>
        <![CDATA[<p>Are you building an application for the cloud without the slightest dependency to an on-premises infrastructure? Lucky you, most of us ar]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/rds/">rds</category>
      <category domain="https://cloudonaut.io/tag/dynamodb/">dynamodb</category>
      <category domain="https://cloudonaut.io/tag/kinesis/">kinesis</category>
      <guid isPermaLink="true">https://cloudonaut.io/cloud-adaption-strategy-event-based-data-synchronization/</guid>
      <pubDate>Wed, 02 May 2018 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Are you building an application for the cloud without the slightest dependency to an on-premises infrastructure? Lucky you, most of us are struggling with uniting an outdated on-premises infrastructure with the shiny cloud. I’d like to share a cloud adaption pattern that we have implemented for some of our clients: event-based data synchronization. The cloud adaption pattern focuses on integrating your legacy with your new environment without corrupting the benefits of the cloud.</p><p>Read on, if your answer to one of the following questions is YES!</p><ul><li>Are you building a new product or feature which needs to access some data stored on-premises?</li><li>Are you re-writing your application and moving to the cloud step by step (also known as <a href="https://www.martinfowler.com/bliki/StranglerApplication.html" target="_blank" rel="noopener">Strangler Application</a>?</li></ul><p>There are two party poopers for a successful journey to the cloud:</p><ol><li>Outdated and old-fashioned database technology</li><li>A VPN or a dedicated network connection between your data center and AWS</li></ol><p>Make sure a doorkeeper prevents both of them from crushing your party.</p><h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><p>Before diving into the details, let us start with an overview of the cloud adaption pattern. The following figure illustrates the components of the event-based data synchronization pattern.</p><ul><li>On-premises Relational Database: stores parts of the data your application needs to access.</li><li>On-premises Change Event Publisher: creates a change event whenever someone creates, updates, or deletes a relevant row within the relational database.</li><li>Internet connection: used to transport change events from your corporate data center to AWS.</li><li>DynamoDB: the NoSQL database stores the data relevant to your application.</li><li>Lambda: runs your code which accesses the data stored on DynamoDB.</li><li>API Gateway: the entry point into your application authorizing and validating incoming requests.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/05/cdc-lambda@730w.webp 730w, /images/2018/05/cdc-lambda@730w2x.webp 1460w, /images/2018/05/cdc-lambda@610w.webp 610w, /images/2018/05/cdc-lambda@610w2x.webp 1220w, /images/2018/05/cdc-lambda@450w.webp 450w, /images/2018/05/cdc-lambda@450w2x.webp 900w, /images/2018/05/cdc-lambda@330w.webp 330w, /images/2018/05/cdc-lambda@330w2x.webp 660w, /images/2018/05/cdc-lambda@545w.webp 545w, /images/2018/05/cdc-lambda@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/05/cdc-lambda@730w.png 730w, /images/2018/05/cdc-lambda@730w2x.png 1460w, /images/2018/05/cdc-lambda@610w.png 610w, /images/2018/05/cdc-lambda@610w2x.png 1220w, /images/2018/05/cdc-lambda@450w.png 450w, /images/2018/05/cdc-lambda@450w2x.png 900w, /images/2018/05/cdc-lambda@330w.png 330w, /images/2018/05/cdc-lambda@330w2x.png 660w, /images/2018/05/cdc-lambda@545w.png 545w, /images/2018/05/cdc-lambda@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/05/cdc-lambda.png" alt="..." title="..."></picture></p><p>There is only a loose coupling between the old world, your legacy applications and on-premises infrastructure, and the new world, the application you are building on top of AWS. The described approach comes with the following benefits:</p><ul><li>High Availability: all parts of your application and infrastructure are distributed among multiple machines in multiple availability zones which leads to a highly available system. Each downtime of the legacy application or corporate data center will not impact the availability of your application in the cloud.</li><li>Scalability: all parts of the cloud infrastructure can scale automatically. Neither the legacy application nor the corporate data center’s infrastructure is adding a bottleneck to your architecture.</li><li>Pay-per-use pricing: all cloud resources are billed per usage. You will not have to pay for idling resources any longer.</li></ul><p>Make sure you are neither introducing outdated and old-fashioned database technology or nor a VPN or dedicated network connection between your data center and AWS. Both will corrupt the described benefits.</p><p>Maybe you are not into a serverless architecture based on Lambda, API Gateway, and DynamoDB, yet. Fine, but not a reason for abandoning the event-based data synchronization pattern as shown in the following figure.</p><ul><li>On-premises Relational Database: stores parts of the data your application needs to access.</li><li>On-premises Change Event Publisher: creates a change event whenever someone creates, updates, or deletes a relevant row within the relational database.</li><li>Internet connection: used to transport change events from your corporate data center to AWS.</li><li>Kinesis: scalable and managed stream provided by AWS.</li><li>Change Event Subscriber: a small service subscribing to the Kinesis data stream and transform each event into a CREATE, UPDATE, or DELETE statement.</li><li>RDS Aurora: the MySQL or Postgres compatible relational database storing your application’s data.</li><li>EC2 instances: your application is running on a fleet of virtual machines.</li><li>Load Balancer: the entry point into your infrastructure distributing requests among your fleet of EC2 instances.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/05/cdc-ec2@730w.webp 730w, /images/2018/05/cdc-ec2@730w2x.webp 1460w, /images/2018/05/cdc-ec2@610w.webp 610w, /images/2018/05/cdc-ec2@610w2x.webp 1220w, /images/2018/05/cdc-ec2@450w.webp 450w, /images/2018/05/cdc-ec2@450w2x.webp 900w, /images/2018/05/cdc-ec2@330w.webp 330w, /images/2018/05/cdc-ec2@330w2x.webp 660w, /images/2018/05/cdc-ec2@545w.webp 545w, /images/2018/05/cdc-ec2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/05/cdc-ec2@730w.png 730w, /images/2018/05/cdc-ec2@730w2x.png 1460w, /images/2018/05/cdc-ec2@610w.png 610w, /images/2018/05/cdc-ec2@610w2x.png 1220w, /images/2018/05/cdc-ec2@450w.png 450w, /images/2018/05/cdc-ec2@450w2x.png 900w, /images/2018/05/cdc-ec2@330w.png 330w, /images/2018/05/cdc-ec2@330w2x.png 660w, /images/2018/05/cdc-ec2@545w.png 545w, /images/2018/05/cdc-ec2@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/05/cdc-ec2.png" alt="..." title="..."></picture></p><p>After explaining the event-based data synchronization in general, I’d like to dive into the details next.</p><h2 id="Change-Data-Capture-CDC"><a href="#Change-Data-Capture-CDC" class="headerlink" title="Change Data Capture (CDC)"></a>Change Data Capture (CDC)</h2><p>To be able to synchronize data from your on-premises database to your database in the cloud, you need to know whenever data has been modified.</p><p>The change event publisher implements the following process:</p><ol><li>Someone changes data in the MSSQL database.</li><li>The MSSQL database tracks the change.</li><li>The change event publisher polls for changes and fetches the information about changed data.</li><li>The change event publisher sends the data to DynamoDB or Kinesis.</li></ol><p>Most traditional relational database management systems (RDBMS) provide the ability to track changes with the help of a change data capture (CDC) or change tracking mechanism. For example, an MSSQL server can answer the question: what rows of the table <code>users</code> have changed? To enable change tracking, you need to activated change tracking for the database.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">DATABASE</span> mydatabase <span class="keyword">SET</span> CHANGE_TRACKING = <span class="keyword">ON</span> (CHANGE_RETENTION = <span class="number">14</span> DAYS, AUTO_CLEANUP = <span class="keyword">ON</span>)</span><br></pre></td></tr></table></figure><p>As well as for each table you need to track.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> [mydatabase].[users] <span class="keyword">ENABLE</span> CHANGE_TRACKING <span class="keyword">WITH</span> (TRACK_COLUMNS_UPDATED = <span class="keyword">ON</span>)</span><br></pre></td></tr></table></figure><p>Next, the change event publisher can ask for changed rows with the following query.</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="selector-tag">SELECT</span> * <span class="selector-tag">FROM</span> CHANGETABLE(CHANGES <span class="selector-attr">[mydatabase]</span>.<span class="selector-attr">[users]</span>, last_sync_version)</span><br></pre></td></tr></table></figure><p>Based on the change information provided by the relational database management systems (RDBMS) the change event publisher creates an event for each changed row.</p><h2 id="Synchronize-over-the-Internet"><a href="#Synchronize-over-the-Internet" class="headerlink" title="Synchronize over the Internet"></a>Synchronize over the Internet</h2><p>Establishing a VPN or a dedicated network connection between your data center and AWS adds complexity and should be avoided where necessary. To transfer change events from on-premises to AWS all you need is Internet connectivity for the change event publisher running in your corporate data center. Both DynamoDB and Kinesis are accessible via the Internet. Authentication and authorization are handled on layer 7 (the application layer) with the help of Identity and Access Management (IAM). Of course, data is encrypted in transit (HTTPS).</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Often when building applications for the cloud it is necessary to access data that is stored in databases located in the corporate data center. However, it is not advisable to make use of outdated and old-fashioned database technology and a VPN or dedicated network connection between your data center and AWS. Doing so ruins the main benefits offered by the cloud: high availability, scalability, and  pay-per-use pricing. Use loose coupling instead by synchronizing changes from the on-premises database to AWS.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Sharing data volumes between machines: EFS</title>
      <link>https://cloudonaut.io/sharing-data-volumes-between-machines-efs/</link>
      <description>
        <![CDATA[<p>Many legacy applications store state in files on disk. Therefore, using Amazon S3, an object store, is impossible by default. Using block]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/efs/">efs</category>
      <guid isPermaLink="true">https://cloudonaut.io/sharing-data-volumes-between-machines-efs/</guid>
      <pubDate>Thu, 26 Apr 2018 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Many legacy applications store state in files on disk. Therefore, using Amazon S3, an object store, is impossible by default. Using block storage might be an option, but it won’t allow access to files from multiple machines in parallel. Hence you need a way to share the files between virtual machines. With Elastic File System (EFS), you can share data between multiple EC2 instances and your data is replicated between multiple Availability Zones (AZ). EFS is based on the NFSv4.1 protocol, which allows you to mount it like any other file system. In this article you’ll learn about the basics of EFS.</p><p><em>Note: EFS only works with Linux. At this time, EFS isn’t supported by Windows EC2 instances.</em></p><p>Let’s have a closer look at how EFS works compared to Elastic Block Store (EBS) and Instance Store. An EBS volume is tied to a data center, also called Availability Zone (AZ), and can only be attached over the network to a single EC2 Instance from the same data center. Usually EBS volume are used as the root volumes, which contain the operating system, or for relational database systems to store the state. An Instance Store consists of a hard drive directly attached to the hardware which the virtual machine is running on. Instance Store can be regarded ephemeral storage and is used for caching or for NoSQL database with embedded data replication only. In contrast, the EFS file system can be used by multiple EC2 instances from different data centers in parallel. Additionally, the data of the EFS file system is replicated among multiple data centers and remains available even if a whole data center suffers from an outage, which isn’t true for EBS and Instance Store. The following figure shows the differences.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/04/efs-ebs-instancestore@730w.webp 730w, /images/2018/04/efs-ebs-instancestore@730w2x.webp 1460w, /images/2018/04/efs-ebs-instancestore@610w.webp 610w, /images/2018/04/efs-ebs-instancestore@610w2x.webp 1220w, /images/2018/04/efs-ebs-instancestore@450w.webp 450w, /images/2018/04/efs-ebs-instancestore@450w2x.webp 900w, /images/2018/04/efs-ebs-instancestore@330w.webp 330w, /images/2018/04/efs-ebs-instancestore@330w2x.webp 660w, /images/2018/04/efs-ebs-instancestore@545w.webp 545w, /images/2018/04/efs-ebs-instancestore@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/04/efs-ebs-instancestore@730w.png 730w, /images/2018/04/efs-ebs-instancestore@730w2x.png 1460w, /images/2018/04/efs-ebs-instancestore@610w.png 610w, /images/2018/04/efs-ebs-instancestore@610w2x.png 1220w, /images/2018/04/efs-ebs-instancestore@450w.png 450w, /images/2018/04/efs-ebs-instancestore@450w2x.png 900w, /images/2018/04/efs-ebs-instancestore@330w.png 330w, /images/2018/04/efs-ebs-instancestore@330w2x.png 660w, /images/2018/04/efs-ebs-instancestore@545w.png 545w, /images/2018/04/efs-ebs-instancestore@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/04/efs-ebs-instancestore.png" alt="Comparing EBS, Instance Store, and EFS File System" title="Comparing EBS, Instance Store, and EFS File System"></picture></p><p>Now that you know about the differences, it’s time to have a closer look at EFS. Two main components require your attention:</p><ol><li>EFS File System: Stores your data</li><li>EFS Mount Target: Makes your data accessible</li></ol><blockquote><p>This article, excerpted from chapter 10 of <a href="http://bit.ly/amazon-web-services-in-action-2nd-edition" target="_blank" rel="noopener">Amazon Web Services in Action, Second Edition</a>.</p><p align="center"><img src="/images/2017/09/aws-in-action-2nd-meap.png" alt="Amazon Web Services in Action, Second Edition" style="max-width: 300px"></p>Save 37% off [Amazon Web Services in Action, Second Edition](http://bit.ly/amazon-web-services-in-action-2nd-edition) with code `fccwittig` at [manning.com](http://bit.ly/amazon-web-services-in-action-2nd-edition).</blockquote><p>The EFS File System is the resource that stores your data in an AWS region. But you can’t access the file system directly. To access your file system, you must create an EFS Mount Target in a subnet. The mount target provides a network endpoint that you can use to mount the file system via NFSv4.1. With the mount target endpoint, you can finally mount the EFS File System on an EC2 Instance. The EC2 Instance must be in the same subnet as the EFS Mount Target, but you can create mount targets in multiple subnets. The following figure demonstrates how to access an EFS File System from EC2 instances running in multiple subnets.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/04/efs-overview@730w.webp 730w, /images/2018/04/efs-overview@730w2x.webp 1460w, /images/2018/04/efs-overview@610w.webp 610w, /images/2018/04/efs-overview@610w2x.webp 1220w, /images/2018/04/efs-overview@450w.webp 450w, /images/2018/04/efs-overview@450w2x.webp 900w, /images/2018/04/efs-overview@330w.webp 330w, /images/2018/04/efs-overview@330w2x.webp 660w, /images/2018/04/efs-overview@545w.webp 545w, /images/2018/04/efs-overview@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/04/efs-overview@730w.png 730w, /images/2018/04/efs-overview@730w2x.png 1460w, /images/2018/04/efs-overview@610w.png 610w, /images/2018/04/efs-overview@610w2x.png 1220w, /images/2018/04/efs-overview@450w.png 450w, /images/2018/04/efs-overview@450w2x.png 900w, /images/2018/04/efs-overview@330w.png 330w, /images/2018/04/efs-overview@330w2x.png 660w, /images/2018/04/efs-overview@545w.png 545w, /images/2018/04/efs-overview@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/04/efs-overview.png" alt="ount targets provide an endpoint for EC2 instances to mount the file system in a subnet" title="ount targets provide an endpoint for EC2 instances to mount the file system in a subnet"></picture></p><p>Equipped with the EFS theory about file systems and mount targets, you can now apply your knowledge to solve a real problem. The Linux operating system is a multiuser system. Many users can store data and run programs isolated from each other. Each Linux user can have a home directory which is usually stored under <code>/home/$username</code>. If the user name is <code>michael</code>, the home directory would be <code>/home/michael</code>. And only the <code>michael</code> user would be allowed to read and write in <code>/home/michael</code>. The <code>ls -d -l /home/*</code> command list all home directories.</p><figure class="highlight crmsh"><table><tr><td class="code"><pre><span class="line">$  ls -d -l /home/*  (<span class="number">1</span>)</span><br><span class="line">drwx------ <span class="number">2</span> andreas    andreas    <span class="number">4096</span> Jul <span class="number">24</span> <span class="number">06</span>:<span class="number">25</span> /home/andreas  (<span class="number">2</span>)</span><br><span class="line">drwx------ <span class="number">3</span> michael    michael    <span class="number">4096</span> Jul <span class="number">24</span> <span class="number">06</span>:<span class="number">38</span> /home/michael  (<span class="number">3</span>)</span><br><span class="line"></span><br><span class="line">(<span class="number">1</span>) List all home directories with absolute paths</span><br><span class="line">(<span class="number">2</span>) /home/andreas can only be accessed by the <span class="keyword">user</span> <span class="title">and</span> <span class="keyword">group</span> <span class="title">andreas</span></span><br><span class="line">(<span class="number">3</span>) /home/michael can only be accessed by the <span class="keyword">user</span> <span class="title">and</span> <span class="keyword">group</span> <span class="title">michael</span></span><br></pre></td></tr></table></figure><p>If you’re using multiple EC2 instances, your users will have a separate home folder on each EC2 instance. If a Linux user uploads a file on one EC2 instance, she can’t access the file on another EC2 instance. To solve this problem, you’ll create an EFS File System and mount the EFS File System on each EC2 Instance under &#96;&#x2F;home. The home directories are then shared across all your EC2 instances and users feel at home when they login no matter on which virtual machine. </p><p>That’s all for this article. If you want to know more, check out the 10th chapter of <a href="http://bit.ly/amazon-web-services-in-action-2nd-edition" target="_blank" rel="noopener">Amazon Web Services in Action, Second Edition</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Behind the scenes of the EC2 network performance benchmark</title>
      <link>https://cloudonaut.io/behind-the-scences-ec2-network-performance-benchmark/</link>
      <description>
        <![CDATA[<p>What is the maximum network throughput you can expect from an EC2 instance of type <code>t2.large</code>? How much does the network perfo]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/s3/">s3</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/athena/">athena</category>
      <guid isPermaLink="true">https://cloudonaut.io/behind-the-scences-ec2-network-performance-benchmark/</guid>
      <pubDate>Fri, 20 Apr 2018 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>What is the maximum network throughput you can expect from an EC2 instance of type <code>t2.large</code>? How much does the network performance increase when switching from a <code>t2.large</code> to <code>m5.large</code> instance? All these kind of questions are hard to answer, as AWS does not disclose the network capacity of all their instance types in detail. Therefore, I ran a network performance benchmark with almost all instance types. The <a href="/ec2-network-performance-cheat-sheet/">EC2 Network Performance Cheat Sheet</a> shows a rough estimation of the network throughput you can expect from the different instance types.</p><p>This blog post focuses on what I have learned while measuring the network capabilities of EC2 instances by using the following building blocks:</p><ul><li>EC2 to run the network benchmark.</li><li>S3 to store the benchmark results.</li><li>Athena to analyze the benchmark results.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/04/ec2-network-performance-benchmark@730w.webp 730w, /images/2018/04/ec2-network-performance-benchmark@730w2x.webp 1460w, /images/2018/04/ec2-network-performance-benchmark@610w.webp 610w, /images/2018/04/ec2-network-performance-benchmark@610w2x.webp 1220w, /images/2018/04/ec2-network-performance-benchmark@450w.webp 450w, /images/2018/04/ec2-network-performance-benchmark@450w2x.webp 900w, /images/2018/04/ec2-network-performance-benchmark@330w.webp 330w, /images/2018/04/ec2-network-performance-benchmark@330w2x.webp 660w, /images/2018/04/ec2-network-performance-benchmark@545w.webp 545w, /images/2018/04/ec2-network-performance-benchmark@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/04/ec2-network-performance-benchmark@730w.png 730w, /images/2018/04/ec2-network-performance-benchmark@730w2x.png 1460w, /images/2018/04/ec2-network-performance-benchmark@610w.png 610w, /images/2018/04/ec2-network-performance-benchmark@610w2x.png 1220w, /images/2018/04/ec2-network-performance-benchmark@450w.png 450w, /images/2018/04/ec2-network-performance-benchmark@450w2x.png 900w, /images/2018/04/ec2-network-performance-benchmark@330w.png 330w, /images/2018/04/ec2-network-performance-benchmark@330w2x.png 660w, /images/2018/04/ec2-network-performance-benchmark@545w.png 545w, /images/2018/04/ec2-network-performance-benchmark@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/04/ec2-network-performance-benchmark.png" alt="EC2 Network Performance Benchmark" title="EC2 Network Performance Benchmark"></picture></p><h2 id="EC2"><a href="#EC2" class="headerlink" title="EC2"></a>EC2</h2><p>After a little bit of research, I decided to use <a href="https://iperf.fr/" target="_blank" rel="noopener">iperf3</a> a reliable speed test tool for TCP and UDP.</p><p>For testing the network performance of each instance type I have launched a <code>c5.18xlarge</code> and started iperf3 in server mode.</p><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="attribute">iperf3</span> -s</span><br></pre></td></tr></table></figure><p>Next, I have created an instance of the instance type under test and ran iperf3 in client mode. By default, iperf3 is using a single stream which is not enough to saturate the network throughput of many instance types. The option <code>-P 10</code> does the trick by using 10 parallel streams.</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">iperf3 <span class="attr">--client</span> <span class="number">10.0</span>.<span class="number">0.10</span> <span class="attr">--time</span> <span class="number">3600</span> <span class="attr">--interval</span> <span class="number">60</span> <span class="attr">--version4</span> <span class="attr">--json</span> -<span class="selector-tag">P</span> <span class="number">10</span></span><br></pre></td></tr></table></figure><p>Did you know some instance types provide network burst? So, an instance can use a huge amount of network capacity, but only for a short period comparable to CPU burst of <code>t2</code> instances.</p><blockquote><p>Instance types that use the ENA and support up to 10 Gbps of throughput use network I&#x2F;O credit mechanism to allocate network bandwidth to instances based on average bandwidth utilization. (<a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/compute-optimized-instances.html#compute-network-performance" target="_blank" rel="noopener">AWS Documentation: Compute Optimized Instances</a>)</p></blockquote><p>I was not aware of that fact. Therefore, my first attempt measuring the network performance for only 60 seconds generated misleading results. To solve the problem, I had to re-run the network benchmark with a duration of 60 minutes (<code>--time 3600</code>) for each instance type. Generally speaking, you should run any load tests for at least 60 minutes to make sure you do not measure any burst capacities. </p><p>Furthermore, I have learned the hard way that spinning up a server and a client instance for 77 instance types is quite costly: 154 instance hours ranging from USD 0.0063 to USD 32.00 are definitely not covered by the Free Tier. :-D Switching from on-demand to spot instances reduced costs significantly. But some instance types are hardly available on the spot market (e.g., <code>h1.2xlarge</code>, <code>d2.8xlarge</code>, <code>g3.8xlarge</code>, …).</p><h2 id="S3"><a href="#S3" class="headerlink" title="S3"></a>S3</h2><p>To be able to analyze the benchmark results later, each client instance uploaded its results to S3. The <code>--json</code> flag tells <code>iperf3</code> to generate a JSON output.</p><p>The following snippet shows an excerpt of a JSON file including the benchmark results of a <code>t2.nano</code> instance. For every interval - 60 seconds <code>--interval 60</code> - <code>iperf3</code> has captured a data point.</p><figure class="highlight nim"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;start&quot;</span>: <span class="meta">&#123;...&#125;</span>,</span><br><span class="line">  <span class="string">&quot;intervals&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;streams&quot;</span>: [...],</span><br><span class="line">      <span class="string">&quot;sum&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;start&quot;</span>: <span class="number">0</span>,</span><br><span class="line">        <span class="string">&quot;end&quot;</span>: <span class="number">60</span>.<span class="number">000137</span>,</span><br><span class="line">        <span class="string">&quot;seconds&quot;</span>: <span class="number">60</span>.<span class="number">000137</span>,</span><br><span class="line">        <span class="string">&quot;bytes&quot;</span>: <span class="number">3605632641</span>,</span><br><span class="line">        <span class="string">&quot;bits_per_second&quot;</span>: <span class="number">480749918</span>.<span class="number">45076</span>,</span><br><span class="line">        <span class="string">&quot;retransmits&quot;</span>: <span class="number">4378</span>,</span><br><span class="line">        <span class="string">&quot;omitted&quot;</span>: <span class="literal">false</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;streams&quot;</span>: [...],</span><br><span class="line">      <span class="string">&quot;sum&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;start&quot;</span>: <span class="number">60</span>.<span class="number">000137</span>,</span><br><span class="line">        <span class="string">&quot;end&quot;</span>: <span class="number">120</span>.<span class="number">000128</span>,</span><br><span class="line">        <span class="string">&quot;seconds&quot;</span>: <span class="number">59</span>.<span class="number">999992</span>,</span><br><span class="line">        <span class="string">&quot;bytes&quot;</span>: <span class="number">3064683489</span>,</span><br><span class="line">        <span class="string">&quot;bits_per_second&quot;</span>: <span class="number">408624517</span>.<span class="number">159294</span>,</span><br><span class="line">        <span class="string">&quot;retransmits&quot;</span>: <span class="number">3713</span>,</span><br><span class="line">        <span class="string">&quot;omitted&quot;</span>: <span class="literal">false</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;streams&quot;</span>: [],</span><br><span class="line">      <span class="string">&quot;sum&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;start&quot;</span>: <span class="number">120</span>.<span class="number">000128</span>,</span><br><span class="line">        <span class="string">&quot;end&quot;</span>: <span class="number">180</span>.<span class="number">000335</span>,</span><br><span class="line">        <span class="string">&quot;seconds&quot;</span>: <span class="number">60</span>.<span class="number">000206</span>,</span><br><span class="line">        <span class="string">&quot;bytes&quot;</span>: <span class="number">2088768192</span>,</span><br><span class="line">        <span class="string">&quot;bits_per_second&quot;</span>: <span class="number">278501469</span>.<span class="number">441085</span>,</span><br><span class="line">        <span class="string">&quot;retransmits&quot;</span>: <span class="number">2481</span>,</span><br><span class="line">        <span class="string">&quot;omitted&quot;</span>: <span class="literal">false</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    ...</span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;end&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;streams&quot;</span>: [...],</span><br><span class="line">    <span class="string">&quot;sum_sent&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;start&quot;</span>: <span class="number">0</span>,</span><br><span class="line">      <span class="string">&quot;end&quot;</span>: <span class="number">3600</span>.<span class="number">000228</span>,</span><br><span class="line">      <span class="string">&quot;seconds&quot;</span>: <span class="number">3600</span>.<span class="number">000228</span>,</span><br><span class="line">      <span class="string">&quot;bytes&quot;</span>: <span class="number">22330189128</span>,</span><br><span class="line">      <span class="string">&quot;bits_per_second&quot;</span>: <span class="number">49622639</span>.<span class="number">361603</span>,</span><br><span class="line">      <span class="string">&quot;retransmits&quot;</span>: <span class="number">35926</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&quot;sum_received&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;start&quot;</span>: <span class="number">0</span>,</span><br><span class="line">      <span class="string">&quot;end&quot;</span>: <span class="number">3600</span>.<span class="number">000228</span>,</span><br><span class="line">      <span class="string">&quot;seconds&quot;</span>: <span class="number">3600</span>.<span class="number">000228</span>,</span><br><span class="line">      <span class="string">&quot;bytes&quot;</span>: <span class="number">22323897981</span>,</span><br><span class="line">      <span class="string">&quot;bits_per_second&quot;</span>: <span class="number">49608659</span>.<span class="number">035823</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&quot;cpu_utilization_percent&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;host_total&quot;</span>: <span class="number">0</span>.<span class="number">175829</span>,</span><br><span class="line">      <span class="string">&quot;host_user&quot;</span>: <span class="number">0</span>.<span class="number">016777</span>,</span><br><span class="line">      <span class="string">&quot;host_system&quot;</span>: <span class="number">0</span>.<span class="number">159218</span>,</span><br><span class="line">      <span class="string">&quot;remote_total&quot;</span>: <span class="number">2</span>.<span class="number">489102</span>,</span><br><span class="line">      <span class="string">&quot;remote_user&quot;</span>: <span class="number">0</span>.<span class="number">324058</span>,</span><br><span class="line">      <span class="string">&quot;remote_system&quot;</span>: <span class="number">2</span>.<span class="number">165151</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="string">&quot;instanceType&quot;</span>: <span class="string">&quot;t2.nano&quot;</span>,</span><br><span class="line">  <span class="string">&quot;region&quot;</span>: <span class="string">&quot;us-east-1&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Compressing the benchmark results is a best practice to reduce the amount of storage needed on S3 as well as the amount of data that needs to be processed when analyzing the results.</p><p>To be able to query only a subset of the files later, I have used a key schema substituted by the date following by the instance type and a timestamp as illustrated in the following snippet.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">d</span>=<span class="number">2018</span>-<span class="number">04</span>-<span class="number">10</span>/c4.<span class="number">2</span>xlarge-<span class="number">1523348026</span>.json.gz</span><br><span class="line"><span class="attribute">d</span>=<span class="number">2018</span>-<span class="number">04</span>-<span class="number">10</span>/c4.<span class="number">4</span>xlarge-<span class="number">1523348038</span>.json.gz</span><br><span class="line"><span class="attribute">d</span>=<span class="number">2018</span>-<span class="number">04</span>-<span class="number">10</span>/c4.<span class="number">8</span>xlarge-<span class="number">1523348045</span>.json.gz</span><br><span class="line"><span class="attribute">d</span>=<span class="number">2018</span>-<span class="number">04</span>-<span class="number">11</span>/r4.<span class="number">4</span>xlarge-<span class="number">1523379049</span>.json.gz</span><br><span class="line"><span class="attribute">d</span>=<span class="number">2018</span>-<span class="number">04</span>-<span class="number">11</span>/r4.<span class="number">8</span>xlarge-<span class="number">1523381053</span>.json.gz</span><br></pre></td></tr></table></figure><p>With the benchmark results stored on S3, there was only one step left: analyzing the data.</p><h2 id="Athena"><a href="#Athena" class="headerlink" title="Athena"></a>Athena</h2><p>I have used <a href="https://aws.amazon.com/athena/" target="_blank" rel="noopener">Amazon Athena</a> to query the benchmark results stored on S3. The first challenge was to create a table based on the JSON data structure.</p><p>The following statement creates a table with the following columns:</p><ul><li>region: the AWS region read from the first level of the JSON data structure</li><li>instanceType: the instance type read from the first level of the JSON data structure</li><li>intervals: which maps parts of the intervals array from the JSON data structure</li></ul><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">EXTERNAL</span> <span class="keyword">TABLE</span> networkbenchmark (</span><br><span class="line">  `intervals` <span class="keyword">array</span>&lt;struct&lt;</span><br><span class="line">    `sum`:struct&lt;`bits_per_second`:<span class="type">decimal</span>(<span class="number">38</span>,<span class="number">6</span>)&gt;</span><br><span class="line">  &gt;&gt;,</span><br><span class="line">  instanceType string,</span><br><span class="line">  region string</span><br><span class="line">)</span><br><span class="line">PARTITIONED <span class="keyword">BY</span> (d <span class="type">date</span>)</span><br><span class="line"><span class="keyword">ROW</span> <span class="keyword">FORMAT</span> SERDE <span class="string">&#x27;org.openx.data.jsonserde.JsonSerDe&#x27;</span></span><br><span class="line"><span class="keyword">LOCATION</span> <span class="string">&#x27;s3://BUCKETNAME/&#x27;</span>;</span><br></pre></td></tr></table></figure><p>To minimize the amount of data Athena needs to process, I have been using the date <code>d</code> as a partition for the table. Doing so is possible because all object keys start with <code>d=2018-04-10</code>.</p><p>Next, it is necessary to tell Athena to load all partitions for a table with the following statement.</p><figure class="highlight abnf"><table><tr><td class="code"><pre><span class="line">MSCK REPAIR TABLE networkbenchmark<span class="comment">;</span></span><br></pre></td></tr></table></figure><p>Finally, I was ready to analyze the data. However, querying the data was a little bit tricky, as the output from <code>iperf3</code> of type JSON contains 60 data points, one data point per minute. To calculate the minimum, maximum, average, and 95th percentile, the query needed to flatten the nested array <code>intervals</code>. The following listing shows a simplified version of the JSON data.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;intervals&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;sum&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;bits_per_second&quot;</span>: <span class="number">480749918.45076</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;sum&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;bits_per_second&quot;</span>: <span class="number">408624517.159294</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;sum&quot;</span>: &#123;</span><br><span class="line">        <span class="string">&quot;bits_per_second&quot;</span>: <span class="number">278501469.441085</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  ],</span><br><span class="line">  <span class="string">&quot;instanceType&quot;</span>: <span class="string">&quot;t2.nano&quot;</span>,</span><br><span class="line">  <span class="string">&quot;region&quot;</span>: <span class="string">&quot;us-east-1&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Athena makes use of the <a href="https://prestodb.io/" target="_blank" rel="noopener">presto query engine</a> which allows expanding arrays into a relation with <code>UNNEST</code>.</p><p>The following query …</p><p>… aggregates <code>bits_per_second</code> for each <code>instancetype</code> and <code>region</code>.<br>… expands each item of the nested <code>intervals</code> array into its a relation.<br>… filters by <code>date</code> and the length of the <code>intervals</code> array <code>cardinality(intervals)</code>.</p><figure class="highlight n1ql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> </span><br><span class="line">  (<span class="built_in">min</span>(interval.<span class="built_in">sum</span>.bits_per_second)/<span class="number">1000000000</span>) <span class="keyword">AS</span> <span class="built_in">min</span>,</span><br><span class="line">  (<span class="built_in">max</span>(interval.<span class="built_in">sum</span>.bits_per_second)/<span class="number">1000000000</span>) <span class="keyword">AS</span> <span class="built_in">max</span>,</span><br><span class="line">  (<span class="built_in">avg</span>(interval.<span class="built_in">sum</span>.bits_per_second)/<span class="number">1000000000</span>) <span class="keyword">AS</span> <span class="built_in">avg</span>,</span><br><span class="line">  (approx_percentile(interval.<span class="built_in">sum</span>.bits_per_second, <span class="number">0.95</span>)/<span class="number">1000000000</span>) <span class="keyword">AS</span> p95,</span><br><span class="line">  region, </span><br><span class="line">  instancetype </span><br><span class="line"><span class="keyword">FROM</span> networkbenchmark CROSS <span class="keyword">JOIN</span> <span class="keyword">UNNEST</span>(intervals) <span class="keyword">WITH</span> ORDINALITY <span class="keyword">AS</span> t(interval, counter)</span><br><span class="line"><span class="keyword">WHERE</span> d &gt;= from_iso8601_date(<span class="string">&#x27;2018-04-12&#x27;</span>) <span class="keyword">AND</span> cardinality(intervals) = <span class="number">60</span></span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> region, instancetype </span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> region, instancetype;</span><br></pre></td></tr></table></figure><p>Are you looking for more details on how to query arrays with Athena? Have a look at <a href="https://docs.aws.amazon.com/athena/latest/ug/querying-arrays.html" target="_blank" rel="noopener">Querying Arrays</a>.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Storing the network benchmark results on S3 and analyzing the data with Athena was convenient and flexible. The results of the network benchmark surprised me, more on that at <a href="/ec2-network-performance-cheat-sheet/">EC2 Network Performance Cheat Sheet</a>, <a href="/ec2-network-performance-demystified-m3-m4/">EC2 network performance demystified: m3 and m4</a>, and <a href="/evolution-of-the-ec2-network-performance-m3-m4-m5/">Evolution of the EC2 Network Performance: m3, m4, and m5</a>. I have published the source code of the network benchmark at <a href="https://github.com/widdix/ec2-network-benchmark" target="_blank" rel="noopener">widdix&#x2F;ec2-network-benchmark</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Evolutionary Serverless Architecture: JeffConfg Hamburg 2018 talk</title>
      <link>https://cloudonaut.io/evolutionary-serverless-architecture/</link>
      <description>
        <![CDATA[<p>I want to share my JeffConf Hamburg talk with you: <strong>Evolutionary Serverless Architecture</strong></p>
<p><picture class="img-fluid]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/evolutionary-serverless-architecture/</guid>
      <pubDate>Tue, 03 Apr 2018 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>I want to share my JeffConf Hamburg talk with you: <strong>Evolutionary Serverless Architecture</strong></p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/04/evolutionary-serverless-architecture@730w.webp 730w, /images/2018/04/evolutionary-serverless-architecture@730w2x.webp 1460w, /images/2018/04/evolutionary-serverless-architecture@610w.webp 610w, /images/2018/04/evolutionary-serverless-architecture@610w2x.webp 1220w, /images/2018/04/evolutionary-serverless-architecture@450w.webp 450w, /images/2018/04/evolutionary-serverless-architecture@450w2x.webp 900w, /images/2018/04/evolutionary-serverless-architecture@330w.webp 330w, /images/2018/04/evolutionary-serverless-architecture@330w2x.webp 660w, /images/2018/04/evolutionary-serverless-architecture@545w.webp 545w, /images/2018/04/evolutionary-serverless-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/04/evolutionary-serverless-architecture@730w.png 730w, /images/2018/04/evolutionary-serverless-architecture@730w2x.png 1460w, /images/2018/04/evolutionary-serverless-architecture@610w.png 610w, /images/2018/04/evolutionary-serverless-architecture@610w2x.png 1220w, /images/2018/04/evolutionary-serverless-architecture@450w.png 450w, /images/2018/04/evolutionary-serverless-architecture@450w2x.png 900w, /images/2018/04/evolutionary-serverless-architecture@330w.png 330w, /images/2018/04/evolutionary-serverless-architecture@330w2x.png 660w, /images/2018/04/evolutionary-serverless-architecture@545w.png 545w, /images/2018/04/evolutionary-serverless-architecture@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/04/evolutionary-serverless-architecture.png" alt="Evolutionary Serverless Architecture" title="Evolutionary Serverless Architecture"></picture></p><p>A system architecture often struggles to adapt to new requirements. Serverless architectures can evolve because the small building blocks are easier to replace. I demonstrate the concept with an example: our Slack bot marbot. <a href="https://marbot.io/" target="_blank" rel="noopener">marbot supports your DevOps team to detect and solve incidents on AWS</a>. How and why have we changed the architecture when moving from hackathon to product? How do we ensure that we are on the right track? <a href="https://www.youtube.com/watch?v=KR-cZkLu3ck" target="_blank" rel="noopener">Watch the talk for answers</a>!</p><noscript><div class="alert alert-warning" role="alert">JavaScript is disabled. Please <a href="https://www.youtube.com/watch?v=KR-cZkLu3ck">visit YouTube.com</a> to watch the video.</div></noscript><div class="embed-responsive embed-responsive-16by9"><iframe class="embed-responsive-item lozad" data-src="https://www.youtube-nocookie.com/embed/KR-cZkLu3ck" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div><p><a href="https://x.com/malweene" target="_blank" rel="noopener">Malwine</a> took visual notes (Sketchnotes) of my talk:</p><blockquote class="twitter-tweet" data-lang="de"><p lang="en" dir="ltr">Evolutionary Serverless Architecture: From hackathon to product - by <a href="https://x.com/hellomichibye?ref_src=twsrc%5Etfw">@hellomichibye</a> at <a href="https://x.com/hashtag/JeffConfHamburg?src=hash&amp;ref_src=twsrc%5Etfw">#JeffConfHamburg</a> <a href="https://x.com/hashtag/Sketchnotes?src=hash&amp;ref_src=twsrc%5Etfw">#Sketchnotes</a> <a href="https://t.co/mkm5aXatps">pic.twitter.com/mkm5aXatps</a></p>&mdash; Malwine (@malweene) <a href="https://x.com/malweene/status/964505410395951104?ref_src=twsrc%5Etfw">16. Februar 2018</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><p>Or have a look at the slides in detail:</p><script async class="speakerdeck-embed" data-id="aea597cec0e54bdb9bad4bbc4d737473" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script><h2 id="My-favorite-talks"><a href="#My-favorite-talks" class="headerlink" title="My favorite talks"></a>My favorite talks</h2><p>I enjoyed my time at the conference. My favorite talks are:</p><ul><li><a href="https://youtu.be/Z7Fcz756vMQ" target="_blank" rel="noopener">Conversational AI and Serverless</a></li><li><a href="https://youtu.be/u_sBNMCWpVA" target="_blank" rel="noopener">Real-Time Serverless Back Ends with GraphQL</a></li><li><a href="https://youtu.be/SPsaqiegOP4" target="_blank" rel="noopener">Why the Fuss about Serverless?</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>Serverless pattern: accessing public and private resources</title>
      <link>https://cloudonaut.io/serverless-pattern-accessing-public-and-private-resources/</link>
      <description>
        <![CDATA[<p>Crossing the chasm between the old world - virtual machines isolated within a private network - and the new world - Serverless making use]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/serverless-pattern-accessing-public-and-private-resources/</guid>
      <pubDate>Fri, 23 Mar 2018 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Crossing the chasm between the old world - virtual machines isolated within a private network - and the new world - Serverless making use of publicly accessible APIs only - can be tricky. On the one hand, it is possible to <a href="https://docs.aws.amazon.com/lambda/latest/dg/vpc.html" target="_blank" rel="noopener">configure VPC access for AWS Lambda</a>. On the other hand, doing so comes with limitations taking away all the fun.</p><p>As soon as you are connecting your Lambda function with a VPC, the function is no longer able to access the Internet, even if you are choosing a public subnet with a route to the Internet gateway for your Lambda function (see <a href="https://docs.aws.amazon.com/lambda/latest/dg/vpc.html#vpc-internet" target="_blank" rel="noopener">Internet Access for Lambda Functions</a> to learn more). So you can’t have both: access to resources within your VPC and through the Internet.</p><p>To be able to access your VPC as well as the Internet, you need to spin up a NAT Gateway. Or in some cases, you might get away with a <a href="https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/vpc-endpoints.html" target="_blank" rel="noopener">VPC Endpoint</a>.</p><p>The following example will illustrate the limitation and demonstrate a workaround.</p><h2 id="Example-Query-a-database-and-publish-result-to-CloudWatch"><a href="#Example-Query-a-database-and-publish-result-to-CloudWatch" class="headerlink" title="Example: Query a database and publish result to CloudWatch"></a>Example: Query a database and publish result to CloudWatch</h2><p>Let us assume your application stores the sessions of logged in users in a relational database, and you want to monitor the number of active sessions. So, you query your RDS database instance and publish the result to a custom CloudWatch metric periodically. All in all, a perfect use case for a Serverless application as you only need limited compute capacity for a few seconds every five minutes. The following figure shows the involved components:</p><ul><li><strong>CloudWatch Scheduled Event</strong> is triggering the Lambda function every five minutes</li><li><strong>Lambda Function</strong> sends a query to the database and stores the result in the <strong>Custom CloudWatch Metric</strong></li><li><strong>RDS Instance</strong> the database storing the active sessions</li><li><strong>VPC</strong> both the RDS instance as well as the Lambda function are placed into a private network</li><li><strong>NAT Gateway</strong> needed, so the Lambda function can access the CloudWatch API (public interface)</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/03/serverless-anti-pattern-public-private@730w.webp 730w, /images/2018/03/serverless-anti-pattern-public-private@730w2x.webp 1460w, /images/2018/03/serverless-anti-pattern-public-private@610w.webp 610w, /images/2018/03/serverless-anti-pattern-public-private@610w2x.webp 1220w, /images/2018/03/serverless-anti-pattern-public-private@450w.webp 450w, /images/2018/03/serverless-anti-pattern-public-private@450w2x.webp 900w, /images/2018/03/serverless-anti-pattern-public-private@330w.webp 330w, /images/2018/03/serverless-anti-pattern-public-private@330w2x.webp 660w, /images/2018/03/serverless-anti-pattern-public-private@545w.webp 545w, /images/2018/03/serverless-anti-pattern-public-private@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/03/serverless-anti-pattern-public-private@730w.png 730w, /images/2018/03/serverless-anti-pattern-public-private@730w2x.png 1460w, /images/2018/03/serverless-anti-pattern-public-private@610w.png 610w, /images/2018/03/serverless-anti-pattern-public-private@610w2x.png 1220w, /images/2018/03/serverless-anti-pattern-public-private@450w.png 450w, /images/2018/03/serverless-anti-pattern-public-private@450w2x.png 900w, /images/2018/03/serverless-anti-pattern-public-private@330w.png 330w, /images/2018/03/serverless-anti-pattern-public-private@330w2x.png 660w, /images/2018/03/serverless-anti-pattern-public-private@545w.png 545w, /images/2018/03/serverless-anti-pattern-public-private@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/03/serverless-anti-pattern-public-private.png" alt="Serverless Anti Pattern for accessing public and private resources" title="Serverless Anti Pattern for accessing public and private resources"></picture></p><p>On component is taking away all the Serverless fun. Which one?</p><h2 id="Problem-NAT-Gateway"><a href="#Problem-NAT-Gateway" class="headerlink" title="Problem: NAT Gateway"></a>Problem: NAT Gateway</h2><p>As shown in the following table the NAT Gateway is a significant cost driver.</p><table class="table">  <thead>    <tr>      <th>Service</th>      <th class="text-right">Monthly Costs</th>    </tr>  </thead>  <tbody>    <tr>      <td>Lambda (Free Tier)</td>      <td class="text-right">USD 0.00</td>    </tr>    <tr>      <td>CloudWatch (Custom Metric)</td>      <td class="text-right">USD 0.40</td>    </tr>    <tr class="danger">      <td>NAT Gateway</td>      <td class="text-right">USD 32.40</td>    </tr>  </tbody></table><p>To be fair, that is not a problem if the NAT Gateway is already in place and use by other parts of the infrastructure. Nevertheless, needing to add a NAT Gateway just to be able to store active sessions within a CloudWatch metric is not feasible.</p><p>Any idea how to work around the need of a NAT Gateway?</p><h2 id="Workaround-Public-and-Private-Lambda-Function"><a href="#Workaround-Public-and-Private-Lambda-Function" class="headerlink" title="Workaround: Public and Private Lambda Function"></a>Workaround: Public and Private Lambda Function</h2><p>Crossing the chasm requires extra creativity. The following figure shows the component allowing you to query data a database and publishing the result to CloudWatch.</p><ol><li>The <strong>CloudWatch Scheduled Event</strong> is triggering the Public Lambda function.</li><li>The <strong>Public Lambda</strong> invokes the <strong>Private Lambda</strong> synchronously.</li><li>The <strong>Private Lambda</strong> queries the <strong>RDS Instance’s</strong> database.</li><li>The <strong>Public Lambda</strong> writes the result from the <strong>Private Lambda</strong> to the <strong>Custom CloudWatch Metric</strong>.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/03/serverless-pattern-public-private@730w.webp 730w, /images/2018/03/serverless-pattern-public-private@730w2x.webp 1460w, /images/2018/03/serverless-pattern-public-private@610w.webp 610w, /images/2018/03/serverless-pattern-public-private@610w2x.webp 1220w, /images/2018/03/serverless-pattern-public-private@450w.webp 450w, /images/2018/03/serverless-pattern-public-private@450w2x.webp 900w, /images/2018/03/serverless-pattern-public-private@330w.webp 330w, /images/2018/03/serverless-pattern-public-private@330w2x.webp 660w, /images/2018/03/serverless-pattern-public-private@545w.webp 545w, /images/2018/03/serverless-pattern-public-private@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/03/serverless-pattern-public-private@730w.png 730w, /images/2018/03/serverless-pattern-public-private@730w2x.png 1460w, /images/2018/03/serverless-pattern-public-private@610w.png 610w, /images/2018/03/serverless-pattern-public-private@610w2x.png 1220w, /images/2018/03/serverless-pattern-public-private@450w.png 450w, /images/2018/03/serverless-pattern-public-private@450w2x.png 900w, /images/2018/03/serverless-pattern-public-private@330w.png 330w, /images/2018/03/serverless-pattern-public-private@330w2x.png 660w, /images/2018/03/serverless-pattern-public-private@545w.png 545w, /images/2018/03/serverless-pattern-public-private@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/03/serverless-pattern-public-private.png" alt="Serverless Pattern for accessing public and private resources" title="Serverless Pattern for accessing public and private resources"></picture></p><p>A 100% Serverless solution, no need for a <strong>NAT Gateway</strong> anymore.</p><p>So, what is the limitation of this workaround? The maximum amount of data passed from the <strong>Private Lambda</strong> to the <strong>Public Lambda</strong> is 6 MB.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Whenever a Lambda function needs to access resources inside and outside a VPC, you should think about splitting your tasks into a Lambda function running outside the VPC and a Lambda function running inside the VPC to avoid the need of a NAT Gateway or VPC Endpoint.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Configure your CloudFormation managed infrastructure with Parameter Store and CodePipeline</title>
      <link>https://cloudonaut.io/configure-your-cloudformation-managed-infrastructure-with-parameter-store-and-codepipeline/</link>
      <description>
        <![CDATA[<p>Getting started with CI&#x2F;CD to manage your AWS infrastructure is hard:</p>
<ol>
<li>You have to familiarize yourself with the availab]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <category domain="https://cloudonaut.io/tag/codepipeline/">codepipeline</category>
      <category domain="https://cloudonaut.io/tag/parameter-store/">parameter-store</category>
      <guid isPermaLink="true">https://cloudonaut.io/configure-your-cloudformation-managed-infrastructure-with-parameter-store-and-codepipeline/</guid>
      <pubDate>Tue, 20 Mar 2018 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Getting started with CI&#x2F;CD to manage your AWS infrastructure is hard:</p><ol><li>You have to familiarize yourself with the available technologies.</li><li>You have to create a Proof of Concept to show your team how it works.</li><li>You have to convince your team to stop using the graphical Management Console.</li></ol><p>The last part is usually the hardest. That’s why I was looking for a way to introduce CI&#x2F;CD while still allowing manual changes using the graphical Management Console.</p><blockquote><p>If you use CloudFormation to create resources for you, you should never make manual changes to the resources. Otherwise, you risk to losing the infrastructure during the next update run of CloudFormation.</p></blockquote><h2 id="Manual-changes-through-Parameter-Store"><a href="#Manual-changes-through-Parameter-Store" class="headerlink" title="Manual changes through Parameter Store"></a>Manual changes through Parameter Store</h2><p>AWS Systems Manager (SSM) Parameter Store is a good place to store the configuration of your application. Parameters can be organized like files in a folder like structure. E.g., the parameter <code>/application/stage/instancetype</code> stores the value <code>t2.micro</code>. Parameter Store comes with a nice UI.</p><p>CloudFormation templates can be parametrized. A CloudFormation parameter can lookup the value from Parameter Store when you create or update a stack.</p><p>But how do you trigger a stack update when the value of the parameter in Parameter Store changes?</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/03/parameter-store-cloudformation-codepipeline@730w.webp 730w, /images/2018/03/parameter-store-cloudformation-codepipeline@730w2x.webp 1460w, /images/2018/03/parameter-store-cloudformation-codepipeline@610w.webp 610w, /images/2018/03/parameter-store-cloudformation-codepipeline@610w2x.webp 1220w, /images/2018/03/parameter-store-cloudformation-codepipeline@450w.webp 450w, /images/2018/03/parameter-store-cloudformation-codepipeline@450w2x.webp 900w, /images/2018/03/parameter-store-cloudformation-codepipeline@330w.webp 330w, /images/2018/03/parameter-store-cloudformation-codepipeline@330w2x.webp 660w, /images/2018/03/parameter-store-cloudformation-codepipeline@545w.webp 545w, /images/2018/03/parameter-store-cloudformation-codepipeline@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/03/parameter-store-cloudformation-codepipeline@730w.png 730w, /images/2018/03/parameter-store-cloudformation-codepipeline@730w2x.png 1460w, /images/2018/03/parameter-store-cloudformation-codepipeline@610w.png 610w, /images/2018/03/parameter-store-cloudformation-codepipeline@610w2x.png 1220w, /images/2018/03/parameter-store-cloudformation-codepipeline@450w.png 450w, /images/2018/03/parameter-store-cloudformation-codepipeline@450w2x.png 900w, /images/2018/03/parameter-store-cloudformation-codepipeline@330w.png 330w, /images/2018/03/parameter-store-cloudformation-codepipeline@330w2x.png 660w, /images/2018/03/parameter-store-cloudformation-codepipeline@545w.png 545w, /images/2018/03/parameter-store-cloudformation-codepipeline@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/03/parameter-store-cloudformation-codepipeline.png" alt="Configure your CloudFormation managed infrastructure with Parameter Store and CodePipeline" title="Configure your CloudFormation managed infrastructure with Parameter Store and CodePipeline"></picture></p><p>Luckily, Parameter Store parameter changes are published to CloudWatch Events. You can subscribe to those events and trigger a CodePipeline execution to update the CloudFormation stack. All of this managed by CloudFormation.</p><p>Read on if you want to learn how you can connect the pieces:</p><ol><li>Develop a CloudFormation template using the Parameter Store to start an EC2 instance</li><li>Using CodePipeline to deploy a CloudFormation stack</li><li>Listening to CloudWatch Events to trigger the pipeline on parameter changes</li></ol><h2 id="Simple-CloudFormation-template-using-the-Parameter-Store"><a href="#Simple-CloudFormation-template-using-the-Parameter-Store" class="headerlink" title="Simple CloudFormation template using the Parameter Store"></a>Simple CloudFormation template using the Parameter Store</h2><p>First, you need the CloudFormation template that describes the EC2 instance. The instance type should be fetched from the <code>/application/stage/instancetype</code> parameter.</p><p>CloudFormation integrates with Parameter Store using parameters as well. Don’t get confused. CloudFormation parameters and Parameter Store parameters are two different things. You use a CloudFormation parameter of type <code>AWS::SSM::Parameter::Value&lt;String&gt;</code> and set the value to the name of the Parameter Store parameter (e.g., <code>/application/stage/instancetype</code>). CloudFormation will then, on each create or update of the stack, ask Parameter Store for the current value. A second CloudFormation parameter <code>ParentVPCStack</code> is used to reference a CloudFormation that contains the VPC.</p><figure class="highlight yaml"><figcaption><span>part 1 of infrastructure.yaml</span><a href="https://github.com/widdix/parameter-store-cloudformation-codepipeline/blob/master/infrastructure.yaml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&#x27;Infrastructure&#x27;</span></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">InstanceType:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;Name of Parameter Store parameter to define the instance type.&#x27;</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::SSM::Parameter::Value&lt;String&gt;&#x27;</span></span><br><span class="line">    <span class="attr">Default:</span> <span class="string">&#x27;/application/stage/instancetype&#x27;</span></span><br><span class="line">  <span class="attr">ParentVPCStack:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;Stack name of parent VPC stack based on vpc/vpc-*azs.yaml template.&#x27;</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br></pre></td></tr></table></figure><p>The CloudFormation parameter can then be used as any other parameter. <code>!Ref InstanceType</code> returns the value of the parameter.</p><figure class="highlight yaml"><figcaption><span>part 2 of infrastructure.yaml</span><a href="https://github.com/widdix/parameter-store-cloudformation-codepipeline/blob/master/infrastructure.yaml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">SecurityGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::SecurityGroup&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">GroupDescription:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line">      <span class="attr">SecurityGroupIngress:</span> <span class="comment"># [...]</span></span><br><span class="line">      <span class="attr">VpcId:</span> <span class="comment"># [...]</span></span><br><span class="line">  <span class="attr">VirtualMachine:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::Instance&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">ImageId:</span> <span class="string">&#x27;ami-97785bed&#x27;</span></span><br><span class="line">      <span class="attr">InstanceType:</span> <span class="type">!Ref</span> <span class="string">InstanceType</span></span><br><span class="line">      <span class="attr">SecurityGroupIds:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">SecurityGroup</span></span><br><span class="line">      <span class="attr">SubnetId:</span> <span class="comment"># [...]</span></span><br></pre></td></tr></table></figure><p>Done is the CloudFormation template spinning up a single EC2 instance with the instance type coming from Parameter Store. I removed some parts (<code># [...]</code>) to focus on the important parts. You can download the full <code>infrastructure.yaml</code> template on <a href="https://github.com/widdix/parameter-store-cloudformation-codepipeline/blob/master/infrastructure.yaml" target="_blank" rel="noopener">GitHub</a>.</p><h2 id="Simple-CodePipeline-to-deploy-a-CloudFormation-stack"><a href="#Simple-CodePipeline-to-deploy-a-CloudFormation-stack" class="headerlink" title="Simple CodePipeline to deploy a CloudFormation stack"></a>Simple CodePipeline to deploy a CloudFormation stack</h2><p>Now it’s time to take care of the deployment pipeline. You need an S3 bucket (<code>ArtifactsBucket</code>) to store the artifacts that are moved through the pipeline. I also added the CodeCommit repository (<code>CodeRepository</code>) to the template to store the project’s source code. Finally, CodePipeline and CloudFormation need permissions (<code>PipelineRole</code>) to invoke the AWS API on your behalf to create the resources described in the CloudFormation templates. Since you can create any resource with CloudFormation, you most likely have to grant full permissions to create a stack. In this example, it should be possible to restrict on certain EC2 actions, but you don’t necessarily know which API calls CloudFormation performs for you.</p><figure class="highlight yaml"><figcaption><span>part 1 of pipeline.yaml</span><a href="https://github.com/widdix/parameter-store-cloudformation-codepipeline/blob/master/pipeline.yaml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&#x27;Pipeline&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">CodeRepository:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodeCommit::Repository&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">RepositoryName:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line">  <span class="attr">ArtifactsBucket:</span></span><br><span class="line">    <span class="attr">DeletionPolicy:</span> <span class="string">Retain</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::S3::Bucket&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">PipelineRole:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">Service:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;cloudformation.amazonaws.com&#x27;</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;codepipeline.amazonaws.com&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">      <span class="attr">ManagedPolicyArns:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;arn:aws:iam::aws:policy/AdministratorAccess&#x27;</span></span><br></pre></td></tr></table></figure><p>The pipeline itself can now be described. The following figure shows how the pipeline looks like.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/03/pipeline@730w.webp 730w, /images/2018/03/pipeline@730w2x.webp 1460w, /images/2018/03/pipeline@610w.webp 610w, /images/2018/03/pipeline@610w2x.webp 1220w, /images/2018/03/pipeline@450w.webp 450w, /images/2018/03/pipeline@450w2x.webp 900w, /images/2018/03/pipeline@330w.webp 330w, /images/2018/03/pipeline@330w2x.webp 660w, /images/2018/03/pipeline@545w.webp 545w, /images/2018/03/pipeline@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/03/pipeline@730w.png 730w, /images/2018/03/pipeline@730w2x.png 1460w, /images/2018/03/pipeline@610w.png 610w, /images/2018/03/pipeline@610w2x.png 1220w, /images/2018/03/pipeline@450w.png 450w, /images/2018/03/pipeline@450w2x.png 900w, /images/2018/03/pipeline@330w.png 330w, /images/2018/03/pipeline@330w2x.png 660w, /images/2018/03/pipeline@545w.png 545w, /images/2018/03/pipeline@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/03/pipeline.png" alt="Pipeline" title="Pipeline"></picture></p><p>In the first stage, the source code is fetched from the CodeCommit repository. After that, a VPC stack is created. The EC2 instance is later launched into the VPC that is created in the second stage. I reused the <code>vpc-2azs.yaml</code> template from our <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">Free Templates for AWS CloudFormation collection</a> to describe the VPC. That’s one of the big advantages of CloudFormation. Once you have a template, you can reuse it as often as you want.</p><figure class="highlight yaml"><figcaption><span>part 2 of pipeline.yaml</span><a href="https://github.com/widdix/parameter-store-cloudformation-codepipeline/blob/master/pipeline.yaml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">Pipeline:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodePipeline::Pipeline&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">ArtifactStore:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">S3</span></span><br><span class="line">      <span class="attr">Location:</span> <span class="type">!Ref</span> <span class="string">ArtifactsBucket</span></span><br><span class="line">    <span class="attr">Name:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line">    <span class="attr">RestartExecutionOnUpdate:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;PipelineRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">Stages:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">      <span class="attr">Actions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">FetchSource</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Source</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeCommit</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">RepositoryName:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CodeRepository.Name&#x27;</span></span><br><span class="line">          <span class="attr">BranchName:</span> <span class="string">master</span></span><br><span class="line">          <span class="attr">PollForSourceChanges:</span> <span class="literal">false</span></span><br><span class="line">        <span class="attr">OutputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">VPC</span></span><br><span class="line">      <span class="attr">Actions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Deploy</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Deploy</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CloudFormation</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ActionMode:</span> <span class="string">CREATE_UPDATE</span></span><br><span class="line">          <span class="attr">Capabilities:</span> <span class="string">CAPABILITY_IAM</span></span><br><span class="line">          <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;PipelineRole.Arn&#x27;</span></span><br><span class="line">          <span class="attr">StackName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-vpc&#x27;</span></span><br><span class="line">          <span class="attr">TemplatePath:</span> <span class="string">&#x27;Source::vpc-2azs.yaml&#x27;</span></span><br><span class="line">          <span class="attr">OutputFileName:</span> <span class="string">&#x27;output.json&#x27;</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="attr">OutputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">VPC</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br><span class="line"><span class="comment"># template continues in a moment</span></span><br></pre></td></tr></table></figure><p>There is one interesting concept that I need to explain. In the VPC stage, the VPC stack is deployed. The CloudFormation stack outputs are stored in a file called <code>output.json</code> and this file is part of the <code>VPC</code> artifact. You can use the <code>VPC</code> artifact later to get access to the stack outputs from the VPC stack.<br>In the third stage, the template containing the EC2 instance is used to create the infrastructure stack. You now pass two input artifacts to CloudFormation deployment, the <code>Source</code> containing the source code from CodeCommit, and the <code>VPC</code> containing the VPC stack outputs.</p><figure class="highlight yaml"><figcaption><span>part 3 of pipeline.yaml</span><a href="https://github.com/widdix/parameter-store-cloudformation-codepipeline/blob/master/pipeline.yaml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># continued template</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Production</span></span><br><span class="line">      <span class="attr">Actions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">DeployInfrastructure</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span> <span class="comment"># [...]</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="comment"># [...]</span></span><br><span class="line">          <span class="attr">TemplatePath:</span> <span class="string">&#x27;Source::infrastructure.yaml&#x27;</span></span><br><span class="line">          <span class="attr">TemplateConfiguration:</span> <span class="string">&#x27;Source::infrastructure.json&#x27;</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">VPC</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br></pre></td></tr></table></figure><p>The <code>infrastructure.json</code> file wires the value from <code>output.json</code> together with the parameter of the <code>infrastructure.yaml</code> template.</p><figure class="highlight json"><figcaption><span>infrastructure.json</span><a href="https://github.com/widdix/parameter-store-cloudformation-codepipeline/blob/master/infrastructure.json">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Parameters&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;ParentVPCStack&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="attr">&quot;Fn::GetParam&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;VPC&quot;</span><span class="punctuation">,</span> <span class="string">&quot;output.json&quot;</span><span class="punctuation">,</span> <span class="string">&quot;StackName&quot;</span><span class="punctuation">]</span><span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>The pipeline is mostly done. One last thing is missing.</p><h2 id="Listening-to-CloudWatch-Events"><a href="#Listening-to-CloudWatch-Events" class="headerlink" title="Listening to CloudWatch Events"></a>Listening to CloudWatch Events</h2><p>Last but not least, you define a CloudWatch Events Rule to trigger the pipeline whenever the Parameter Store parameter changes. And as always, you need to give AWS (to be more precise, CloudWatch Events) permissions to execute the pipeline.</p><figure class="highlight yaml"><figcaption><span>part 4 of pipeline.yaml</span><a href="https://github.com/widdix/parameter-store-cloudformation-codepipeline/blob/master/pipeline.yaml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">PipelineTriggerRole:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">      <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">      <span class="attr">Statement:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">        <span class="attr">Principal:</span></span><br><span class="line">          <span class="attr">Service:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;events.amazonaws.com&#x27;</span></span><br><span class="line">        <span class="attr">Action:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    <span class="attr">Policies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">&#x27;codepipeline&#x27;</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;codepipeline:StartPipelineExecution&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:codepipeline:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:$&#123;Pipeline&#125;&#x27;</span></span><br><span class="line"><span class="attr">ParameterStorePipelineTriggerRule:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::Events::Rule&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">EventPattern:</span></span><br><span class="line">      <span class="attr">source:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;aws.ssm&#x27;</span></span><br><span class="line">      <span class="attr">&#x27;detail-type&#x27;:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;Parameter Store Change&#x27;</span></span><br><span class="line">      <span class="attr">resources:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:ssm:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:parameter/application/stage/instancetype&#x27;</span></span><br><span class="line">    <span class="attr">State:</span> <span class="string">ENABLED</span></span><br><span class="line">    <span class="attr">Targets:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Arn:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:codepipeline:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:$&#123;Pipeline&#125;&#x27;</span></span><br><span class="line">      <span class="attr">Id:</span> <span class="string">pipeline</span></span><br><span class="line">      <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;PipelineTriggerRole.Arn&#x27;</span></span><br></pre></td></tr></table></figure><p>There is one more thing to talk about when talking about CloudWatch Events. CodeCommit does also publish an event if the repository changes. The following Event Rule executed the pipeline whenever the source code changes in the repository.</p><figure class="highlight yaml"><figcaption><span>part 5 of pipeline.yaml</span><a href="https://github.com/widdix/parameter-store-cloudformation-codepipeline/blob/master/pipeline.yaml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">CodeCommitPipelineTriggerRule:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::Events::Rule&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">EventPattern:</span></span><br><span class="line">      <span class="attr">source:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;aws.codecommit&#x27;</span></span><br><span class="line">      <span class="attr">&#x27;detail-type&#x27;:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;CodeCommit Repository State Change&#x27;</span></span><br><span class="line">      <span class="attr">resources:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CodeRepository.Arn&#x27;</span></span><br><span class="line">      <span class="attr">detail:</span></span><br><span class="line">        <span class="attr">referenceType:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">branch</span></span><br><span class="line">        <span class="attr">referenceName:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">master</span></span><br><span class="line">    <span class="attr">State:</span> <span class="string">ENABLED</span></span><br><span class="line">    <span class="attr">Targets:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Arn:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:codepipeline:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:$&#123;Pipeline&#125;&#x27;</span></span><br><span class="line">      <span class="attr">Id:</span> <span class="string">pipeline</span></span><br><span class="line">      <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;PipelineTriggerRole.Arn&#x27;</span></span><br></pre></td></tr></table></figure><p>You can download the full <code>pipeline.yaml</code> template on <a href="https://github.com/widdix/parameter-store-cloudformation-codepipeline/blob/master/pipeline.yaml" target="_blank" rel="noopener">GitHub</a>.</p><h2 id="Setup-instructions"><a href="#Setup-instructions" class="headerlink" title="Setup instructions"></a>Setup instructions</h2><blockquote><p>Have you <a href="https://docs.aws.amazon.com/cli/latest/userguide/installing.html" target="_blank" rel="noopener">installed</a> and <a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html" target="_blank" rel="noopener">configured</a> the AWS CLI?</p></blockquote><ol><li>Clone the example repository <code>git clone https://github.com/widdix/parameter-store-cloudformation-codepipeline.git</code> or <a href="https://github.com/widdix/parameter-store-cloudformation-codepipeline/archive/master.zip" target="_blank" rel="noopener">download the ZIP file</a></li><li><code>cd parameter-store-cloudformation-codepipeline/</code></li><li>Create parameter in Parameter Store: <code>aws ssm put-parameter --name &#39;/application/stage/instancetype&#39; --value &#39;t2.micro&#39; --type String</code></li><li>Create pipeline stack with CloudFormation: <code>aws cloudformation create-stack --stack-name cloudonaut --template-body file://pipeline.yaml --capabilities CAPABILITY_IAM</code></li><li>Wait until CloudFormation stack is created: <code>aws cloudformation wait stack-create-complete --stack-name cloudonaut</code></li><li>Push files to the CodeCommit repository created by the pipeline stack (I don’t use <code>git push</code> here to skip the git configuration):</li><li><code>COMMIT_ID=&quot;$(aws codecommit put-file --repository-name cloudonaut --branch-name master --file-content file://infrastructure.yaml --file-path infrastructure.yaml --query commitId --output text)&quot;</code></li><li><code>COMMIT_ID=&quot;$(aws codecommit put-file --repository-name cloudonaut --branch-name master --parent-commit-id $COMMIT_ID --file-content file://infrastructure.json --file-path infrastructure.json --query commitId --output text)&quot;</code></li><li><code>COMMIT_ID=&quot;$(aws codecommit put-file --repository-name cloudonaut --branch-name master --parent-commit-id $COMMIT_ID --file-content file://vpc-2azs.yaml --file-path vpc-2azs.yaml --query commitId --output text)&quot;</code></li><li>Wait until the first pipeline run is finished: <code>open &#39;https://console.aws.amazon.com/codepipeline/home#/view/cloudonaut&#39;</code></li><li>Visit the website exposed by the EC2 instance: <code>open &quot;http://$(aws cloudformation describe-stacks --stack-name cloudonaut-infrastructure --query &quot;Stacks[0].Outputs[0].OutputValue&quot; --output text)&quot;</code></li><li>Update the parameter value: <code>aws ssm put-parameter --name &#39;/application/stage/instancetype&#39; --value &#39;t2.nano&#39; --type String --overwrite</code> (<code>t2.nano</code> is outside th Free Tier, expect charges of a few cents)</li><li>Wait until the second pipeline run is finished: <code>open &#39;https://console.aws.amazon.com/codepipeline/home#/view/cloudonaut&#39;</code></li><li>Visit the website exposed by the EC2 instance: <code>open &quot;http://$(aws cloudformation describe-stacks --stack-name cloudonaut-infrastructure --query &quot;Stacks[0].Outputs[0].OutputValue&quot; --output text)&quot;</code></li></ol><h2 id="Clean-up-instructions"><a href="#Clean-up-instructions" class="headerlink" title="Clean up instructions"></a>Clean up instructions</h2><ol><li>Remove CloudFormation stacks</li><li><code>aws cloudformation delete-stack --stack-name cloudonaut-infrastructure</code></li><li><code>aws cloudformation wait stack-delete-complete --stack-name cloudonaut-infrastructure</code></li><li><code>aws cloudformation delete-stack --stack-name cloudonaut-vpc</code></li><li><code>aws cloudformation wait stack-delete-complete --stack-name cloudonaut-vpc</code></li><li><code>aws cloudformation delete-stack --stack-name cloudonaut</code></li><li><code>aws cloudformation wait stack-delete-complete --stack-name cloudonaut</code></li><li>Remove S3 bucket prefixed with <code>cloudonaut-artifactsbucket-</code> including all files: <code>open &quot;https://s3.console.aws.amazon.com/s3/home&quot;</code></li><li>Remove Parameter Store parameter: <code>aws ssm delete-parameter --name &#39;/application/stage/instancetype&#39;</code></li></ol><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>I like to combine the ease of Parameter Store with the benefits of CI&#x2F;CD. Parameter Store can be turned into a graphical user interface to configure your infrastructure. This is very handy if you work in a team where not everyone if familiar with the concepts of CI&#x2F;CD. Introducing Parameter Store to your team simplifies things. You can use a CI&#x2F;CD approach to deploy infrastructure while the team can still use a graphical user interface to control some parameters without bypassing the CI&#x2F;CD pipeline. A few examples of parameters that I used in the past:</p><ul><li>The size of an auto-scaling group allows you to manually start or shutdown a fleet of EC2 instances through the pipeline</li><li>The storage of your RDS database instance</li><li>The number of nodes in an Elasticsearch cluster</li><li>The instance type of EC2 instances managed by an auto-scaling group</li></ul><p>By the way, parameter Store keeps a record of all changes to a parameter which is handy if you need to record changes to your infrastructure.</p><blockquote><p>Unfortunately, CloudFormation does not support encrypted values from Parameter Store which would be awesome to manage secrets such as database passwords.</p></blockquote>]]>
      </content:encoded>
    </item>
    <item>
      <title>Burst credits of t2 EC2 instances need monitoring</title>
      <link>https://cloudonaut.io/burst-credits-of-t2-ec2-instances-need-monitoring/</link>
      <description>
        <![CDATA[<p>EC2 is one of the fundamental services on AWS. If you are not 100% Serverless, your application health depends on the health of your EC2]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <guid isPermaLink="true">https://cloudonaut.io/burst-credits-of-t2-ec2-instances-need-monitoring/</guid>
      <pubDate>Tue, 13 Mar 2018 20:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>EC2 is one of the fundamental services on AWS. If you are not 100% Serverless, your application health depends on the health of your EC2 instances. When I do AWS architecture reviews for our clients, I check that CPU burst capacity is monitored for EC2 instances of type t2. <strong>A widespread mistake is that credits of burstable EC2 instances (t2 family) are not monitored.</strong> If your <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-credits-baseline-concepts.html" target="_blank" rel="noopener">burstable EC2 instance</a> runs out of credits, the <strong>performance drops by 70-95%</strong>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/03/t2-credit-shortage@730w.webp 730w, /images/2018/03/t2-credit-shortage@730w2x.webp 1460w, /images/2018/03/t2-credit-shortage@610w.webp 610w, /images/2018/03/t2-credit-shortage@610w2x.webp 1220w, /images/2018/03/t2-credit-shortage@450w.webp 450w, /images/2018/03/t2-credit-shortage@450w2x.webp 900w, /images/2018/03/t2-credit-shortage@330w.webp 330w, /images/2018/03/t2-credit-shortage@330w2x.webp 660w, /images/2018/03/t2-credit-shortage@545w.webp 545w, /images/2018/03/t2-credit-shortage@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/03/t2-credit-shortage@730w.png 730w, /images/2018/03/t2-credit-shortage@730w2x.png 1460w, /images/2018/03/t2-credit-shortage@610w.png 610w, /images/2018/03/t2-credit-shortage@610w2x.png 1220w, /images/2018/03/t2-credit-shortage@450w.png 450w, /images/2018/03/t2-credit-shortage@450w2x.png 900w, /images/2018/03/t2-credit-shortage@330w.png 330w, /images/2018/03/t2-credit-shortage@330w2x.png 660w, /images/2018/03/t2-credit-shortage@545w.png 545w, /images/2018/03/t2-credit-shortage@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/03/t2-credit-shortage.png" alt="t2 performance can drop by 95%! Are you prepared?" title="t2 performance can drop by 95%! Are you prepared?"></picture></p><h2 id="Why-are-t2-instances-special"><a href="#Why-are-t2-instances-special" class="headerlink" title="Why are t2 instances special?"></a>Why are t2 instances special?</h2><p>Let’s look at the <code>t2.large</code> in more detail. Your baseline performance is 0.6 vCPU (70% performance drop) while you can burst up to 2 vCPUs as long as you have credits. But the baseline can be much worse. Let’s look at the <code>t2.nano</code>, the cheapest instance type. Your baseline is 0.05 vCPU (95% performance drop) while you can burst up to 1 vCPU. The following table shows the information for all t2 instance types.</p><table class="table table-striped table-responsive"><thead><tr><th>instance type</th><th>performance drop</th><th>baseline vCPUs</th><th>maximum vCPUs</th></tr></thead><tbody><tr><td><code>t2.nano</code></td><td>95%</td><td>0.05</td><td>1</td></tr><tr><td><code>t2.micro</code></td><td>90%</td><td>0.10</td><td>1</td></tr><tr><td><code>t2.small</code></td><td>80%</td><td>0.20</td><td>1</td></tr><tr><td><code>t2.medium</code></td><td>80%</td><td>0.40</td><td>2</td></tr><tr><td><code>t2.large</code></td><td>70%</td><td>0.60</td><td>2</td></tr><tr><td><code>t2.xlarge</code></td><td>77.5%</td><td>0.90</td><td>4</td></tr><tr><td><code>t2.2xlarge</code></td><td>83.1%</td><td>1.35</td><td>8</td></tr></tbody></table><h2 id="Why-does-this-matter"><a href="#Why-does-this-matter" class="headerlink" title="Why does this matter?"></a>Why does this matter?</h2><p>Your t2 instance only consumes credits if your application requires more than the baseline performance. This means that you can only run out of credits if your instance is bursting. In other words, credits are only consumed if your application needs performance.</p><p>Now imagine what happens if the t2 instance is no longer able to burst and drops to the baseline from one second to the other. This is precisely what happens if you run out of burst credits. In other words, performance drops by up to 95% from one second to the other. This will have a significant impact on your application. <strong>Most likely, the application will not be responsive anymore.</strong></p><h2 id="How-can-you-avoid-the-performance-drop"><a href="#How-can-you-avoid-the-performance-drop" class="headerlink" title="How can you avoid the performance drop?"></a>How can you avoid the performance drop?</h2><p>First of all, your t2 instances are earning credits while not bursting. For Example, a <code>t2.large</code> earns 36 credits per hour. One CPU credit is equal to one vCPU running at 100% utilization for one minute. As long as you don’t run out of credits, performance will not drop to the baseline. But how do you know? First, each t2 EC2 instance publishes the <code>CPUCreditBalance</code> metric to CloudWatch. The metric reports the remaining CPU credits available. Second, you can define a CloudWatch Alarm that continuously monitors the <code>CPUCreditBalance</code> metric. As soon as you run out of credits, an alert is triggered and you can react. E.g., increase capacity or reschedule work.</p><h3 id="CloudWatch-Alarms-and-marbot"><a href="#CloudWatch-Alarms-and-marbot" class="headerlink" title="CloudWatch Alarms and marbot"></a>CloudWatch Alarms and marbot</h3><p>We found that emails are not a good way to handle alerts. In a team, multiple people are responsible. If you send an email to a group email address:</p><ol><li>Your team has no idea if someone already started to work on solving the issue.</li><li>You disturb the whole team for each alert.</li><li>It’s easy to ignore an email.</li><li>You have no statistics about how many alerts are generated. Too many alerts are an indication that your team is no longer able to handle them.</li><li>No help to investigate the issue is available, like links to the AWS Management Console.</li></ol><p>To solve the problem, we built marbot: a <a href="https://marbot.io/" target="_blank" rel="noopener">Slack bot supporting your DevOps team to detect and solve incidents on AWS</a>.</p><p><img class="img-fluid" src="/images/2018/01/marbot-alert.gif" alt="marbot forwards alerts to Slack" title="marbot forwards alerts to Slack"></p><p>marbot sends alerts to a single user from the Slack channel via a direct message. If the user doesn’t acknowledge the alert within 5 minutes, marbot will escalate to the next level. Escalations minimize distraction while keeping response time low. <a href="https://marbot.io/" target="_blank" rel="noopener">Try marbot for free now</a>.</p><h3 id="CloudFormation-template"><a href="#CloudFormation-template" class="headerlink" title="CloudFormation template"></a>CloudFormation template</h3><p>We developed a CloudFormation template to monitor an EC2 instance in any region (includes <code>CPUCreditBalance</code> metric monitoring). The template integrates with marbot, but you can modify it to send out emails. The template is available on <a href="https://github.com/marbot-io/monitoring-jump-start/blob/master/marbot-ec2-instance.yml" target="_blank" rel="noopener">GitHub</a> for free. We also offer a <a href="https://github.com/marbot-io/monitoring-jump-start/blob/master/marbot-auto-scaling-group.yml" target="_blank" rel="noopener">version</a> that works with a fleet of EC2 instances managed by an Auto Scaling Group.</p><p>If you have already installed marbot, you can also ask marbot to monitor your EC2 instance or <a href="https://marbot.io/blog/monitoring-jump-start-amazon-ec2.html" target="_blank" rel="noopener">read more detailed setup instructions</a>. Otherwise: <a href="https://marbot.io/" target="_blank" rel="noopener">Try marbot for free now</a>.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>t2 EC2 instances are cheap, but they increase complexity. You have to monitor burst credits to ensure that you will not suffer from a 95% performance drop. We usually advise not to use the t2 family in production systems that serve user traffic. But we like t2 instances for test environments and internal applications like Jenkins. If you want to stay with the low-cost t2 family, you can enable <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/t2-unlimited.html" target="_blank" rel="noopener">T2 Unlimited</a> which provides a way to continue to burst without credits but with an additional charge.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>New CloudFormation Templates - Store your state</title>
      <link>https://cloudonaut.io/new-cloudformation-templates-store-your-state/</link>
      <description>
        <![CDATA[<p>We help numerous clients to automate AWS with the help of CloudFormation. As you can imagine, we can reuse CloudFormation templates acros]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/new-cloudformation-templates-store-your-state/</guid>
      <pubDate>Wed, 07 Mar 2018 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>We help numerous clients to automate AWS with the help of CloudFormation. As you can imagine, we can reuse CloudFormation templates across clients. The template library comes with several advantages:</p><ul><li>Higher quality: we continuously improve the templates while they are used in production</li><li>Increased speed: we can set up complex AWS infrastructures within hours</li><li>Lower costs: we don’t have to start from scratch</li></ul><p>We open-sourced parts of our template library in 2015: <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">https://github.com/widdix/aws-cf-templates</a>. Since then, we continuously added new templates and received contributions from the community.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/03/library@730w.webp 730w, /images/2018/03/library@730w2x.webp 1460w, /images/2018/03/library@610w.webp 610w, /images/2018/03/library@610w2x.webp 1220w, /images/2018/03/library@450w.webp 450w, /images/2018/03/library@450w2x.webp 900w, /images/2018/03/library@330w.webp 330w, /images/2018/03/library@330w2x.webp 660w, /images/2018/03/library@545w.webp 545w, /images/2018/03/library@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/03/library@730w.jpeg 730w, /images/2018/03/library@730w2x.jpeg 1460w, /images/2018/03/library@610w.jpeg 610w, /images/2018/03/library@610w2x.jpeg 1220w, /images/2018/03/library@450w.jpeg 450w, /images/2018/03/library@450w2x.jpeg 900w, /images/2018/03/library@330w.jpeg 330w, /images/2018/03/library@330w2x.jpeg 660w, /images/2018/03/library@545w.jpeg 545w, /images/2018/03/library@545w2x.jpeg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/03/library.jpeg" alt="CloudFormation template library" title="CloudFormation template library"></picture></p><p>Lately, I realized that a valuable category of templates was not part of the open-source library: <a href="https://templates.cloudonaut.io/en/stable/state/" target="_blank" rel="noopener">Templates to store your state</a>. Today, we released six new production-ready CloudFormation templates to store your state:</p><ul><li>RDS Aurora cluster</li><li>RDS Postgres</li><li>ElastiCache memcached</li><li>Elasticsearch cluster</li><li>S3 bucket</li><li>DynamoDB table</li></ul><p>All templates are production-ready. If no other limitations are documented, they are:</p><ul><li>Highly available: <strong>no single point of failure</strong></li><li>Scalable: <strong>increase or decrease the capacity based on utilization</strong></li><li>Frictionless deployment: deliver new versions of your application automatically without downtime</li><li>Secure: using the latest operating systems and software components, follow the least privilege principle in all areas, <strong>backups enabled</strong></li><li>Operations friendly: provide tools for logging, <strong>monitoring and alerting</strong> to recognize and debug problems</li></ul><p>The development of the templates was sponsored by <a href="https://github.com/ngault" target="_blank" rel="noopener">https://github.com/ngault</a>. If you are also missing templates and would like to sponsor development <a href="mailto:&#x68;&#101;&#x6c;&#x6c;&#x6f;&#64;&#99;&#108;&#111;&#117;&#100;&#x6f;&#110;&#97;&#117;&#116;&#46;&#105;&#x6f;">get in touch with us</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Tweaking RDS database performance and ElastiCache</title>
      <link>https://cloudonaut.io/tweaking-rds-performance-and-elasticache/</link>
      <description>
        <![CDATA[<h2 id="Tweaking-database-performance"><a href="#Tweaking-database-performance" class="headerlink" title="Tweaking database performance"></a]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/rds/">rds</category>
      <category domain="https://cloudonaut.io/tag/elasticache/">elasticache</category>
      <guid isPermaLink="true">https://cloudonaut.io/tweaking-rds-performance-and-elasticache/</guid>
      <pubDate>Tue, 06 Mar 2018 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h2 id="Tweaking-database-performance"><a href="#Tweaking-database-performance" class="headerlink" title="Tweaking database performance"></a>Tweaking database performance</h2><p>An RDS database, or a SQL database in general, can only be scaled vertically. To scale a database vertically means to increase the resources of your database but you still have a single database. If the performance of your database becomes insufficient, you must increase the performance of the underlying hardware:</p><ul><li>Faster CPU</li><li>More memory</li><li>Faster storage</li></ul><p>Keep in mind that you can’t increase resources without limits. One of the largest RDS database instance types comes with 32 cores and 244 GiB memory. In comparison, an object store like S3 or a NoSQL-database like DynamoDB can be scaled horizontally without limits.</p><blockquote><p>This article, excerpted from chapter 11 and 12 of <a href="http://bit.ly/amazon-web-services-in-action-2nd-edition" target="_blank" rel="noopener">Amazon Web Services in Action, Second Edition</a>.</p><p align="center"><img src="/images/2017/09/aws-in-action-2nd-meap.png" alt="Amazon Web Services in Action, Second Edition" style="max-width: 300px"></p>Save 37% off [Amazon Web Services in Action, Second Edition](http://bit.ly/amazon-web-services-in-action-2nd-edition) with code `fccwittig` at [manning.com](http://bit.ly/amazon-web-services-in-action-2nd-edition).</blockquote><h3 id="Increasing-database-resources"><a href="#Increasing-database-resources" class="headerlink" title="Increasing database resources"></a>Increasing database resources</h3><p>When you start an RDS database, you choose an instance type. An instance type defines the computing power and memory of a virtual machine (like when you start an EC2 instance). Choosing a bigger instance type increases computing power and memory for RDS databases.<br>You can change the instance type with the help of the CloudFormation template, the CLI, the Management Console, or AWS SDKs. You may want to increase the instance type if the performance isn’t good enough for you. The following listing shows a CloudFormation snippet to create a MySQL database of type <code>db.m3.large</code> with 2 virtual cores and 7.5 GB memory.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Database:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::RDS::DBInstance&#x27;</span></span><br><span class="line">  <span class="attr">DeletionPolicy:</span> <span class="string">Delete</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AllocatedStorage:</span> <span class="number">5</span></span><br><span class="line">    <span class="attr">BackupRetentionPeriod:</span> <span class="number">3</span></span><br><span class="line">    <span class="attr">PreferredBackupWindow:</span> <span class="string">&#x27;05:00-06:00&#x27;</span></span><br><span class="line">    <span class="attr">DBInstanceClass:</span> <span class="string">&#x27;db.m3.large&#x27;</span></span><br><span class="line">    <span class="attr">DBName:</span> <span class="string">wordpress</span></span><br><span class="line">    <span class="attr">Engine:</span> <span class="string">MySQL</span></span><br><span class="line">    <span class="attr">MasterUsername:</span> <span class="string">wordpress</span></span><br><span class="line">    <span class="attr">MasterUserPassword:</span> <span class="string">wordpress</span></span><br><span class="line">    <span class="attr">VPCSecurityGroups:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Sub</span> <span class="string">$&#123;DatabaseSecurityGroup.GroupId&#125;</span></span><br><span class="line">    <span class="attr">DBSubnetGroupName:</span> <span class="type">!Ref</span> <span class="string">DBSubnetGroup</span></span><br><span class="line">    <span class="attr">MultiAZ:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>Because a database has to read and write data to a disk, I&#x2F;O performance is important for the database’s overall performance. RDS offers three different types of storage, as you may already know from the block storage service EBS:</p><ol><li>General purpose (SSD)</li><li>Provisioned IOPS (SSD)</li><li>Magnetic</li></ol><p>You should choose general purpose (SSD) or even provisioned IOPS (SSD) storage for production workloads. The options are exactly the same as for the block storage service EBS you can use for virtual machines. If you need to guarantee a high level of read or write throughput, you should use the provisioned IOPS (SSD) option. The general purpose (SSD) option offers moderate baseline performance with the ability to burst. The throughput for general purpose (SSD) depends on the initialized storage size. Magnetic storage is an option if you need to store data at a low cost or if you don’t need to access it in a predictable, performant way. The next listing shows how to enable general purpose (SSD) storage with the help of a CloudFormation template.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Database:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::RDS::DBInstance&#x27;</span></span><br><span class="line">  <span class="attr">DeletionPolicy:</span> <span class="string">Delete</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AllocatedStorage:</span> <span class="number">5</span></span><br><span class="line">    <span class="attr">BackupRetentionPeriod:</span> <span class="number">3</span></span><br><span class="line">    <span class="attr">PreferredBackupWindow:</span> <span class="string">&#x27;05:00-06:00&#x27;</span></span><br><span class="line">    <span class="attr">DBInstanceClass:</span> <span class="string">&#x27;db.m3.large&#x27;</span></span><br><span class="line">    <span class="attr">DBName:</span> <span class="string">wordpress</span></span><br><span class="line">    <span class="attr">Engine:</span> <span class="string">MySQL</span></span><br><span class="line">    <span class="attr">MasterUsername:</span> <span class="string">wordpress</span></span><br><span class="line">    <span class="attr">MasterUserPassword:</span> <span class="string">wordpress</span></span><br><span class="line">    <span class="attr">VPCSecurityGroups:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Sub</span> <span class="string">$&#123;DatabaseSecurityGroup.GroupId&#125;</span></span><br><span class="line">    <span class="attr">DBSubnetGroupName:</span> <span class="type">!Ref</span> <span class="string">DBSubnetGroup</span></span><br><span class="line">    <span class="attr">MultiAZ:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">StorageType:</span> <span class="string">&#x27;gp2&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="Using-read-replication-to-increase-read-performance"><a href="#Using-read-replication-to-increase-read-performance" class="headerlink" title="Using read replication to increase read performance"></a>Using read replication to increase read performance</h3><p>SQL databases can also be scaled horizontally in special circumstances. A database suffering from many read requests can be scaled horizontally by adding additional database instances for read replication. As figure 1 shows, changes to the database are asynchronously replicated to an additional read-only database instance. The read requests can be distributed between the master database and its read-replication databases to increase read throughput.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/03/rds_read_replica@730w.webp 730w, /images/2018/03/rds_read_replica@730w2x.webp 1460w, /images/2018/03/rds_read_replica@610w.webp 610w, /images/2018/03/rds_read_replica@610w2x.webp 1220w, /images/2018/03/rds_read_replica@450w.webp 450w, /images/2018/03/rds_read_replica@450w2x.webp 900w, /images/2018/03/rds_read_replica@330w.webp 330w, /images/2018/03/rds_read_replica@330w2x.webp 660w, /images/2018/03/rds_read_replica@545w.webp 545w, /images/2018/03/rds_read_replica@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/03/rds_read_replica@730w.png 730w, /images/2018/03/rds_read_replica@730w2x.png 1460w, /images/2018/03/rds_read_replica@610w.png 610w, /images/2018/03/rds_read_replica@610w2x.png 1220w, /images/2018/03/rds_read_replica@450w.png 450w, /images/2018/03/rds_read_replica@450w2x.png 900w, /images/2018/03/rds_read_replica@330w.png 330w, /images/2018/03/rds_read_replica@330w2x.png 660w, /images/2018/03/rds_read_replica@545w.png 545w, /images/2018/03/rds_read_replica@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/03/rds_read_replica.png" alt="Figure 1. Read requests are distributed between the master and read-replication databases for higher read performance." title="Figure 1. Read requests are distributed between the master and read-replication databases for higher read performance."></picture></p><p>Tweaking read performance with replication makes sense only if an application generates many read requests and few write requests. Fortunately, most applications read more than they write.</p><h3 id="Creating-a-read-replication-database"><a href="#Creating-a-read-replication-database" class="headerlink" title="Creating a read-replication database"></a>Creating a read-replication database</h3><p>Amazon RDS supports read replication for MySQL, MariaDB, and PostgreSQL databases. To use read replication, you need to enable automatic backups for your database.</p><blockquote><p>Warning: Starting an RDS read replica will incur charges. See <a href="https://aws.amazon.com/rds/pricing/" target="_blank" rel="noopener">https://aws.amazon.com/rds/pricing/</a> if you want to find out the current hourly price.</p></blockquote><p>Execute the following command from your local machine to create a read-replication database. Replace the $DBInstanceIdentifier with the value from <code>aws rds describe-db-instances --query &quot;DBInstances[0].DBInstanceIdentifier&quot; --output text</code>.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">$ aws rds <span class="keyword">create</span>-db-instance-<span class="keyword">read</span>-<span class="keyword">replica</span> \</span><br><span class="line">  <span class="comment">--db-instance-identifier awsinaction-db-read \</span></span><br><span class="line">  <span class="comment">--source-db-instance-identifier $DBInstanceIdentifier</span></span><br></pre></td></tr></table></figure><p>RDS automatically triggers the following steps in the background:</p><ol><li>Creating a snapshot from the source database, also called the master database</li><li>Launching a new database based on that snapshot</li><li>Activating replication between the master and read-replication databases</li><li>Creating an endpoint for SQL read requests to the read-replication database</li></ol><p>After the read-replication database is successfully created, it’s available to answer SQL read requests. The application using the SQL database must support the use of a read-replication database. WordPress, for example, doesn’t support the use of a read replica by default, but you can use a plugin called HyperDB to do this; the configuration is a little tricky, but we’ll skip this part. You can get more information here: <a href="https://wordpress.org/plugins/hyperdb/" target="_blank" rel="noopener">https://wordpress.org/plugins/hyperdb/</a>. Creating or deleting a read replica doesn’t affect the availability of the master database.</p><h4 id="Using-read-replication-to-transfer-data-to-another-region"><a href="#Using-read-replication-to-transfer-data-to-another-region" class="headerlink" title="Using read replication to transfer data to another region"></a>Using read replication to transfer data to another region</h4><p>RDS supports read replication between regions for Aurora, MySQL, MariaDB, and PostgreSQL databases. You can replicate your data from the data centers in Northern Virginia to the data centers in Ireland, for example. Three major use cases for this feature are:</p><ol><li>Backing up data to another region for the unlikely case of an outage of a complete region</li><li>Transferring data to another region to be able to answer read requests with lower latency</li><li>Migrating a database to another region</li></ol><p>Creating read replication between two regions incurs an additional cost because you have to pay for the transferred data.</p><h3 id="Promoting-a-read-replica-to-a-standalone-database"><a href="#Promoting-a-read-replica-to-a-standalone-database" class="headerlink" title="Promoting a read replica to a standalone database"></a>Promoting a read replica to a standalone database</h3><p>If you create a read-replication database to migrate a database from one region to another, or if you need to perform heavy and load-intensive tasks on your database, such as adding an index, it’s helpful to switch your workload from the master database to a read-replication database. The read replica must become the new master database. Promoting read-replication databases to become master databases is possible for Aurora, MySQL, MariaDB, and PostgreSQL databases with RDS.</p><p>The following command promotes the read-replication database you created in this section to a standalone master database. Note that the read-replication database will perform a restart and be unavailable for a few minutes:</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">$ aws rds promote-<span class="keyword">read</span>-<span class="keyword">replica</span> <span class="comment">--db-instance-identifier awsinaction-db-read</span></span><br></pre></td></tr></table></figure><p>The RDS database instance named <code>awsinaction-db-read</code> accepts write requests after the transformation from a read-replication database to a master database’s successful.</p><h2 id="Caching-data-in-memory-ElastiCache"><a href="#Caching-data-in-memory-ElastiCache" class="headerlink" title="Caching data in memory: ElastiCache"></a>Caching data in memory: ElastiCache</h2><p>Imagine a relational database being used for a popular mobile game where players’ scores and ranks are updated and read frequently. The read and write pressure to the database is extremely high, like when ranking scores across millions of players. Mitigating that pressure by scaling the database may help with load but not necessarily the latency or cost. Also, relational databases tend to be more expensive than caching data stores.</p><p style="float: left;"><img src="/images/2018/03/cache_architecture.png" alt="Figure 2. Cache sits between the application and the database" style="max-width: 200px"></p><p>A proven solution that many gaming companies employ is using an in-memory data store, such as Redis, for both caching and ranking through player and game metadata. Instead of reading and sorting the leaderboard directly from the relational database, they also store and use an in-memory game leaderboard in Redis commonly using a Redis Sorted Set which sorts the data automatically upon insertion based on the Sorted Set score parameter. The score value may consist of the actual player ranking or player score in the game.<br>Because the data resides in memory and doesn’t require heavy computation to produce the sort, the retrieval of the information is incredibly fast, leaving little reason to query this information from a relational database. In addition, any other game and player metadata such as player profile, game level information, etc. that requires heavy reads can also be cached within this in-memory layer, freeing their database from heavy read traffic.</p><p>In this solution, both the relational database and in-memory layer store updates to the leaderboard, one serves as the primary database and the other the working and fast processing layer. And for caching data, they may employ a variety of caching techniques to keep the data which is cached fresh, which we’ll review later.  Figure 2 shows where the cache sits between your application and the database.</p><p>A cache comes with multiple benefits:</p><ul><li>The read traffic can be served from the caching layer which frees resources on your data store e.g. for write requests.</li><li>It also speeds up your application because the caching layer responds faster than your data store.</li><li>You can downsize your data store which can be more expensive than the caching layer.</li></ul><p>Most caching layers reside in-memory and this is why they’re fast. The downside is that you can lose the cached data at any time because of a hardware defect or a restart. With Redis, there’s optional failover support. In the event of a node failure, a replica node is elected to be the new primary and already has a copy of the data. Always keep a copy of your data in a primary datastore with disk durability, e.g. like the relational database in the mobile game example.</p><p>Depending on your caching strategy you can either populate the cache in real-time or on-demand. In the mobile game example, on demand means that if the leaderboard isn’t in the cache, the application asks the relational database and puts the result into the cache. Any subsequent request to the cache results in a cache hit meaning the data is found. This is true until the duration of the TTL (time to live) value on the cached value expires. Another term for this strategy’s lazy-loading the data from the primary datastore. We could also have a cronjob running in the background that queries the leaderboard from the relation database every minute and puts the result in the cache.</p><p>The Lazy Loading Strategy (get data on demand) is implemented like this:</p><ol><li>The application writes data to the data store</li><li>Later, if the application wants to read the data, it makes a request to the caching layer</li><li>The caching layer doesn’t contain the data</li><li>The Application reads from the data store directly and puts the read value into the cache and also returns the value to the client</li><li>Later, if the application wants to read the data again, it makes a request to the caching layer and finds the value</li></ol><p>This strategy comes with a problem. What if the data is changed while it’s already in the cache? The cache still contains the old value; setting an appropriate TTL value is critical to ensure cache validity. Let’s say, you apply a TTL for five minutes to your cached data. This means you’re accepting the maximum of five minutes of out of sync data with your primary database. Understanding the frequency of change for the underlying data and the effects of the user experience with producing out of sync data’s the first step of identifying the appropriate TTL value to apply. A common mistake some developers make is assuming that applying a few seconds of a cache TTL makes it not worthwhile of having a cache. Remember within those few seconds, millions of requests can be eliminated from your backend, speeding up your application and reducing the backend database pressure. Performance testing your application with and without your cache along with various caching approaches helps fine tune your implementation. </p><p>In summary, the shorter the time to live (TTL) the more load you have on your underlying datastore. The higher the TTL is, the more out of data the data gets.<br>The Write Through Strategy (cache data upfront) is implemented differently to tackle the synchronization issue:</p><ol><li>The application writes data to the data store and the cache (or the cache is filled asynchronously, e.g. in a cronjob, AWS Lambda function or the application)</li><li>Later, if the application wants to read the data, it makes a request to the caching layer</li><li>The caching layer contains the data</li><li>The value is returned to the client</li></ol><p>This strategy comes with a problem. What if the cache isn’t big enough to contain all your data? Caches are in-memory and your data store disk capacity is usually larger than your cache’s memory capacity. When your cache reaches the available memory, it evicts data or stops accepting new data. In both situations, the application doesn’t work anymore. In the gaming app, the global leaderboard always fits into the cache. Imagine a leaderboard is 4 KB in size and the cache has a capacity of 1 GB (1,048,576 KB). But what about team leaderboards? You can only store 262,144 (1,048,576 &#x2F; 4) leaderboards, and if you have more teams than that, you run into a capacity issue.</p><p>Figure 3 compares the two caching strategies.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/03/cache_strategy@730w.webp 730w, /images/2018/03/cache_strategy@730w2x.webp 1460w, /images/2018/03/cache_strategy@610w.webp 610w, /images/2018/03/cache_strategy@610w2x.webp 1220w, /images/2018/03/cache_strategy@450w.webp 450w, /images/2018/03/cache_strategy@450w2x.webp 900w, /images/2018/03/cache_strategy@330w.webp 330w, /images/2018/03/cache_strategy@330w2x.webp 660w, /images/2018/03/cache_strategy@545w.webp 545w, /images/2018/03/cache_strategy@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/03/cache_strategy@730w.png 730w, /images/2018/03/cache_strategy@730w2x.png 1460w, /images/2018/03/cache_strategy@610w.png 610w, /images/2018/03/cache_strategy@610w2x.png 1220w, /images/2018/03/cache_strategy@450w.png 450w, /images/2018/03/cache_strategy@450w2x.png 900w, /images/2018/03/cache_strategy@330w.png 330w, /images/2018/03/cache_strategy@330w2x.png 660w, /images/2018/03/cache_strategy@545w.png 545w, /images/2018/03/cache_strategy@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/03/cache_strategy.png" alt="Figure 3. Comparing the Lazy Loading and Write Through Caching Strategy" title="Figure 3. Comparing the Lazy Loading and Write Through Caching Strategy"></picture></p><p>When evicting data the cache needs to decide which data it should delete. One popular strategy is to evict the least recently used (LRU) data. This means that cached data contains meta information about the time when it was last accessed. In case of a LRU eviction, the data with the smallest timestamp is chosen for eviction.</p><p>Caches are usually implemented using key-value stores. A key-value store doesn’t support sophisticated query languages such as SQL. They support to retrieve data based on a key, usually a string, or specialized commands, e.g. to extract sorted data efficiently.</p><p>Imagine in your relational database, you’ve a player table for your mobile game. One of the most common queries is <code>SELECT id, nick FROM player ORDER BY score DESC LIMIT 10</code> to retrieve the top ten players. Luckily, the game is popular. But this comes with a technical challenge. If many players look at the leaderboard, the database becomes busy, which causes high latency or even timeouts. You must come up with a plan to reduce the load of the database. As you already learned, caching can help. What technique should you employ to cache? You have a few options:</p><p>One approach you can take with either Memcached or Redis is you can store the result of your SQL query as a String value and the SQL statement as your key name. Instead of using the whole SQL query as the key, you can also hash the string with a hash function like md5 or sha256 to optimize storage and bandwidth (1) as shown in  Figure 4. Before the application sends the query to the database, it takes the SQL query as the key to ask the caching layer for data (2). If the cache doesn’t contain data for the key (3), the SQL query sends to the relational database (4). The result (5) is then stored in the cache using the SQL query as the key (6). The next time the application wants to perform the query it asks the caching layer (7) which now contains the cached table (8).<br>To implement caching, you only need to know the key of the cached item. This can be a SQL query, a filename, a URL, or a user id. You take the key and ask the cache for a result. If no result is found, you make a second call to the underlying datastore who knows the truth.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/03/sql_cache@730w.webp 730w, /images/2018/03/sql_cache@730w2x.webp 1460w, /images/2018/03/sql_cache@610w.webp 610w, /images/2018/03/sql_cache@610w2x.webp 1220w, /images/2018/03/sql_cache@450w.webp 450w, /images/2018/03/sql_cache@450w2x.webp 900w, /images/2018/03/sql_cache@330w.webp 330w, /images/2018/03/sql_cache@330w2x.webp 660w, /images/2018/03/sql_cache@545w.webp 545w, /images/2018/03/sql_cache@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/03/sql_cache@730w.png 730w, /images/2018/03/sql_cache@730w2x.png 1460w, /images/2018/03/sql_cache@610w.png 610w, /images/2018/03/sql_cache@610w2x.png 1220w, /images/2018/03/sql_cache@450w.png 450w, /images/2018/03/sql_cache@450w2x.png 900w, /images/2018/03/sql_cache@330w.png 330w, /images/2018/03/sql_cache@330w2x.png 660w, /images/2018/03/sql_cache@545w.png 545w, /images/2018/03/sql_cache@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/03/sql_cache.png" alt="Figure 4. SQL caching layer implementation" title="Figure 4. SQL caching layer implementation"></picture></p><p>With Redis, you also have other options of storing the data in a variety of other data structures such as a Redis SortedSet. If the data was stored in a Redis SortedSet, retrieving the ranked data’s efficient. You could store players and their scores and sort by the score. An equivalent command to the SQL query is:</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">ZREVRANGE</span> <span class="string">&quot;player-scores&quot;</span> <span class="number">0</span> <span class="number">9</span></span><br></pre></td></tr></table></figure><p>This returns the ten players in a Sorted Set named “player-scores” ordered by highest to lowest.<br>The two most popular implementations of in-memory key-value stores are Memcached and Redis.  Table 1 compares their features.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th>Memcached</th><th>Redis</th></tr></thead><tbody><tr><td>Data types</td><td>simple</td><td>complex</td></tr><tr><td>Data manipulation commands</td><td>12</td><td>125</td></tr><tr><td>Server-side scripting</td><td>no</td><td>yes (Lua)</td></tr><tr><td>Transactions</td><td>no</td><td>yes</td></tr><tr><td>Multi-threaded</td><td>yes</td><td>no</td></tr></tbody></table><p>Amazon ElastiCache offers managed Memcached and Redis clusters. Managed includes:</p><ul><li>Installation: AWS installs the software for you and has enhanced the underlying engines</li><li>Administration: AWS administers Memcached&#x2F;Redis for you and provides you means to configure your cluster through parameter groups. AWS also detects and automates failovers (Redis only)</li><li>Monitoring: AWS publishes metrics to CloudWatch for you</li><li>Patching: AWS performs security upgrades in a customizable time window</li><li>Backups: AWS optionally backs up your data in a customizable time window (Redis only)</li><li>Replication: AWS optionally sets up replication (Redis only)</li></ul><h2 id="Learn-more"><a href="#Learn-more" class="headerlink" title="Learn more"></a>Learn more</h2><p>Do you want to learn more? Check out <a href="http://bit.ly/amazon-web-services-in-action-2nd-edition" target="_blank" rel="noopener">Amazon Web Services in Action, Second Edition</a> with chapters about RDS and ElastiCache going into more details.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Introducing AWS Lambda</title>
      <link>https://cloudonaut.io/introducing-aws-lambda/</link>
      <description>
        <![CDATA[<p>You don’t need a virtual machine to run your own source code any more as AWS Lambda offers execution environments for Java, JavaScript (N]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/introducing-aws-lambda/</guid>
      <pubDate>Mon, 12 Feb 2018 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>You don’t need a virtual machine to run your own source code any more as AWS Lambda offers execution environments for Java, JavaScript (Node.js), C#, Go, and Python. All you need to do is to implement a function, upload your source code and configure the execution environment. Afterwards, your source code is executed within a fully-managed computing environment. Your new tool enables you to automate operations tasks within your infrastructure with ease. This is because AWS Lambda is well-integrated with all parts of AWS. We use AWS to automate our infrastructure regularly. For example, to add and remove instances to a container cluster, based on a custom algorithm, or to process and analyze log files.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/02/introducing-aws-lambda@730w.webp 730w, /images/2018/02/introducing-aws-lambda@730w2x.webp 1460w, /images/2018/02/introducing-aws-lambda@610w.webp 610w, /images/2018/02/introducing-aws-lambda@610w2x.webp 1220w, /images/2018/02/introducing-aws-lambda@450w.webp 450w, /images/2018/02/introducing-aws-lambda@450w2x.webp 900w, /images/2018/02/introducing-aws-lambda@330w.webp 330w, /images/2018/02/introducing-aws-lambda@330w2x.webp 660w, /images/2018/02/introducing-aws-lambda@545w.webp 545w, /images/2018/02/introducing-aws-lambda@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/02/introducing-aws-lambda@730w.jpg 730w, /images/2018/02/introducing-aws-lambda@730w2x.jpg 1460w, /images/2018/02/introducing-aws-lambda@610w.jpg 610w, /images/2018/02/introducing-aws-lambda@610w2x.jpg 1220w, /images/2018/02/introducing-aws-lambda@450w.jpg 450w, /images/2018/02/introducing-aws-lambda@450w2x.jpg 900w, /images/2018/02/introducing-aws-lambda@330w.jpg 330w, /images/2018/02/introducing-aws-lambda@330w2x.jpg 660w, /images/2018/02/introducing-aws-lambda@545w.jpg 545w, /images/2018/02/introducing-aws-lambda@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/02/introducing-aws-lambda.jpg" alt="Introducing AWS Lambda" title="Introducing AWS Lambda"></picture></p><p>AWS Lambda offers a maintenance-free and highly-available computing environment. You no longer need to install security updates, replace failed virtual machines, or manage remote access (SSH or RDP) for administrators. On top of that, AWS Lambda isn’t billed by the second but per execution. Therefore, you don’t need to pay for idling resources which are waiting for work (e.g. a task triggered once a day).</p><p>AWS Lambda is a powerful tool in different scenarios: a REST API for a web or mobile application, an event-driven system to process data, or even an IoT backend. We are also big fans of using AWS Lambda to automate operational tasks within our cloud infrastructure. For example, using a Lambda function to perform periodic health checks of a website. Or to add tags to newly launched EC2 instances automatically.</p><p>But what is AWS Lambda? We’ll start with a short introduction.</p><blockquote><p>This article, excerpted from chapter 7 of <a href="http://bit.ly/amazon-web-services-in-action-2nd-edition" target="_blank" rel="noopener">Amazon Web Services in Action, Second Edition</a>, is about adding a new tool to your toolbox. The tool we’re talking about is as flexible as a Swiss army knife, and it’s called AWS Lambda.</p><p align="center"><img src="/images/2017/09/aws-in-action-2nd-meap.png" alt="Amazon Web Services in Action, Second Edition" style="max-width: 300px"></p>Save 37% off [Amazon Web Services in Action, Second Edition](http://bit.ly/amazon-web-services-in-action-2nd-edition) with code `fccwittig` at [manning.com](http://bit.ly/amazon-web-services-in-action-2nd-edition).</blockquote><h2 id="Executing-your-code-with-AWS-Lambda"><a href="#Executing-your-code-with-AWS-Lambda" class="headerlink" title="Executing your code with AWS Lambda"></a>Executing your code with AWS Lambda</h2><p>Compute capacity is available at different layers of abstraction on AWS: virtual machines, containers, and functions. Containers offer another layer of abstraction on top of virtual machines. Unfortunately, we don’t cover containers in this article. AWS Lambda provides computing power as well but in a fine-granular manner: an execution environment for small functions, not a full-blown operating system or container.</p><h2 id="What-is-Serverless"><a href="#What-is-Serverless" class="headerlink" title="What is Serverless?"></a>What is Serverless?</h2><p>When reading about AWS Lambda you might have stumbled upon the term serverless already. The following quote summarizes the confusion created by a catchy and provocative phrase:</p><blockquote><p>[… ] the word serverless is a bit of a misnomer. Whether you use a computer service such as AWS Lambda to execute your code, or interact with an API, there are still servers running in the background. The difference is that these servers are hidden from you. No infrastructure exists for you to think about and there’s no way to tweak the underlying operating system. Someone else takes care of the nitty-gritty details of infrastructure management, freeing your time for other things.<br><em>Peter Sbarski, Serverless Architectures on AWS, Manning (2017)</em></p></blockquote><p>We define the following the following criteria for a serverless system:</p><ul><li>No need to manage and maintain virtual machines.</li><li>Fully managed service offering scalability and high availability.</li><li>Billed per request and its resource consumption.</li></ul><p>AWS isn’t the only provider offering a serverless platform. Google (Cloud Functions) and Microsoft (Azure Functions) are competing with AWS in this area, for example.</p><h2 id="Running-your-code-on-AWS-Lambda"><a href="#Running-your-code-on-AWS-Lambda" class="headerlink" title="Running your code on AWS Lambda"></a>Running your code on AWS Lambda</h2><p>As shown in the following figure, to execute your code with AWS Lambda the following four steps are needed:</p><ol><li>Writing the source code.</li><li>Uploading your code and its dependencies (e.g. libraries or modules).</li><li>Creating a function determining the runtime environment and configuration.</li><li>Executing the function.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/02/lambda-intro@730w.webp 730w, /images/2018/02/lambda-intro@730w2x.webp 1460w, /images/2018/02/lambda-intro@610w.webp 610w, /images/2018/02/lambda-intro@610w2x.webp 1220w, /images/2018/02/lambda-intro@450w.webp 450w, /images/2018/02/lambda-intro@450w2x.webp 900w, /images/2018/02/lambda-intro@330w.webp 330w, /images/2018/02/lambda-intro@330w2x.webp 660w, /images/2018/02/lambda-intro@545w.webp 545w, /images/2018/02/lambda-intro@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/02/lambda-intro@730w.png 730w, /images/2018/02/lambda-intro@730w2x.png 1460w, /images/2018/02/lambda-intro@610w.png 610w, /images/2018/02/lambda-intro@610w2x.png 1220w, /images/2018/02/lambda-intro@450w.png 450w, /images/2018/02/lambda-intro@450w2x.png 900w, /images/2018/02/lambda-intro@330w.png 330w, /images/2018/02/lambda-intro@330w2x.png 660w, /images/2018/02/lambda-intro@545w.png 545w, /images/2018/02/lambda-intro@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/02/lambda-intro.png" alt="Running your code on AWS Lambda" title="Running your code on AWS Lambda"></picture></p><p>You don’t need to launch any virtual machines. AWS executes your code in a fully-managed compute environment.</p><p>Currently, AWS Lambda offers runtime environments for the following languages:</p><ul><li>Java</li><li>JavaScript (Node.js)</li><li>C#</li><li>Go</li><li>Python</li></ul><p>Next, you’ll learn more about AWS Lambda by comparing its concepts with virtual machines offered by EC2.</p><h2 id="Comparing-AWS-Lambda-with-virtual-machines-Amazon-EC2"><a href="#Comparing-AWS-Lambda-with-virtual-machines-Amazon-EC2" class="headerlink" title="Comparing AWS Lambda with virtual machines (Amazon EC2)"></a>Comparing AWS Lambda with virtual machines (Amazon EC2)</h2><p>What’s the difference between AWS Lambda and virtual machines? First off, the granularity of virtualization. On the one hand, a virtual machine provides a full operating system which is used to run one or multiple applications. On the other hand, AWS Lambda offers an execution environment for a function, a small part of an application.</p><p>Furthermore, Amazon EC2 offers virtual machines as a service. But you’re still responsible for operating these virtual machines in a secure, scalable and highly available way. Doing this generates a bunch of operating tasks resulting in substantial maintenance effort. By contrast, AWS Lambda offers a fully-managed execution environment. AWS is managing the underlying infrastructure for you and provides a production-ready infrastructure to you.</p><p>Otherwise AWS Lambda is billed per execution, not per second that a virtual machine is running. You don’t need to pay for unused resource waiting for requests or tasks any longer. For example, running a script to check the health of a website every five minutes on a virtual machine costs you a minimum of USD 4. Executing the same health check with AWS Lambda is free as you don’t even exceed the never-ending monthly free tier.</p><p>The following table compares AWS Lambda and virtual machines in detail.</p><table class="table table-striped table-responsive"><thead><tr><th></th><th>AWS Lambda</th><th>Amazon EC2</th></tr></thead><tbody><tr><td>Granularity of virtualization</td><td>Small piece of source code aka function.</td><td>A whole operating system.</td></tr><tr><td>Scalability</td><td>Scales automatically. Throttling limit prevents you from creating unwanted costs accidentally and can be increased by AWS support if needed.</td><td>Auto Scaling Group allows you to scale the number of EC2 instances serving requests automatically. But configuring and monitoring the scaling activities are your responsibility.</td></tr><tr><td>High Availability</td><td>Fault tolerant by default. Compute infrastructure spans multiple machines and data centers.</td><td>A virtual machine isn&#39;t highly available by default. Nevertheless it&#39;s possible to setup a highly available infrastructure based on EC2 instances as well.</td></tr><tr><td>Maintenance effort</td><td>Almost zero. You need to configure your function.</td><td>You&#39;re responsible for maintaining all layers between the operating system of your virtual machine and the runtime environment of your application.</td></tr><tr><td>Deployment effort</td><td>Almost zero due to a well-defined API.</td><td>Rolling out your application among a fleet of virtual machines is a challenge requiring tools and know-how.</td></tr><tr><td>Pricing model</td><td>Pay per request as well as execution time and memory.</td><td>Pay for operating hours of virtual machine billed per second.</td></tr></tbody></table><h2 id="Learn-more"><a href="#Learn-more" class="headerlink" title="Learn more"></a>Learn more</h2><p>Do you want to learn more? Check out chapter 7 of our book <a href="http://bit.ly/amazon-web-services-in-action-2nd-edition" target="_blank" rel="noopener">Amazon Web Services in Action, Second Edition</a> including two real-world examples: website health check and automatically tagging a newly launched EC2 instance with it’s owner.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Monitoring a critical part of your infrastructure: Amazon Elasticsearch domain</title>
      <link>https://cloudonaut.io/monitoring-a-critical-part-of-your-infrastructure-amazon-elasticsearch-domain/</link>
      <description>
        <![CDATA[<p>I used Elasticsearch in various projects: to add rich search functionality to applications as well as to collect and analyze logs with th]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <category domain="https://cloudonaut.io/tag/elasticsearch/">elasticsearch</category>
      <guid isPermaLink="true">https://cloudonaut.io/monitoring-a-critical-part-of-your-infrastructure-amazon-elasticsearch-domain/</guid>
      <pubDate>Thu, 08 Feb 2018 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>I used Elasticsearch in various projects: to add rich search functionality to applications as well as to collect and analyze logs with the help of Kibana. In both cases, either your users or your operators rely on the Elasticsearch infrastructure. In one of my past projects, the team used Elasticsearch to store logs of EC2 instances. Over time, more and more applications were moved to AWS. Therefore, the volume of logs shipped to Elasticsearch also increased. One day, it was a Sunday, the Elasticsearch cluster became suddenly unavailable, and the log shippers were throwing errors. Luckily, the log shippers were monitored, and someone was paged to look at the issue. It took some time to find out that the Elasticsearch cluster had no disk space available. Situations like this are avoidable. Monitor available disk space and you can react before the disks are full.</p><p>Amazon Elasticsearch provides Elasticsearch as a Service. The fully managed service covers a lot of the challenges of operating a search engine (e.g., cluster management, patching the operating system and the search engine, …). But you are still responsible for some operational aspects: sizing and performance optimizations. Therefore, you need to monitor every Elasticsearch domain that serves production workloads.</p><p>Monitoring your whole cloud infrastructure is a complex task, as Andreas pointed out in his <a href="/aws-monitoring-primer/">AWS Monitoring Primer</a>. In this blog post, I will focus on the relevant parts for monitoring your Elasticsearch domain:</p><ol><li>I guide you to the relevant AWS monitoring services and features offered by AWS.</li><li>I present best practices based on real-world client projects.</li><li>I provide a CloudFormation template that implements all ideas in the post.</li><li>You can use the template to monitor any Elasticsearch domain in a minute.</li></ol><p>Let’s get started!</p><h2 id="Identifying-important-CloudWatch-metrics"><a href="#Identifying-important-CloudWatch-metrics" class="headerlink" title="Identifying important CloudWatch metrics"></a>Identifying important CloudWatch metrics</h2><p>Each Elasticsearch domain sends <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/es-metricscollected.html" target="_blank" rel="noopener">metrics to CloudWatch</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/02/elasticsearch-event@730w.webp 730w, /images/2018/02/elasticsearch-event@730w2x.webp 1460w, /images/2018/02/elasticsearch-event@610w.webp 610w, /images/2018/02/elasticsearch-event@610w2x.webp 1220w, /images/2018/02/elasticsearch-event@450w.webp 450w, /images/2018/02/elasticsearch-event@450w2x.webp 900w, /images/2018/02/elasticsearch-event@330w.webp 330w, /images/2018/02/elasticsearch-event@330w2x.webp 660w, /images/2018/02/elasticsearch-event@545w.webp 545w, /images/2018/02/elasticsearch-event@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/02/elasticsearch-event@730w.png 730w, /images/2018/02/elasticsearch-event@730w2x.png 1460w, /images/2018/02/elasticsearch-event@610w.png 610w, /images/2018/02/elasticsearch-event@610w2x.png 1220w, /images/2018/02/elasticsearch-event@450w.png 450w, /images/2018/02/elasticsearch-event@450w2x.png 900w, /images/2018/02/elasticsearch-event@330w.png 330w, /images/2018/02/elasticsearch-event@330w2x.png 660w, /images/2018/02/elasticsearch-event@545w.png 545w, /images/2018/02/elasticsearch-event@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/02/elasticsearch-event.png" alt="CloudWatch metrics expose internals of the Elasticsearch domain" title="CloudWatch metrics expose internals of the Elasticsearch domain"></picture></p><p>The most important metrics are:</p><table class="table table-striped table-responsive"><thead><tr><th>area</th><th>metric</th><th>description</th><th>relevance</th></tr></thead><tbody><tr><td>Storage</td><td>FreeStorageSpace</td><td>The free space, in megabytes, for all data nodes in the cluster.</td><td>ES throws a ClusterBlockException when this metric reaches 0.</td></tr><tr><td>CPU</td><td>CPUUtilization</td><td>The maximum percentage of CPU resources used for data nodes in the cluster.</td><td>100% CPU utilization isn&#39;t uncommon, but sustained high averages are problematic.</td></tr><tr><td>CPU</td><td>CPUCreditBalance</td><td>The remaining CPU credits available for data nodes in the cluster (only applies to the t2 family)</td><td>If you run out of burst credits, performance will drop significantly.</td></tr><tr><td>CPU</td><td>MasterCPUUtilization</td><td>The maximum percentage of CPU resources used by the dedicated master nodes.</td><td>Because of their role in cluster stability, dedicated master nodes should have lower average usage than data nodes.</td></tr><tr><td>CPU</td><td>MasterCPUCreditBalance</td><td>The remaining CPU credits available for dedicated master nodes in the cluster (only applies to the t2 family).</td><td>If you run out of burst credits, performance will drop significantly.</td></tr><tr><td>Memory</td><td>JVMMemoryPressure</td><td>The maximum percentage of the Java heap used for all data nodes in the cluster.</td><td>The cluster could encounter out of memory errors if usage increases.</td></tr><tr><td>Memory</td><td>MasterJVMMemoryPressure</td><td>The maximum percentage of the Java heap used for all dedicated master nodes in the cluster.</td><td>Because of their role in cluster stability, dedicated master nodes should have lower average usage than data nodes.</td></tr><tr><td>Cluster</td><td>ClusterStatus.yellow</td><td>At least one replica shard is not allocated to a node</td><td>Your high availability is compromised to some degree. If more shards disappear, you might lose data. Think of yellow as a warning that should prompt investigation.</td></tr><tr><td>Cluster</td><td>ClusterStatus.red</td><td>At least one primary shard is not allocated to a node.</td><td>You are missing data: searches will return partial results, and indexing into that shard will return an exception.</td></tr><tr><td>Cluster</td><td>ClusterIndexWritesBlocked</td><td>Indicates whether your cluster is accepting or blocking incoming write requests.</td><td>A value of 1 means that the cluster is blocking write requests.</td></tr><tr><td>Cluster</td><td>AutomatedSnapshotFailure</td><td>The number of failed automated snapshots for the cluster.</td><td>A value of 1 indicates that no automated snapshot was taken for the domain in the previous 36 hours.</td></tr><tr><td>Cluster</td><td>KibanaHealthyNodes</td><td>A health check for Kibana.</td><td>A value of 0 indicates that Kibana is inaccessible.</td></tr><tr><td>Cluster</td><td>KMSKeyError</td><td>Indicates whether your cluster can use the configured KMS key.</td><td>A value of 1 indicates that the KMS customer master key used to encrypt data at rest has been disabled.</td></tr><tr><td>Cluster</td><td>KMSKeyInaccessible</td><td>Indicates whether your cluster can use the configured KMS key.</td><td>A value of 1 indicates that the KMS customer master key used to encrypt data at rest has been deleted or revoked its grants to Amazon ES.</td></tr></tbody></table><p>Once important metrics are identified, you can use them to understand how a healthy system differs from an impacted system.</p><h2 id="Defining-thresholds"><a href="#Defining-thresholds" class="headerlink" title="Defining thresholds"></a>Defining thresholds</h2><p>One of the hardest parts of monitoring is to define what healthy means. For each metric, you have to define a threshold between healthy and impacted. E.g., you regard CPU utilization under 80% as healthy because the application was never impacted when the CPU was not utilized. Thresholds are defined based on observations from the past. They might need adjustment in the future.</p><blockquote><p>We don’t know about the whole application here. We can only reason about one component: the search engine. Application monitoring is a different topic. E.g., HTTP 5XX responses, latency, sign-ups.</p></blockquote><p>From our experience and the <a href="https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/cloudwatch-alarms.html" target="_blank" rel="noopener">AWS documentation</a>, we usually start with the following thresholds to identify unhealthy behavior and adjust them over time.</p><table class="table table-striped table-responsive"><thead><tr><th>area</th><th>metric</th><th>comparison operator</th><th>threshold</th><th>rationale</th></tr></thead><tbody><tr><td>Storage</td><td>FreeStorageSpace</td><td>&lt;</td><td>2000 MB</td><td>2 GB usually provides enough time to a) fix why so much space is consumed or b) add capacity. You can also modify this value to 10% of your database capacity.</td></tr><tr><td>CPU</td><td>CPUUtilization</td><td>&gt;</td><td>80 %</td><td>Queuing theory tells us the latency increases exponentially with utilization. In practice, we see higher latency when utilization exceeds 80% and unacceptable high latency with utilization above 90%.</td></tr><tr><td>CPU</td><td>CPUCreditBalance</td><td>&lt;</td><td>20</td><td>One credit equals 1 minute of 100% usage of a vCPU. 20 credits should give you enough time to a) fix the inefficiency, b) add capacity or c) don&#39;t use t2 type.</td></tr><tr><td>CPU</td><td>MasterCPUUtilization</td><td>&gt;</td><td>50 %</td><td>Because of their role in cluster stability and blue/green deployments, dedicated master nodes should have lower average CPU usage than data nodes.</td></tr><tr><td>CPU</td><td>MasterCPUCreditBalance</td><td>&lt;</td><td>20</td><td>One credit equals 1 minute of 100% usage of a vCPU. 20 credits should give you enough time to a) fix the inefficiency, b) add capacity or c) don&#39;t use t2 type.</td></tr><tr><td>Memory</td><td>JVMMemoryPressure</td><td>&gt;</td><td>80 %</td><td>The cluster could encounter out of memory errors if usage increases.</td></tr><tr><td>Memory</td><td>MasterJVMMemoryPressure</td><td>&gt;</td><td>80 %</td><td>The cluster could encounter out of memory errors if usage increases.</td></tr><tr><td>Cluster</td><td>ClusterStatus.yellow</td><td>&gt;</td><td>0</td><td>Your high availability is compromised to some degree. If more shards disappear, you might lose data. Think of yellow as a warning that should prompt investigation.</td></tr><tr><td>Cluster</td><td>ClusterStatus.red</td><td>&gt;</td><td>0</td><td>You are missing data: searches will return partial results, and indexing into that shard will return an exception.</td></tr><tr><td>Cluster</td><td>ClusterIndexWritesBlocked</td><td>&gt;</td><td>0</td><td>Cluster is blocking write requests.</td></tr><tr><td>Cluster</td><td>AutomatedSnapshotFailure</td><td>&gt;</td><td>0</td><td>No automated snapshot was taken for the domain in the previous 36 hours.</td></tr><tr><td>Cluster</td><td>KibanaHealthyNodes</td><td>&lt;</td><td>1</td><td>Kibana is inaccessible.</td></tr><tr><td>Cluster</td><td>KMSKeyError</td><td>&gt;</td><td>0</td><td>The KMS customer master key used to encrypt data at rest has been disabled.</td></tr><tr><td>Cluster</td><td>KMSKeyInaccessible</td><td>&gt;</td><td>0</td><td>The  KMS customer master key used to encrypt data at rest has been deleted or revoked its grants to Amazon ES.</td></tr></tbody></table><p>Now you know what healthy&#x2F;unhealthy means. It’s time to define CloudWatch Alarms to send you an alert if a metric exceeds its threshold.</p><h2 id="Observing-metrics-with-CloudWatch-Alarms-and-marbot"><a href="#Observing-metrics-with-CloudWatch-Alarms-and-marbot" class="headerlink" title="Observing metrics with CloudWatch Alarms and marbot"></a>Observing metrics with CloudWatch Alarms and marbot</h2><p>A CloudWatch Alarm continuously watches a metric. Once the threshold is reached, an action is performed that sends a message to an SNS topic. From this topic, you can then send yourself an email. We found that emails are not a good way to handle alerts. In a team, multiple people are responsible. If you send an email to a group email address:</p><ol><li>Your team has no idea if someone already started to work on solving the issue.</li><li>You disturb the whole team for each alert.</li><li>It’s easy to ignore an email.</li><li>You have no statistics about how many alerts are generated. Too many alerts are an indication that your team is no longer able to handle them.</li><li>No help to investigate the issue is available, like links to the AWS Management Console.</li></ol><p>To solve the problem, we built <a href="https://marbot.io/" target="_blank" rel="noopener">marbot: a Slack chatbot that manages and escalates AWS alerts for you</a>.</p><p><img class="img-fluid" src="/images/2018/01/marbot-alert.gif" alt="marbot forwards alerts to Slack" title="marbot forwards alerts to Slack"></p><p>marbot sends alerts to a single user from the Slack channel via a direct message. If the user doesn’t acknowledge the alert within 5 minutes, marbot will escalate to the next level. Escalations minimize distraction while keeping response time low. <a href="https://marbot.io/" target="_blank" rel="noopener">Try marbot for free now</a>.</p><h2 id="CloudFormation-template"><a href="#CloudFormation-template" class="headerlink" title="CloudFormation template"></a>CloudFormation template</h2><p>We developed a CloudFormation template to monitor an Elasticsearch domain in any region. The template integrates with marbot, but you can modify it to send out emails. The template is available on <a href="https://github.com/marbot-io/monitoring-jump-start/blob/master/marbot-elasticsearch.yml" target="_blank" rel="noopener">GitHub</a> for free.</p><p>If you have already installed marbot, you can also ask marbot to monitor your Elasticsearch domain or <a href="https://marbot.io/blog/monitoring-jump-start-amazon-elasticsearch.html" target="_blank" rel="noopener">read more detailed setup instructions</a>. Otherwise: <a href="https://marbot.io/" target="_blank" rel="noopener">Try marbot for free now</a>.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Monitoring an Elasticsearch domain requires looking at 14 different CloudWatch metrics.</p><p>CloudWatch Alarms can trigger actions. The obvious choice is to send out an email if a metric exceeds a threshold. But we recommend not to use emails. Instead, use a tool like marbot. marbot comes with <a href="https://marbot.io/" target="_blank" rel="noopener">alert escalation, deduplication, and context-aware links to the AWS Management Console</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Monitoring a critical part of your infrastructure: Amazon Relational Database Service (RDS)</title>
      <link>https://cloudonaut.io/monitoring-a-critical-part-of-your-infrastructure-amazon-relational-database-service-rds/</link>
      <description>
        <![CDATA[<p>Amazon RDS provides PostgreSQL, MySQL, MariaDB, Oracle, and Microsoft SQL Server as a Service. The fully managed service covers a lot of]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/rds/">rds</category>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <guid isPermaLink="true">https://cloudonaut.io/monitoring-a-critical-part-of-your-infrastructure-amazon-relational-database-service-rds/</guid>
      <pubDate>Mon, 22 Jan 2018 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Amazon RDS provides PostgreSQL, MySQL, MariaDB, Oracle, and Microsoft SQL Server as a Service. The fully managed service covers a lot of the challenges of operating a database (e.g., master-standby replication, snapshots, patching the operating system and the database system, …). But you are still responsible for some operational aspects: sizing and performance optimizations. Therefore, you need to monitor every RDS instance that serves production workloads.</p><blockquote><p>Aurora is not covered in this post. </p></blockquote><p>Monitoring your whole cloud infrastructure is a complex task, as Andreas pointed out in his <a href="/aws-monitoring-primer/">AWS Monitoring Primer</a>. In this blog post, I will focus on the relevant parts for monitoring your RDS database instances:</p><ol><li>I guide you to the relevant AWS monitoring services and features offered by AWS.</li><li>I present best practices based on real-world client projects.</li><li>I provide a CloudFormation template that implements all ideas in the post.</li><li>You can use the template to monitor any RDS database instance in a minute.</li></ol><p>Let’s get started!</p><h2 id="Identifying-important-CloudWatch-metrics"><a href="#Identifying-important-CloudWatch-metrics" class="headerlink" title="Identifying important CloudWatch metrics"></a>Identifying important CloudWatch metrics</h2><p>Each RDS database instance (Multi-AZ or Single-AZ) sends <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/rds-metricscollected.html" target="_blank" rel="noopener">metrics to CloudWatch</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/rds-event-low-storage@730w.webp 730w, /images/2018/01/rds-event-low-storage@730w2x.webp 1460w, /images/2018/01/rds-event-low-storage@610w.webp 610w, /images/2018/01/rds-event-low-storage@610w2x.webp 1220w, /images/2018/01/rds-event-low-storage@450w.webp 450w, /images/2018/01/rds-event-low-storage@450w2x.webp 900w, /images/2018/01/rds-event-low-storage@330w.webp 330w, /images/2018/01/rds-event-low-storage@330w2x.webp 660w, /images/2018/01/rds-event-low-storage@545w.webp 545w, /images/2018/01/rds-event-low-storage@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/rds-event-low-storage@730w.png 730w, /images/2018/01/rds-event-low-storage@730w2x.png 1460w, /images/2018/01/rds-event-low-storage@610w.png 610w, /images/2018/01/rds-event-low-storage@610w2x.png 1220w, /images/2018/01/rds-event-low-storage@450w.png 450w, /images/2018/01/rds-event-low-storage@450w2x.png 900w, /images/2018/01/rds-event-low-storage@330w.png 330w, /images/2018/01/rds-event-low-storage@330w2x.png 660w, /images/2018/01/rds-event-low-storage@545w.png 545w, /images/2018/01/rds-event-low-storage@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/rds-event-low-storage.png" alt="CloudWatch metrics expose internals of the RDS database instance" title="CloudWatch metrics expose internals of the RDS database instance"></picture></p><p>The most important metrics are:</p><table class="table table-striped table-responsive"><thead><tr><th>area</th><th>metric</th><th>description</th><th>relevance</th></tr></thead><tbody><tr><td>Storage</td><td>BurstBalance</td><td>The percent of <a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html">burst credits</a> available. (only applies to the storage type gp2)</td><td>If you run out of burst credits, I/O performance will drop significantly.</td></tr><tr><td>Storage</td><td>DiskQueueDepth</td><td>The number of outstanding IOs (read/write requests) waiting to access the disk.</td><td>If many requests are queued, the storage is a bottleneck and latency is increased.</td></tr><tr><td>Storage</td><td>FreeStorageSpace</td><td>The amount of available storage space in bytes.</td><td>If your DB instance runs out of storage space, it might no longer be available.</td></tr><tr><td>CPU</td><td>CPUUtilization</td><td>The percentage of CPU utilization.</td><td>If the CPU is highly utilized, latency is added because computing tasks have to wait until they are scheduled.</td></tr><tr><td>CPU</td><td>CPUCreditBalance</td><td>The number (not percentage!) of burst credits available. (only applies to the instance family t2)</td><td>If you run out of burst credits, performance will drop significantly.</td></tr><tr><td>Memory</td><td>FreeableMemory</td><td>The amount of available random access memory in bytes.</td><td>A highly utilized system usually comes with higher latency.</td></tr><tr><td>Memory</td><td>SwapUsage</td><td>The amount of swap space used on the DB instance in bytes.</td><td>If memory is moved to disk performance usually suffers.</td></tr></tbody></table><p>Once important metrics are identified, you can use them to understand how a healthy system differs from an impacted system.</p><h2 id="Defining-thresholds"><a href="#Defining-thresholds" class="headerlink" title="Defining thresholds"></a>Defining thresholds</h2><p>One of the hardest parts of monitoring is to define what healthy means. For each metric, you have to define a threshold between healthy and impacted. E.g., you regard CPU utilization under 80% as healthy because the application was never impacted when the CPU was not utilized. Thresholds are defined based on observations from the past. They might need adjustment in the future.</p><blockquote><p>We don’t know about the whole application here. We can only reason about one component: the database. Application monitoring is a different topic. E.g., HTTP 5XX responses, latency, sign-ups.</p></blockquote><p>From our experience, we usually start with the following thresholds to identify unhealthy behavior and adjust them over time.</p><table class="table table-striped table-responsive"><thead><tr><th>area</th><th>metric</th><th>comparison operator</th><th>threshold</th><th>rationale</th></tr></thead><tbody><tr><td>Storage</td><td>BurstBalance</td><td>&lt;</td><td>20 %</td><td>20 % of credits allow you to burst for a few minutes which gives you enough time to a) fix the inefficiency, b) add capacity or c) switch to io1 storage type.</td></tr><tr><td>Storage</td><td>DiskQueueDepth</td><td>&gt;</td><td>64</td><td>This number is calculated from our experience with RDS workloads.</td></tr><tr><td>Storage</td><td>FreeStorageSpace</td><td>&lt;</td><td>2 GB</td><td>2 GB usually provides enough time to a) fix why so much space is consumed or b) add capacity. You can also modify this value to 10% of your database capacity.</td></tr><tr><td>CPU</td><td>CPUUtilization</td><td>&gt;</td><td>80 %</td><td>Queuing theory tells us the latency increases exponentially with utilization. In practice, we see higher latency when utilization exceeds 80% and unacceptable high latency with utilization above 90%</td></tr><tr><td>CPU</td><td>CPUCreditBalance</td><td>&lt;</td><td>20</td><td>One credit equals 1 minute of 100% usage of a vCPU. 20 credits should give you enough time to a) fix the inefficiency, b) add capacity or c) don&#39;t use t2 type.</td></tr><tr><td>Memory</td><td>FreeableMemory</td><td>&lt;</td><td>64 MB</td><td>This number is calculated from our experience with RDS workloads.</td></tr><tr><td>Memory</td><td>SwapUsage</td><td>&gt;</td><td>256 MB</td><td>Sometimes you can not entirely avoid swapping. But once the database accesses paged memory, it will slow down.</td></tr></tbody></table><p>Now you know what healthy&#x2F;unhealthy means. It’s time to define CloudWatch Alarms to send you an alert if a metric exceeds its threshold.</p><h2 id="Observing-metrics-with-CloudWatch-Alarms-and-marbot"><a href="#Observing-metrics-with-CloudWatch-Alarms-and-marbot" class="headerlink" title="Observing metrics with CloudWatch Alarms and marbot"></a>Observing metrics with CloudWatch Alarms and marbot</h2><p>A CloudWatch Alarm continuously watches a metric. Once the threshold is reached, an action is performed that sends a message to an SNS topic. From this topic, you can then send yourself an email. We found that emails are not a good way to handle alerts. In a team, multiple people are responsible. If you send an email to a group email address:</p><ol><li>Your team has no idea if someone already started to work on solving the issue.</li><li>You disturb the whole team for each alert.</li><li>It’s easy to ignore an email.</li><li>You have no statistics about how many alerts are generated. Too many alerts are an indication that your team is no longer able to handle them.</li><li>No help to investigate the issue is available, like links to the AWS Management Console.</li></ol><p>To solve the problem, we built <a href="https://marbot.io/" target="_blank" rel="noopener">marbot: a Slack chatbot that manages and escalates AWS alerts for you</a>.</p><p><img class="img-fluid" src="/images/2018/01/marbot-alert.gif" alt="marbot forwards alerts to Slack" title="marbot forwards alerts to Slack"></p><p>marbot sends alerts to a single user from the Slack channel via a direct message. If the user doesn’t acknowledge the alert within 5 minutes, marbot will escalate to the next level. Escalations minimize distraction while keeping response time low. <a href="https://marbot.io/" target="_blank" rel="noopener">Try marbot for free now</a>.</p><p>A sample alert follows:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/aws-rds-alert@730w.webp 730w, /images/2018/01/aws-rds-alert@730w2x.webp 1460w, /images/2018/01/aws-rds-alert@610w.webp 610w, /images/2018/01/aws-rds-alert@610w2x.webp 1220w, /images/2018/01/aws-rds-alert@450w.webp 450w, /images/2018/01/aws-rds-alert@450w2x.webp 900w, /images/2018/01/aws-rds-alert@330w.webp 330w, /images/2018/01/aws-rds-alert@330w2x.webp 660w, /images/2018/01/aws-rds-alert@545w.webp 545w, /images/2018/01/aws-rds-alert@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/aws-rds-alert@730w.png 730w, /images/2018/01/aws-rds-alert@730w2x.png 1460w, /images/2018/01/aws-rds-alert@610w.png 610w, /images/2018/01/aws-rds-alert@610w2x.png 1220w, /images/2018/01/aws-rds-alert@450w.png 450w, /images/2018/01/aws-rds-alert@450w2x.png 900w, /images/2018/01/aws-rds-alert@330w.png 330w, /images/2018/01/aws-rds-alert@330w2x.png 660w, /images/2018/01/aws-rds-alert@545w.png 545w, /images/2018/01/aws-rds-alert@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/aws-rds-alert.png" alt="marbot delivers RDS database instance failover alerts to Slack" title="marbot delivers RDS database instance failover alerts to Slack"></picture></p><h2 id="Other-sources"><a href="#Other-sources" class="headerlink" title="Other sources"></a>Other sources</h2><p>Besides metrics, RDS sends out <a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Events.html" target="_blank" rel="noopener">events</a> if the state of the database instance has changed. E.g., because of a Multi-AZ failover.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/rds-event-failover@730w.webp 730w, /images/2018/01/rds-event-failover@730w2x.webp 1460w, /images/2018/01/rds-event-failover@610w.webp 610w, /images/2018/01/rds-event-failover@610w2x.webp 1220w, /images/2018/01/rds-event-failover@450w.webp 450w, /images/2018/01/rds-event-failover@450w2x.webp 900w, /images/2018/01/rds-event-failover@330w.webp 330w, /images/2018/01/rds-event-failover@330w2x.webp 660w, /images/2018/01/rds-event-failover@545w.webp 545w, /images/2018/01/rds-event-failover@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/rds-event-failover@730w.png 730w, /images/2018/01/rds-event-failover@730w2x.png 1460w, /images/2018/01/rds-event-failover@610w.png 610w, /images/2018/01/rds-event-failover@610w2x.png 1220w, /images/2018/01/rds-event-failover@450w.png 450w, /images/2018/01/rds-event-failover@450w2x.png 900w, /images/2018/01/rds-event-failover@330w.png 330w, /images/2018/01/rds-event-failover@330w2x.png 660w, /images/2018/01/rds-event-failover@545w.png 545w, /images/2018/01/rds-event-failover@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/rds-event-failover.png" alt="RDS database instance failover event" title="RDS database instance failover event"></picture></p><p>We recommend to subscribe to events of the following categories:</p><ul><li>failover</li><li>failure</li><li>low storage</li><li>maintenance</li><li>notification</li><li>recovery</li></ul><h2 id="CloudFormation-template"><a href="#CloudFormation-template" class="headerlink" title="CloudFormation template"></a>CloudFormation template</h2><p>We developed a CloudFormation template to monitor an RDS database instance in any region. The template integrates with marbot, but you can modify it to send out emails. The template is available on <a href="https://github.com/marbot-io/monitoring-jump-start/blob/master/marbot-rds.yml" target="_blank" rel="noopener">GitHub</a> for free.</p><p>If you have already installed marbot, you can also ask marbot to monitor your RDS database or <a href="https://marbot.io/blog/monitoring-jump-start-amazon-rds-instance.html" target="_blank" rel="noopener">read more detailed setup instructions</a>. Otherwise: <a href="https://marbot.io/" target="_blank" rel="noopener">Try marbot for free now</a>.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>There are multiple options available to monitor an RDS database instance. Most importantly, CloudWatch Metrics and Alarms. But you should also not forget about RDS events. Otherwise, you miss events like failovers.</p><p>CloudWatch Alarms can trigger actions. The obvious choice is to send out an email if a metric exceeds a threshold. But we recommend not to use emails. Instead, use a tool like marbot. marbot comes with <a href="https://marbot.io/" target="_blank" rel="noopener">alert escalation, deduplication, and context-aware links to the AWS Management Console</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Monitoring a critical part of your infrastructure: Amazon ElastiCache memcached cluster</title>
      <link>https://cloudonaut.io/monitoring-a-critical-part-of-your-infrastructure-amazon-elasticache-memcached-cluster/</link>
      <description>
        <![CDATA[<p>In most of my projects where end-user latency is important, I usually add a caching layer to the architecture. The goal of a caching laye]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <category domain="https://cloudonaut.io/tag/elasticache/">elasticache</category>
      <category domain="https://cloudonaut.io/tag/memcached/">memcached</category>
      <guid isPermaLink="true">https://cloudonaut.io/monitoring-a-critical-part-of-your-infrastructure-amazon-elasticache-memcached-cluster/</guid>
      <pubDate>Mon, 22 Jan 2018 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>In most of my projects where end-user latency is important, I usually add a caching layer to the architecture. The goal of a caching layer is to reduce load from the database and the speed up the most popular data retrievals. In one project, I was asked to investigate an increase in latency over the last months. Just by looking at the CloudWatch metrics, I was discovering that the memcached cluster had a high number of evictions. An eviction occurs when a new item is added to the cluster that has not enough memory to store the new item. The cluster will remove old items to make room for the new item. So, the technical reason was: the cluster had not enough memory to hold all the cached data. Old items were evicted and therefore no longer cached which caused the increased latency for requests that wanted to access those no-longer-cached items. The business reason was that the number of users was growing. Three months before, the cache was large enough. But now, with twice the amount of users, the cache cluster is too small. Bottom line is: always monitor your critical components to get notified of such problems before they occur.</p><p>Amazon ElastiCache provides Redis and memcached as a Service. The fully managed service covers a lot of the challenges of operating an in-memory cache (e.g., cluster management, patching the operating system and the caching system, …). But you are still responsible for some operational aspects: sizing and performance optimizations. Therefore, you need to monitor every ElastiCache cluster that serves production workloads.</p><blockquote><p>This blog post covers memcached. Redis is not in scope.</p></blockquote><p>Monitoring your whole cloud infrastructure is a complex task, as Andreas pointed out in his <a href="/aws-monitoring-primer/">AWS Monitoring Primer</a>. In this blog post, I will focus on the relevant parts for monitoring your ElastiCache memcached cluster:</p><ol><li>I guide you to the relevant AWS monitoring services and features offered by AWS.</li><li>I present best practices based on real-world client projects.</li><li>I provide a CloudFormation template that implements all ideas in the post.</li><li>You can use the template to monitor any ElastiCache memcached cluster in a minute.</li></ol><p>Let’s get started!</p><h2 id="Identifying-important-CloudWatch-metrics"><a href="#Identifying-important-CloudWatch-metrics" class="headerlink" title="Identifying important CloudWatch metrics"></a>Identifying important CloudWatch metrics</h2><p>Each ElastiCache memcached cluster sends <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/elasticache-metricscollected.html" target="_blank" rel="noopener">metrics to CloudWatch</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/elasticache-event@730w.webp 730w, /images/2018/01/elasticache-event@730w2x.webp 1460w, /images/2018/01/elasticache-event@610w.webp 610w, /images/2018/01/elasticache-event@610w2x.webp 1220w, /images/2018/01/elasticache-event@450w.webp 450w, /images/2018/01/elasticache-event@450w2x.webp 900w, /images/2018/01/elasticache-event@330w.webp 330w, /images/2018/01/elasticache-event@330w2x.webp 660w, /images/2018/01/elasticache-event@545w.webp 545w, /images/2018/01/elasticache-event@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/elasticache-event@730w.png 730w, /images/2018/01/elasticache-event@730w2x.png 1460w, /images/2018/01/elasticache-event@610w.png 610w, /images/2018/01/elasticache-event@610w2x.png 1220w, /images/2018/01/elasticache-event@450w.png 450w, /images/2018/01/elasticache-event@450w2x.png 900w, /images/2018/01/elasticache-event@330w.png 330w, /images/2018/01/elasticache-event@330w2x.png 660w, /images/2018/01/elasticache-event@545w.png 545w, /images/2018/01/elasticache-event@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/elasticache-event.png" alt="CloudWatch metrics expose internals of the ElastiCache memcached cluster" title="CloudWatch metrics expose internals of the ElastiCache memcached cluster"></picture></p><p>The most important metrics are:</p><table class="table table-striped table-responsive"><thead><tr><th>area</th><th>metric</th><th>description</th><th>relevance</th></tr></thead><tbody><tr><td>CPU</td><td>CPUUtilization</td><td>The percentage of CPU utilization.</td><td>If the CPU is highly utilized, latency is added because computing tasks have to wait until they are scheduled.</td></tr><tr><td>Memory</td><td>Evictions</td><td>The number of non-expired items the cache evicted to allow space for new writes.</td><td>Items are evicted if you are running out of memory.</td></tr><tr><td>Memory</td><td>SwapUsage</td><td>The amount of swap used on the host in bytes.</td><td>If memory is moved to disk performance usually suffers.</td></tr></tbody></table><p>Once important metrics are identified, you can use them to understand how a healthy system differs from an impacted system.</p><h2 id="Defining-thresholds"><a href="#Defining-thresholds" class="headerlink" title="Defining thresholds"></a>Defining thresholds</h2><p>One of the hardest parts of monitoring is to define what healthy means. For each metric, you have to define a threshold between healthy and impacted. E.g., you regard CPU utilization under 80% as healthy because the application was never impacted when the CPU was not utilized. Thresholds are defined based on observations from the past. They might need adjustment in the future.</p><blockquote><p>We don’t know about the whole application here. We can only reason about one component: the cache. Application monitoring is a different topic. E.g., HTTP 5XX responses, latency, sign-ups.</p></blockquote><p>From our experience and the <a href="https://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/CacheMetrics.WhichShouldIMonitor.html" target="_blank" rel="noopener">AWS documentation</a>, we usually start with the following thresholds to identify unhealthy behavior and adjust them over time.</p><table class="table table-striped table-responsive"><thead><tr><th>area</th><th>metric</th><th>comparison operator</th><th>threshold</th><th>rationale</th></tr></thead><tbody><tr><td>CPU</td><td>CPUUtilization</td><td>&gt;</td><td>80 %</td><td>Queuing theory tells us the latency increases exponentially with utilization. In practice, we see higher latency when utilization exceeds 80% and unacceptable high latency with utilization above 90%.</td></tr><tr><td>Memory</td><td>Evictions</td><td>&gt;</td><td>1000</td><td>This number is calculated from our experience with ElastiCache workloads. 1000 evictions per second with an item size of 10 KB imply the cluster is releasing 10 MB of memory per second due to evictions.</td></tr><tr><td>Memory</td><td>SwapUsage</td><td>&gt;</td><td>256 MB</td><td>Sometimes you can not entirely avoid swapping. But once the cache accesses paged memory, it will slow down.</td></tr></tbody></table><p>Now you know what healthy&#x2F;unhealthy means. It’s time to define CloudWatch Alarms to send you an alert if a metric exceeds its threshold.</p><h2 id="Observing-metrics-with-CloudWatch-Alarms-and-marbot"><a href="#Observing-metrics-with-CloudWatch-Alarms-and-marbot" class="headerlink" title="Observing metrics with CloudWatch Alarms and marbot"></a>Observing metrics with CloudWatch Alarms and marbot</h2><p>A CloudWatch Alarm continuously watches a metric. Once the threshold is reached, an action is performed that sends a message to an SNS topic. From this topic, you can then send yourself an email. We found that emails are not a good way to handle alerts. In a team, multiple people are responsible. If you send an email to a group email address:</p><ol><li>Your team has no idea if someone already started to work on solving the issue.</li><li>You disturb the whole team for each alert.</li><li>It’s easy to ignore an email.</li><li>You have no statistics about how many alerts are generated. Too many alerts are an indication that your team is no longer able to handle them.</li><li>No help to investigate the issue is available, like links to the AWS Management Console.</li></ol><p>To solve the problem, we built <a href="https://marbot.io/" target="_blank" rel="noopener">marbot: a Slack chatbot that manages and escalates AWS alerts for you</a>.</p><p><img class="img-fluid" src="/images/2018/01/marbot-alert.gif" alt="marbot forwards alerts to Slack" title="marbot forwards alerts to Slack"></p><p>marbot sends alerts to a single user from the Slack channel via a direct message. If the user doesn’t acknowledge the alert within 5 minutes, marbot will escalate to the next level. Escalations minimize distraction while keeping response time low. <a href="https://marbot.io/" target="_blank" rel="noopener">Try marbot for free now</a>.</p><h2 id="Other-sources"><a href="#Other-sources" class="headerlink" title="Other sources"></a>Other sources</h2><p>Besides metrics, ElastiCache sends out <a href="https://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/ElastiCacheSNS.html" target="_blank" rel="noopener">notifications</a> if the state of the cache cluster has changed. E.g., because of a node failure. Unfortunately, you can not filter the types of notifications in ElastiCache. You have to filter them on your side as <a href="https://marbot.io/help/setup-integration-amazon-elasticache-notification.html" target="_blank" rel="noopener">marbot does</a>.</p><p>A sample alert follows:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/amazon-elasticache-alert@730w.webp 730w, /images/2018/01/amazon-elasticache-alert@730w2x.webp 1460w, /images/2018/01/amazon-elasticache-alert@610w.webp 610w, /images/2018/01/amazon-elasticache-alert@610w2x.webp 1220w, /images/2018/01/amazon-elasticache-alert@450w.webp 450w, /images/2018/01/amazon-elasticache-alert@450w2x.webp 900w, /images/2018/01/amazon-elasticache-alert@330w.webp 330w, /images/2018/01/amazon-elasticache-alert@330w2x.webp 660w, /images/2018/01/amazon-elasticache-alert@545w.webp 545w, /images/2018/01/amazon-elasticache-alert@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/amazon-elasticache-alert@730w.png 730w, /images/2018/01/amazon-elasticache-alert@730w2x.png 1460w, /images/2018/01/amazon-elasticache-alert@610w.png 610w, /images/2018/01/amazon-elasticache-alert@610w2x.png 1220w, /images/2018/01/amazon-elasticache-alert@450w.png 450w, /images/2018/01/amazon-elasticache-alert@450w2x.png 900w, /images/2018/01/amazon-elasticache-alert@330w.png 330w, /images/2018/01/amazon-elasticache-alert@330w2x.png 660w, /images/2018/01/amazon-elasticache-alert@545w.png 545w, /images/2018/01/amazon-elasticache-alert@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/amazon-elasticache-alert.png" alt="marbot delivers ElastiCache notifications to Slack" title="marbot delivers ElastiCache notifications to Slack"></picture></p><h2 id="CloudFormation-template"><a href="#CloudFormation-template" class="headerlink" title="CloudFormation template"></a>CloudFormation template</h2><p>We developed a CloudFormation template to monitor an ElastiCache memcached cluster in any region. The template integrates with marbot, but you can modify it to send out emails. The template is available on <a href="https://github.com/marbot-io/monitoring-jump-start/blob/master/marbot-elasticache-memcached.yml" target="_blank" rel="noopener">GitHub</a> for free.</p><p>If you have already installed marbot, you can also ask marbot to monitor your ElastiCache memcached cluster or <a href="https://marbot.io/blog/monitoring-jump-start-amazon-elasticache-memcached.html" target="_blank" rel="noopener">read more detailed setup instructions</a>. Otherwise: <a href="https://marbot.io/" target="_blank" rel="noopener">Try marbot for free now</a>.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>There are multiple options available to monitor an ElastiCache memcached cluster. Most importantly, CloudWatch Metrics and Alarms. But you should also not forget about ElastiCache notifications. Otherwise, you miss events like node failures.</p><p>CloudWatch Alarms can trigger actions. The obvious choice is to send out an email if a metric exceeds a threshold. But we recommend not to use emails. Instead, use a tool like marbot. marbot comes with <a href="https://marbot.io/" target="_blank" rel="noopener">alert escalation, deduplication, and context-aware links to the AWS Management Console</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Migrating to Amazon Linux 2</title>
      <link>https://cloudonaut.io/migrating-to-amazon-linux-2/</link>
      <description>
        <![CDATA[<p>I run all my EC2 workloads on Amazon Linux. It comes with a superb AWS integration, a secure default configuration, regular security upda]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <guid isPermaLink="true">https://cloudonaut.io/migrating-to-amazon-linux-2/</guid>
      <pubDate>Tue, 16 Jan 2018 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>I run all my EC2 workloads on Amazon Linux. It comes with a superb AWS integration, a secure default configuration, regular security updates, and I can open AWS Support tickets if I run into any problems.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/amazon-linux-2@730w.webp 730w, /images/2018/01/amazon-linux-2@730w2x.webp 1460w, /images/2018/01/amazon-linux-2@610w.webp 610w, /images/2018/01/amazon-linux-2@610w2x.webp 1220w, /images/2018/01/amazon-linux-2@450w.webp 450w, /images/2018/01/amazon-linux-2@450w2x.webp 900w, /images/2018/01/amazon-linux-2@330w.webp 330w, /images/2018/01/amazon-linux-2@330w2x.webp 660w, /images/2018/01/amazon-linux-2@545w.webp 545w, /images/2018/01/amazon-linux-2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/amazon-linux-2@730w.jpg 730w, /images/2018/01/amazon-linux-2@730w2x.jpg 1460w, /images/2018/01/amazon-linux-2@610w.jpg 610w, /images/2018/01/amazon-linux-2@610w2x.jpg 1220w, /images/2018/01/amazon-linux-2@450w.jpg 450w, /images/2018/01/amazon-linux-2@450w2x.jpg 900w, /images/2018/01/amazon-linux-2@330w.jpg 330w, /images/2018/01/amazon-linux-2@330w2x.jpg 660w, /images/2018/01/amazon-linux-2@545w.jpg 545w, /images/2018/01/amazon-linux-2@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/amazon-linux-2.jpg" alt="Migrating to Amazon Linux 2" title="Migrating to Amazon Linux 2"></picture></p><p>In late December 2017, AWS announced the successor of Amazon Linux: <strong>Amazon Linux 2</strong>. </p><p><a href="https://forums.aws.amazon.com/ann.jspa?annID=5653" target="_blank" rel="noopener">AWS also announced</a> that Amazon Linux 2018.03 is the last release for the current generation of Amazon Linux and will be supported until June 30, 2020. Therefore, you have to come up with a migration plan.</p><p>Amazon Linux 2 comes with the same benefits as Amazon Linux, but it adds some new capabilities:</p><ul><li>long-term support: Amazon Linux 2 supports each LTS release for five years</li><li>on-premises support: virtual machine images for on-premises development and testing are available</li><li>systemd: replacing SystemVinit</li><li>extras library: provides up-to-date versions of software bundles such as nginx</li></ul><p>Let’s dive into some of the changes in more detail. At the end of the post, I will also outline some pitfalls I encountered when migrating our <a href="https://github.com/widdix/aws-cf-templates/pull/145" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a> to Amazon Linux 2.</p><p>Further reading: <a href="https://aws.amazon.com/amazon-linux-2/release-notes/" target="_blank" rel="noopener">Release Notes</a>, <a href="https://aws.amazon.com/amazon-linux-2/faqs/" target="_blank" rel="noopener">FAQs</a>, <a href="https://aws.amazon.com/blogs/aws/amazon-linux-2-modern-stable-and-enterprise-friendly/" target="_blank" rel="noopener">AWS Blog Post</a>, <a href="https://aws.amazon.com/about-aws/whats-new/2017/12/introducing-amazon-linux-2" target="_blank" rel="noopener">Announcement</a></p><h2 id="Long-term-support"><a href="#Long-term-support" class="headerlink" title="Long-term support"></a>Long-term support</h2><p>The Amazon Linux delivers a continuous flow of updates that allow you to roll from one version of the Amazon Linux AMI to the most recent. A <code>yum update</code> always moves your system to the latest Amazon Linux version. There were no versions of Amazon Linux available, only snapshots.</p><p>Amazon Linux 2 changes this. You will have Amazon Linux 2 versions that are supplied with updates for five years. Once a new Amazon Linux 2 LTS release becomes available, no breaking changes will be introduced by AWS for this release.</p><h2 id="systemd"><a href="#systemd" class="headerlink" title="systemd"></a>systemd</h2><p>Amazon Linux uses <a href="https://en.wikipedia.org/wiki/Init" target="_blank" rel="noopener">SysVinit</a> to bootstrap the Linux user space and to manage system processes after booting. This procedure is usually called init. One of the major drawbacks of <code>SysVinit</code> is that it starts tasks serially, waiting for each to finish loading before moving on to the next. This can result in long delays during boot.</p><p>Amazon Linux 2 uses <a href="https://en.wikipedia.org/wiki/Systemd" target="_blank" rel="noopener">systemd</a> as the init system. <code>systemd</code> executes elements of its startup sequence in parallel, which is faster than the traditional serial approach from <code>SysVinit</code>. <code>systemd</code> can also ensure that a service is running (e.g., it restarts a service if it crashed).</p><p><code>systemd</code> is not just the name of the init system daemon but also refers to the entire software bundle around it, which includes:</p><ul><li><code>journald</code>: responsible for event logging (replaces syslog)</li><li><code>udevd</code>: device manager for the Linux kernel, which handles the &#x2F;dev directory and all user space actions when adding&#x2F;removing devices</li><li><code>logind</code>: manages user logins and seats in various ways.</li></ul><p>I will not cover <code>udevd</code> and <code>logind</code> in this post. You should not get in touch with them as a normal user like me. Keep in mind that networking configuration is not controlled by <code>networkd</code> (also part of <code>systemd</code> software bundle). Instead, networking configuration is controlled by <a href="https://cloudinit.readthedocs.io/" target="_blank" rel="noopener">cloud-init</a> which is triggered by <code>systemd</code> <a href="https://cloudinit.readthedocs.io/en/latest/topics/boot.html" target="_blank" rel="noopener">several times during boot</a>. <code>cloud-init</code> handles early initialization of an EC2 instance (also works with other vendors).</p><p>Further reading: <a href="https://www.freedesktop.org/software/systemd/man/systemd.html" target="_blank" rel="noopener">systemd man page</a></p><h3 id="Reading-logs-from-journald"><a href="#Reading-logs-from-journald" class="headerlink" title="Reading logs from journald"></a>Reading logs from journald</h3><p>To read all system logs (journal in journald terminology), starting with the oldest entry, run <code>journalctl</code>. The output is paged through <code>less</code> by default. Which means you can scroll down &#x2F; up an entry with the <code>DOWN</code> &#x2F; <code>UP</code> arrow keys, or scroll a full page down&#x2F;up with the <code>SPACE</code> &#x2F; <code>b</code> keys. Press the <code>q</code> key to quit. To reverse the order, run <code>journalctl -r</code>.</p><p>To show only the most recent journal entries, and continuously print new entries, run <code>journalctl -f</code> (like a <code>tail -f</code>).</p><p>There are many ways to filter the output. Based on priority, run <code>journalctl -p err</code> to get levels alert, crit, and err (using syslog log levels). Based on the unit, run <code>journalctl -u sshd</code> to get all entries for <code>sshd</code>. Check the further reading links for more information.</p><p>Keep in mind that some applications still write logs to <code>/var/log</code>. Journald also forwards logs to <code>rsyslog</code> which is configured (<code>/etc/rsyslog.conf</code>) to write some of them to files in <code>/var/log</code>.</p><p>Further reading: <a href="https://www.freedesktop.org/software/systemd/man/journalctl.html" target="_blank" rel="noopener">journalctl man page</a></p><h3 id="Controlling-systemd-services"><a href="#Controlling-systemd-services" class="headerlink" title="Controlling systemd services"></a>Controlling systemd services</h3><p>To start a service (unit in systemd terminology), you run:</p><figure class="highlight crmsh"><table><tr><td class="code"><pre><span class="line">systemctl <span class="literal">start</span> awslogsd.service</span><br></pre></td></tr></table></figure><p>To make sure a service (unit in systemd terminology) is started during boot&#x2F;reboot, you run:</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">systemctl <span class="built_in">enable</span> awslogsd.service</span><br></pre></td></tr></table></figure><p>There are many other commands. E.g., you can also reboot the system:</p><figure class="highlight nsis"><table><tr><td class="code"><pre><span class="line"><span class="params">system</span>ctl <span class="keyword">reboot</span></span><br></pre></td></tr></table></figure><p>Further reading: <a href="https://www.freedesktop.org/software/systemd/man/systemctl.html" target="_blank" rel="noopener">systemctl man page</a></p><h2 id="Extras-Library"><a href="#Extras-Library" class="headerlink" title="Extras Library"></a>Extras Library</h2><p>The Extras Library (aka Amazon Linux Extras Repository or Extras mechanism), provides a way to install up-to-date software bundles (topics in Amazon Linux 2 terminology) without impacting the stability of the rest of the operating system.</p><blockquote><p>Extras Library is not covered by LTS!</p></blockquote><p>To get a list of available topics, run:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ amazon-linux-extras list</span><br><span class="line">  0  ansible2                 available  [ =2.4.2 ]</span><br><span class="line">  1  emacs                    available  [ =25.3 ]</span><br><span class="line">  2  memcached1.5             available  [ =1.5.1 ]</span><br><span class="line">  3  nginx1.12                available  [ =1.12.2 ]</span><br><span class="line">  4  postgresql9.6            available  [ =9.6.6  =9.6.8 ]</span><br><span class="line">  5  python3                  available  [ =3.6.2 ]</span><br><span class="line">  6  redis4.0                 available  [ =4.0.5 ]</span><br><span class="line">  7  R3.4                     available  [ =3.4.3 ]</span><br><span class="line">  8  rust1                    available  [ =1.22.1  =1.26.0 ]</span><br><span class="line">  9  vim                      available  [ =8.0 ]</span><br><span class="line"> 10  golang1.9                available  [ =1.9.2 ]</span><br><span class="line"> 11  ruby2.4                  available  [ =2.4.2  =2.4.4 ]</span><br><span class="line"> 12  nano                     available  [ =2.9.1 ]</span><br><span class="line"> 13  php7.2                   available  [ =7.2.0  =7.2.4  =7.2.5 ]</span><br><span class="line"> 14  lamp-mariadb10.2-php7.2  available  [ =10.2.10_7.2.0  =10.2.10_7.2.4  =10.2.10_7.2.5 ]</span><br><span class="line"> 15  libreoffice              available  [ =5.0.6.2_15 ]</span><br><span class="line"> 16  gimp                     available  [ =2.8.22 ]</span><br><span class="line"> 17  docker=latest            enabled    [ =17.12.1  =18.03.1 ]</span><br><span class="line"> 18  mate-desktop1.x          available  [ =1.19.0  =1.20.0 ]</span><br><span class="line"> 19  GraphicsMagick1.3        available  [ =1.3.29 ]</span><br><span class="line"> 20  tomcat8.5                available  [ =8.5.31 ]</span><br></pre></td></tr></table></figure><p>To install an topic, run <code>amazon-linux-extras install &lt;topic&gt;</code> (e.g., <code>amazon-linux-extras install ruby2.4</code>).</p><p>If you install (or only enable) a topic, a new repository (plus two for sources and debuginfo) is configured in <code>/etc/yum.repos.d/amzn2-extras.repo</code>.</p><h2 id="Pitfalls"><a href="#Pitfalls" class="headerlink" title="Pitfalls"></a>Pitfalls</h2><p>I migrated <a href="https://github.com/widdix/aws-cf-templates/pull/145" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a> to Amazon Linux 2. In the following, I will outline the problems I was faced with and how I worked around them.</p><h3 id="The-awslogs-agent-was-renamed"><a href="#The-awslogs-agent-was-renamed" class="headerlink" title="The awslogs agent was renamed"></a>The awslogs agent was renamed</h3><p>The <code>awslogs</code> agent was renamed to <code>awslogsd</code> but you still install it via <code>yum install awslogs</code>.</p><p>You can start (activate in systemd terminology) awslogs with <code>systemctl start awslogsd.service</code> (shortcut: <code>systemctl start awslogsd</code>).</p><h3 id="The-awslogs-agent-does-not-support-journald"><a href="#The-awslogs-agent-does-not-support-journald" class="headerlink" title="The awslogs agent does not support journald"></a>The awslogs agent does not support journald</h3><p>awslogs agent cannot read logs directly from the journal. <code>journald</code> fowards all logs to <code>rsyslog</code> which is configured (<code>/etc/rsyslog.conf</code>) to write some of the logs to files in <code>/var/log</code> from where the awslogs agent can pick them up.</p><h3 id="Where-are-the-log-files"><a href="#Where-are-the-log-files" class="headerlink" title="Where are the log files?"></a>Where are the log files?</h3><p><code>/var/log</code> does not contain all system logs anymore.</p><p>If in doubt, you can access all system logs with <code>journalctl</code>.</p><h3 id="Ruby-is-missing"><a href="#Ruby-is-missing" class="headerlink" title="Ruby is missing"></a>Ruby is missing</h3><p>Ruby is no longer installed by default. This breaks <code>cfn-init</code> if you want to install RubyGems.</p><p>You can install Ruby 2.0 with <code>yum install ruby</code> or Ruby 2.4 with <code>amazon-linux-extras install ruby2.4</code>.</p><h3 id="netcat-is-missing"><a href="#netcat-is-missing" class="headerlink" title="netcat is missing"></a>netcat is missing</h3><p>netcat (or <code>nc</code>) is no longer installed by default.</p><p>You can install <code>ncat</code> with <code>yum install nmap-ncat</code>, but this will install nmap based ncat which behaves differently (e.g., no <code>-z</code> flag anymore). <a href="https://unix.stackexchange.com/questions/368155/what-are-the-differences-between-ncat-nc-and-netcat" target="_blank" rel="noopener">Learn more</a></p><h3 id="Nginx-package-not-available-by-default"><a href="#Nginx-package-not-available-by-default" class="headerlink" title="Nginx package not available by default"></a>Nginx package not available by default</h3><p><code>nginx</code> is no longer part of the default repository.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">$ yum install nginx</span><br><span class="line">Failed to <span class="built_in">set</span> locale, defaulting to C</span><br><span class="line">Loaded plugins: langpacks, update-motd</span><br><span class="line">No package nginx available.</span><br><span class="line">Error: Nothing to <span class="keyword">do</span></span><br></pre></td></tr></table></figure><p>To install nginx, use the new Amazon Linux Extras Repository <code>amazon-linux-extras install nginx1.12</code>.</p><h3 id="EPEL-repository-is-missing"><a href="#EPEL-repository-is-missing" class="headerlink" title="EPEL repository is missing"></a>EPEL repository is missing</h3><p>The EPEL repository (Extra Packages for Enterprise Linux) is no longer installed by default or available to install. The Extras Library replaces the EPEL repository but contains only a fraction of the packages which may causes troubles during your migration.</p><h3 id="NAT-and-ECS-optimized-AMIs-are-missing"><a href="#NAT-and-ECS-optimized-AMIs-are-missing" class="headerlink" title="NAT and ECS optimized AMIs are missing"></a>NAT and ECS optimized AMIs are missing</h3><p>NAT and ECS optimized AMI are not available. You can replace your NAT instances with NAT Gateways to get around this problem. But for ECS workloads there is no easy workaround. I advise waiting for news from AWS regarding the ECS optimized AMI.</p><h3 id="cfn-init-is-not-integrated-with-the-Extras-Library"><a href="#cfn-init-is-not-integrated-with-the-Extras-Library" class="headerlink" title="cfn-init is not integrated with the Extras Library"></a>cfn-init is not integrated with the Extras Library</h3><p>You can not install packages from the Extras Library with the package mechanism in <code>cfn-init</code> easily. <code>cfn-init</code> is the way how you can install software onto EC2 instances managed by CloudFormation.</p><p>There can either run <code>amazon-linux-extras enable &lt;topic&gt;</code> before running <code>cfn-init</code> which than can install the package by using the package mechanism. Or you can use two config sets. The first config sets uses the command mechanism to enable the topic. The second config set uses the package mechanism to install the enabled package. You have to use two config sets because commands run after package installation. Here is an example:</p><figure class="highlight nix"><table><tr><td class="code"><pre><span class="line"><span class="params">AutoScalingGroup:</span></span><br><span class="line">  <span class="params">Type:</span> &#x27;AWS::AutoScaling::AutoScalingGroup&#x27;</span><br><span class="line">  <span class="params">Properties:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line"><span class="params">LaunchConfiguration:</span></span><br><span class="line">  <span class="params">Type:</span> &#x27;AWS::AutoScaling::LaunchConfiguration&#x27;</span><br><span class="line">  <span class="params">Metadata:</span></span><br><span class="line">    &#x27;AWS::CloudFormation::<span class="params">Init&#x27;:</span></span><br><span class="line">      <span class="params">configSets:</span></span><br><span class="line">        <span class="params">default:</span> [extras, config]</span><br><span class="line">      <span class="params">extras:</span></span><br><span class="line">        <span class="params">commands:</span></span><br><span class="line">          <span class="params">a_enable_nginx:</span></span><br><span class="line">            <span class="params">command:</span> &#x27;amazon-linux-extras enable nginx1.<span class="number">12</span> <span class="operator">&amp;&amp;</span> yum <span class="operator">-</span>y clean metadata&#x27;</span><br><span class="line">            <span class="params">test:</span> <span class="string">&quot;[ ! grep -Fxq &#x27;[amzn2extra-nginx1.12]&#x27; /etc/yum.repos.d/amzn2-extras.repo ]&quot;</span></span><br><span class="line">      <span class="params">config:</span></span><br><span class="line">        <span class="params">packages:</span></span><br><span class="line">          <span class="params">yum:</span></span><br><span class="line">            <span class="params">nginx:</span> [] <span class="comment"># will install nginx1.12</span></span><br><span class="line">  <span class="params">Properties:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line">    <span class="params">UserData:</span></span><br><span class="line">      &#x27;Fn::<span class="params">Base64&#x27;:</span> <span class="operator">!</span>Sub |</span><br><span class="line">        <span class="comment">#!/bin/bash -x</span></span><br><span class="line">        <span class="symbol">/opt/aws/bin/cfn-init</span> <span class="operator">-</span>v <span class="operator">-</span>-stack $&#123;AWS::StackName&#125; <span class="operator">-</span>-resource LaunchConfiguration <span class="operator">-</span>-region $&#123;AWS::Region&#125;</span><br><span class="line">        <span class="symbol">/opt/aws/bin/cfn-signal</span> <span class="operator">-</span>e $<span class="operator">?</span> <span class="operator">-</span>-stack $&#123;AWS::StackName&#125; <span class="operator">-</span>-resource AutoScalingGroup <span class="operator">-</span>-region $&#123;AWS::Region&#125;</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Amazon Linux 2 is the new default for running Linux workloads on AWS. Amazon Linux 2 benefits from systemd, LTS, and a new extras library. There are a few pain points when migrating, most notably the missing EPEL repository. Besides that, you should spend some time to understand how systemd works, because that’s central in modern Linux operating systems.</p><p>It’s time to plan your migration from Amazon Linux now!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Monitoring Primer</title>
      <link>https://cloudonaut.io/aws-monitoring-primer/</link>
      <description>
        <![CDATA[<p>Monitoring is critical for a secure, high-performing, resilient, and efficient cloud infrastructure. This blog post summarizes all the bi]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-monitoring-primer/</guid>
      <pubDate>Thu, 11 Jan 2018 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Monitoring is critical for a secure, high-performing, resilient, and efficient cloud infrastructure. This blog post summarizes all the bits and pieces you need to think of when monitoring your AWS account.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/monitoring@730w.webp 730w, /images/2018/01/monitoring@730w2x.webp 1460w, /images/2018/01/monitoring@610w.webp 610w, /images/2018/01/monitoring@610w2x.webp 1220w, /images/2018/01/monitoring@450w.webp 450w, /images/2018/01/monitoring@450w2x.webp 900w, /images/2018/01/monitoring@330w.webp 330w, /images/2018/01/monitoring@330w2x.webp 660w, /images/2018/01/monitoring@545w.webp 545w, /images/2018/01/monitoring@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/monitoring@730w.jpg 730w, /images/2018/01/monitoring@730w2x.jpg 1460w, /images/2018/01/monitoring@610w.jpg 610w, /images/2018/01/monitoring@610w2x.jpg 1220w, /images/2018/01/monitoring@450w.jpg 450w, /images/2018/01/monitoring@450w2x.jpg 900w, /images/2018/01/monitoring@330w.jpg 330w, /images/2018/01/monitoring@330w2x.jpg 660w, /images/2018/01/monitoring@545w.jpg 545w, /images/2018/01/monitoring@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/monitoring.jpg" alt="Monitoring" title="Monitoring"></picture></p><h2 id="Overview"><a href="#Overview" class="headerlink" title="Overview"></a>Overview</h2><p>The following mind map provides an overview of monitoring goals as well as all services and features related to monitoring:</p><ul><li><strong>Amazon CloudWatch Events</strong>: subscribe to notifications from your AWS infrastructure.</li><li><strong>Service Specific Events</strong>: subscribe to notifications from specific services (partially legacy). </li><li><strong>Amazon CloudWatch Metrics</strong>: get insights into the utilization and condition of your resources.</li><li><strong>Amazon CloudWatch Alarms</strong>: define alarms based on metrics to get notified about incidents or resolve problems automatically.</li><li><strong>Logging</strong>: search and analyze logs from AWS services or your applications.</li><li><strong>Amazon Simple Notification Service (SNS)</strong>: deliver notifications from your AWS infrastructure to responsible persons or teams.</li><li><strong>Dashboards</strong>: get an overview of your system’s status.</li></ul><p>Note that I will not cover any 3rd party monitoring solutions in this blog post.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/aws-monitoring-primer-overview@730w.webp 730w, /images/2018/01/aws-monitoring-primer-overview@730w2x.webp 1460w, /images/2018/01/aws-monitoring-primer-overview@610w.webp 610w, /images/2018/01/aws-monitoring-primer-overview@610w2x.webp 1220w, /images/2018/01/aws-monitoring-primer-overview@450w.webp 450w, /images/2018/01/aws-monitoring-primer-overview@450w2x.webp 900w, /images/2018/01/aws-monitoring-primer-overview@330w.webp 330w, /images/2018/01/aws-monitoring-primer-overview@330w2x.webp 660w, /images/2018/01/aws-monitoring-primer-overview@545w.webp 545w, /images/2018/01/aws-monitoring-primer-overview@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/aws-monitoring-primer-overview@730w.png 730w, /images/2018/01/aws-monitoring-primer-overview@730w2x.png 1460w, /images/2018/01/aws-monitoring-primer-overview@610w.png 610w, /images/2018/01/aws-monitoring-primer-overview@610w2x.png 1220w, /images/2018/01/aws-monitoring-primer-overview@450w.png 450w, /images/2018/01/aws-monitoring-primer-overview@450w2x.png 900w, /images/2018/01/aws-monitoring-primer-overview@330w.png 330w, /images/2018/01/aws-monitoring-primer-overview@330w2x.png 660w, /images/2018/01/aws-monitoring-primer-overview@545w.png 545w, /images/2018/01/aws-monitoring-primer-overview@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/aws-monitoring-primer-overview.png" alt="AWS Monitoring Primer Overview Mind Map" title="AWS Monitoring Primer Overview Mind Map"></picture></p><p>And you thought monitoring your AWS infrastructure is easy?</p><h2 id="Amazon-CloudWatch-Events"><a href="#Amazon-CloudWatch-Events" class="headerlink" title="Amazon CloudWatch Events"></a>Amazon CloudWatch Events</h2><p>Each CloudWatch event indicates an operational change in your AWS account. More and more services are publishing CloudWatch events as shown in the following excerpt of the mind map. A rule defines a filter and routes events to a target. Supported targets are Amazon SNS, AWS Lambda, Amazon SQS, and many more. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/aws-monitoring-primer-cloudwatch-events@730w.webp 730w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@730w2x.webp 1460w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@610w.webp 610w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@610w2x.webp 1220w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@450w.webp 450w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@450w2x.webp 900w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@330w.webp 330w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@330w2x.webp 660w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@545w.webp 545w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/aws-monitoring-primer-cloudwatch-events@730w.png 730w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@730w2x.png 1460w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@610w.png 610w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@610w2x.png 1220w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@450w.png 450w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@450w2x.png 900w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@330w.png 330w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@330w2x.png 660w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@545w.png 545w, /images/2018/01/aws-monitoring-primer-cloudwatch-events@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/aws-monitoring-primer-cloudwatch-events.png" alt="CloudWatch Events" title="CloudWatch Events"></picture></p><p>A few examples of how we use CloudWatch events for monitoring.</p><ul><li>When AWS needs to reboot virtual machines an <strong>AWS Health Event</strong> is published to announce the reboot in advance.</li><li>When someone tries to log into the AWS Management Console as a root user a <strong>AWS Console Sign In via CloudTrail</strong> event is published.</li><li>When a deployment step fails <strong>CodePipline</strong> creates an event.</li></ul><p>Next, we will have a look at service specific events.</p><h2 id="Service-Specific-Events"><a href="#Service-Specific-Events" class="headerlink" title="Service Specific Events"></a>Service Specific Events</h2><p>In addition to CloudWatch events, some services publish service specific events. Typically, service-specific events are published to Amazon SNS or sent via email. The following figure shows an overview of service-specific events.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/aws-monitoring-primer-service-specific-events@730w.webp 730w, /images/2018/01/aws-monitoring-primer-service-specific-events@730w2x.webp 1460w, /images/2018/01/aws-monitoring-primer-service-specific-events@610w.webp 610w, /images/2018/01/aws-monitoring-primer-service-specific-events@610w2x.webp 1220w, /images/2018/01/aws-monitoring-primer-service-specific-events@450w.webp 450w, /images/2018/01/aws-monitoring-primer-service-specific-events@450w2x.webp 900w, /images/2018/01/aws-monitoring-primer-service-specific-events@330w.webp 330w, /images/2018/01/aws-monitoring-primer-service-specific-events@330w2x.webp 660w, /images/2018/01/aws-monitoring-primer-service-specific-events@545w.webp 545w, /images/2018/01/aws-monitoring-primer-service-specific-events@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/aws-monitoring-primer-service-specific-events@730w.png 730w, /images/2018/01/aws-monitoring-primer-service-specific-events@730w2x.png 1460w, /images/2018/01/aws-monitoring-primer-service-specific-events@610w.png 610w, /images/2018/01/aws-monitoring-primer-service-specific-events@610w2x.png 1220w, /images/2018/01/aws-monitoring-primer-service-specific-events@450w.png 450w, /images/2018/01/aws-monitoring-primer-service-specific-events@450w2x.png 900w, /images/2018/01/aws-monitoring-primer-service-specific-events@330w.png 330w, /images/2018/01/aws-monitoring-primer-service-specific-events@330w2x.png 660w, /images/2018/01/aws-monitoring-primer-service-specific-events@545w.png 545w, /images/2018/01/aws-monitoring-primer-service-specific-events@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/aws-monitoring-primer-service-specific-events.png" alt="Service Specific Events" title="Service Specific Events"></picture></p><p>A few examples of how we use service-specific events for monitoring.</p><ul><li>We use <strong>AWS Budget Notifications</strong> to monitor current and forecasted costs of an AWS account.</li><li>We subscribe to <strong>Amazon RDS events</strong> to get notified about maintenance windows or issues with our database instances.</li><li>We configure <strong>AWS Trusted Advisor</strong> to send security, performance, cost savings and reliability advice via email.</li></ul><p>The next step is to use CloudWatch metrics to get insights into the utilization and condition of your resources.</p><h2 id="Amazon-CloudWatch-Metrics"><a href="#Amazon-CloudWatch-Metrics" class="headerlink" title="Amazon CloudWatch Metrics"></a>Amazon CloudWatch Metrics</h2><p>Almost every AWS resource sends metrics to CloudWatch allowing us to look into the black boxes also known as EC2, S3, RDS, and so on. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/aws-monitoring-primer-cloudwatch-metrics@730w.webp 730w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@730w2x.webp 1460w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@610w.webp 610w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@610w2x.webp 1220w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@450w.webp 450w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@450w2x.webp 900w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@330w.webp 330w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@330w2x.webp 660w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@545w.webp 545w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/aws-monitoring-primer-cloudwatch-metrics@730w.png 730w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@730w2x.png 1460w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@610w.png 610w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@610w2x.png 1220w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@450w.png 450w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@450w2x.png 900w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@330w.png 330w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@330w2x.png 660w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@545w.png 545w, /images/2018/01/aws-monitoring-primer-cloudwatch-metrics@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/aws-monitoring-primer-cloudwatch-metrics.png" alt="CloudWatch Metrics" title="CloudWatch Metrics"></picture></p><p>Discussing every metric available through CloudWatch is out of scope for this blog post. Have a look at <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CW_Support_For_AWS.html" target="_blank" rel="noopener">Amazon CloudWatch Metrics and Dimensions Reference</a> if you want to dive into the details.</p><p>We usually have an eye for the following metrics.</p><ul><li><strong>Utilization of limited resources</strong> for EC2, RDS, and similar services: CPU, memory, storage, and network.</li><li><strong>Error counters</strong> like HTTP errors on ALB or failed Lambda invocations.</li><li><strong>Cost-related metrics</strong> like the estimated charges of the account or the consumed LCUs of an ALB.</li></ul><p>Typically, you don’t want to look at metrics all day long. That’s why you define alarms on metrics to get notified if a metric reaches an unwanted threshold.</p><h2 id="Amazon-CloudWatch-Alarms"><a href="#Amazon-CloudWatch-Alarms" class="headerlink" title="Amazon CloudWatch Alarms"></a>Amazon CloudWatch Alarms</h2><p>A CloudWatch alarm defines a threshold on a CloudWatch metric. For example to get notified if the CPU utilization of an EC2 instance increases 80% on average over a period of 5 minutes. Besides sending notifications via SNS, an alarm can also trigger actions to resolve a problem automatically like recovering an EC2 instance.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/aws-monitoring-primer-cloudwatch-alarms@730w.webp 730w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@730w2x.webp 1460w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@610w.webp 610w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@610w2x.webp 1220w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@450w.webp 450w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@450w2x.webp 900w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@330w.webp 330w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@330w2x.webp 660w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@545w.webp 545w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/aws-monitoring-primer-cloudwatch-alarms@730w.png 730w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@730w2x.png 1460w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@610w.png 610w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@610w2x.png 1220w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@450w.png 450w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@450w2x.png 900w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@330w.png 330w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@330w2x.png 660w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@545w.png 545w, /images/2018/01/aws-monitoring-primer-cloudwatch-alarms@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/aws-monitoring-primer-cloudwatch-alarms.png" alt="CloudWatch Alarms" title="CloudWatch Alarms"></picture></p><p>We usually use CloudWatch alarms to monitor metrics indicating users of our systems do have a bad experience or our system approaches a problem which it cannot resolve itself.</p><ul><li>99% of the requests processed by the load balancer are not answered within 500 ms.</li><li>The load balancer answers request with 500 error code.</li><li>Only 1 GB storage left on the RDS instance.</li></ul><h2 id="Logging"><a href="#Logging" class="headerlink" title="Logging"></a>Logging</h2><p>There are various sources producing log messages within your cloud infrastructure. Analyzing and monitoring all the different kinds of log messages allow you to increase security, reliability, and performance.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/aws-monitoring-primer-logging@730w.webp 730w, /images/2018/01/aws-monitoring-primer-logging@730w2x.webp 1460w, /images/2018/01/aws-monitoring-primer-logging@610w.webp 610w, /images/2018/01/aws-monitoring-primer-logging@610w2x.webp 1220w, /images/2018/01/aws-monitoring-primer-logging@450w.webp 450w, /images/2018/01/aws-monitoring-primer-logging@450w2x.webp 900w, /images/2018/01/aws-monitoring-primer-logging@330w.webp 330w, /images/2018/01/aws-monitoring-primer-logging@330w2x.webp 660w, /images/2018/01/aws-monitoring-primer-logging@545w.webp 545w, /images/2018/01/aws-monitoring-primer-logging@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/aws-monitoring-primer-logging@730w.png 730w, /images/2018/01/aws-monitoring-primer-logging@730w2x.png 1460w, /images/2018/01/aws-monitoring-primer-logging@610w.png 610w, /images/2018/01/aws-monitoring-primer-logging@610w2x.png 1220w, /images/2018/01/aws-monitoring-primer-logging@450w.png 450w, /images/2018/01/aws-monitoring-primer-logging@450w2x.png 900w, /images/2018/01/aws-monitoring-primer-logging@330w.png 330w, /images/2018/01/aws-monitoring-primer-logging@330w2x.png 660w, /images/2018/01/aws-monitoring-primer-logging@545w.png 545w, /images/2018/01/aws-monitoring-primer-logging@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/aws-monitoring-primer-logging.png" alt="Logging" title="Logging"></picture></p><p>You should have a look at the following log message sources:</p><ul><li>Your application logs information about the processed requests or error fault conditions. Use <strong>Amazon CloudWatch Logs</strong> or <strong>Amazon Elasticsearch Service</strong> to collect log messages centralized.</li><li>The <strong>VPC Flow Logs</strong> contain aggregated information about all packages flowing through your network. Very helpful to debug issues with your firewall configuration or to detect malicious activities.</li><li><strong>AWS CloudTrail</strong> provides a global audit log of your AWS account, including all AWS API requests modifying your cloud infrastructure. The go-to-source for security and compliance geeks.</li></ul><p>Using filters allows you to watch for specific log messages and send alerts automatically. For example, notify all developers whenever the application throws an unresolvable exception.</p><h2 id="Amazon-Simple-Notification-Service-SNS"><a href="#Amazon-Simple-Notification-Service-SNS" class="headerlink" title="Amazon Simple Notification Service (SNS)"></a>Amazon Simple Notification Service (SNS)</h2><p>Almost all discussed sources for alerts use Amazon Simple Notification Service (SNS) to deliver notifications.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/aws-monitoring-primer-sns@730w.webp 730w, /images/2018/01/aws-monitoring-primer-sns@730w2x.webp 1460w, /images/2018/01/aws-monitoring-primer-sns@610w.webp 610w, /images/2018/01/aws-monitoring-primer-sns@610w2x.webp 1220w, /images/2018/01/aws-monitoring-primer-sns@450w.webp 450w, /images/2018/01/aws-monitoring-primer-sns@450w2x.webp 900w, /images/2018/01/aws-monitoring-primer-sns@330w.webp 330w, /images/2018/01/aws-monitoring-primer-sns@330w2x.webp 660w, /images/2018/01/aws-monitoring-primer-sns@545w.webp 545w, /images/2018/01/aws-monitoring-primer-sns@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/aws-monitoring-primer-sns@730w.png 730w, /images/2018/01/aws-monitoring-primer-sns@730w2x.png 1460w, /images/2018/01/aws-monitoring-primer-sns@610w.png 610w, /images/2018/01/aws-monitoring-primer-sns@610w2x.png 1220w, /images/2018/01/aws-monitoring-primer-sns@450w.png 450w, /images/2018/01/aws-monitoring-primer-sns@450w2x.png 900w, /images/2018/01/aws-monitoring-primer-sns@330w.png 330w, /images/2018/01/aws-monitoring-primer-sns@330w2x.png 660w, /images/2018/01/aws-monitoring-primer-sns@545w.png 545w, /images/2018/01/aws-monitoring-primer-sns@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/aws-monitoring-primer-sns.png" alt="Amazon Simple Notification Service (SNS)" title="Amazon Simple Notification Service (SNS)"></picture></p><p>Popular options to deliver your notifications with SNS are:</p><ul><li><strong>email</strong>: rough but simple way to send alerts and notifications to individuals or a whole group.</li><li><strong>HTTP&#x2F;HTTPS</strong>: integrate with Incident Management Tools (e.g., OpsGenie, pagerduty, or <a href="https://marbot.io/" target="_blank" rel="noopener">marbot</a>).</li><li><strong>AWS Lambda</strong>: execute a small function, e.g., to create a Jira via the Atlassian API.</li></ul><p>Sometimes it is not necessary to send an alert and wake up a teammate. Dashboards show monitoring information casually.</p><h2 id="Dashboards"><a href="#Dashboards" class="headerlink" title="Dashboards"></a>Dashboards</h2><p>A dashboard is a tool to provide a quick overview of the system’s status. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/aws-monitoring-primer-dashboards@730w.webp 730w, /images/2018/01/aws-monitoring-primer-dashboards@730w2x.webp 1460w, /images/2018/01/aws-monitoring-primer-dashboards@610w.webp 610w, /images/2018/01/aws-monitoring-primer-dashboards@610w2x.webp 1220w, /images/2018/01/aws-monitoring-primer-dashboards@450w.webp 450w, /images/2018/01/aws-monitoring-primer-dashboards@450w2x.webp 900w, /images/2018/01/aws-monitoring-primer-dashboards@330w.webp 330w, /images/2018/01/aws-monitoring-primer-dashboards@330w2x.webp 660w, /images/2018/01/aws-monitoring-primer-dashboards@545w.webp 545w, /images/2018/01/aws-monitoring-primer-dashboards@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/aws-monitoring-primer-dashboards@730w.png 730w, /images/2018/01/aws-monitoring-primer-dashboards@730w2x.png 1460w, /images/2018/01/aws-monitoring-primer-dashboards@610w.png 610w, /images/2018/01/aws-monitoring-primer-dashboards@610w2x.png 1220w, /images/2018/01/aws-monitoring-primer-dashboards@450w.png 450w, /images/2018/01/aws-monitoring-primer-dashboards@450w2x.png 900w, /images/2018/01/aws-monitoring-primer-dashboards@330w.png 330w, /images/2018/01/aws-monitoring-primer-dashboards@330w2x.png 660w, /images/2018/01/aws-monitoring-primer-dashboards@545w.png 545w, /images/2018/01/aws-monitoring-primer-dashboards@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/aws-monitoring-primer-dashboards.png" alt="Dashboards" title="Dashboards"></picture></p><p>We use <strong>CloudWatch Dashboards</strong> to group CloudWatch metrics for different scenarios. A few examples:</p><ul><li>A dashboard to present all metrics indicating the state of the EC2 instances belonging to an ECS cluster.</li><li>A dashboard showing all metrics indicating the state of a microservice.</li><li>A dashboard displaying metrics indicating the resource consumption and occurring costs.</li></ul><p>By the way, you should use AWS CloudFormation to define your dashboards in code instead of clicking through the AWS Management Console.</p><p>Use the <strong>AWS Personal Health Dashboard</strong> to get an overview of AWS services are suffering from any issues.</p><p><em>Thanks to <a href="https://x.com/@beratdgan" target="_blank" rel="noopener">@beratdgan</a>, <a href="https://x.com/@nfonrose" target="_blank" rel="noopener">@nfonrose</a>, <a href="https://x.com/@edyesed" target="_blank" rel="noopener">@edyesed</a>, <a href="https://x.com/@Cool_Sops" target="_blank" rel="noopener">@Cool_Sops</a>, <a href="https://x.com/@moritzonken" target="_blank" rel="noopener">@moritzonken</a>, and <a href="https://x.com/@derBroBro" target="_blank" rel="noopener">@derBroBro</a> for their feedback on the mind map.</em></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>EC2 Network Performance Cheat Sheet</title>
      <link>https://cloudonaut.io/ec2-network-performance-cheat-sheet/</link>
      <description>
        <![CDATA[<p>What is the maximum network throughput of your EC2 instance? The answer to this question is key to choosing the type of an instance or de]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <guid isPermaLink="true">https://cloudonaut.io/ec2-network-performance-cheat-sheet/</guid>
      <pubDate>Mon, 08 Jan 2018 13:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>What is the maximum network throughput of your EC2 instance? The answer to this question is key to choosing the type of an instance or defining monitoring alerts on network throughput.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2018/01/network@730w.webp 730w, /images/2018/01/network@730w2x.webp 1460w, /images/2018/01/network@610w.webp 610w, /images/2018/01/network@610w2x.webp 1220w, /images/2018/01/network@450w.webp 450w, /images/2018/01/network@450w2x.webp 900w, /images/2018/01/network@330w.webp 330w, /images/2018/01/network@330w2x.webp 660w, /images/2018/01/network@545w.webp 545w, /images/2018/01/network@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2018/01/network@730w.jpg 730w, /images/2018/01/network@730w2x.jpg 1460w, /images/2018/01/network@610w.jpg 610w, /images/2018/01/network@610w2x.jpg 1220w, /images/2018/01/network@450w.jpg 450w, /images/2018/01/network@450w2x.jpg 900w, /images/2018/01/network@330w.jpg 330w, /images/2018/01/network@330w2x.jpg 660w, /images/2018/01/network@545w.jpg 545w, /images/2018/01/network@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2018/01/network.jpg" alt="EC2 Network Performance Cheat Sheet" title="EC2 Network Performance Cheat Sheet"></picture></p><p>Unfortunately, you will only find very vague information about the networking capabilities of EC2 instances within AWS’s service description and documentation. That is why I run a network performance benchmark for almost all EC2 instance types within the last few days. The results are compiled into the following cheat sheet.</p><p>The data analysis in the following table shows the burst and baseline throughput of each instance type. The burst throughput is defined as the 95th percentile, which means the throughput was at least reached for 3 minutes. The baseline throughput is defined as the 10th percentile, which means the throughput was reached at least for 54 minutes.</p><table class="table">  <thead>    <tr>      <th>INSTANCE TYPE</th>      <th class="text-right">Baseline (Gbit/s)</th>      <th class="text-right">Burst (Gbit/s)</th>    </tr>  </thead>  <tbody>    <tr>      <td>m5.large</td>      <td class="text-right">0.74</td>      <td class="text-right">10.04</td>    </tr>    <tr>      <td>m5.xlarge</td>      <td class="text-right">1.24</td>      <td class="text-right">10.04</td>    </tr>    <tr>      <td>m5.2xlarge</td>      <td class="text-right">2.49</td>      <td class="text-right">10.04</td>    </tr>    <tr>      <td>m5.4xlarge</td>      <td class="text-right">4.97</td>      <td class="text-right">10.04</td>    </tr>    <tr>      <td>m5.12xlarge</td>      <td class="text-right">10.04</td>      <td class="text-right"></td>    </tr>    <tr>      <td>m5.24xlarge</td>      <td class="text-right">21.49</td>      <td class="text-right"></td>    </tr>    <tr>      <td>t3.nano</td>      <td class="text-right">0.03</td>      <td class="text-right">5.06</td>    </tr>    <tr>      <td>t3.micro</td>      <td class="text-right">0.06</td>      <td class="text-right">5.09</td>    </tr>    <tr>      <td>t3.small</td>      <td class="text-right">0.13</td>      <td class="text-right">5.11</td>    </tr>    <tr>      <td>t3.medium</td>      <td class="text-right">0.25</td>      <td class="text-right">4.98</td>    </tr>    <tr>      <td>t3.large</td>      <td class="text-right">0.51</td>      <td class="text-right">5.11</td>    </tr>    <tr>      <td>t3.xlarge</td>      <td class="text-right">1.02</td>      <td class="text-right">5.11</td>    </tr>    <tr>      <td>t3.2xlarge</td>      <td class="text-right">2.04</td>      <td class="text-right">5.11</td>    </tr>    <tr>      <td colspan="3">Additional instance types can be found in the download below.</td>    </tr>  </tbody></table><p>Download the free <a href="/download/files/ec2-network-performance-cheat-sheet.xlsx"><strong>EC2 Network Performance Cheat Sheet</strong></a> (XLSX format).</p><h2 id="Methodology"><a href="#Methodology" class="headerlink" title="Methodology"></a>Methodology</h2><p>I have run the network performance benchmark in <code>us-east-1</code> on April 12th, 2018 using <code>iperf3</code>. Each benchmark run took 60 minutes, and a data point was recorded every minute. A fresh <code>c5.18xlarge</code> was used as the counterparty for the network benchmark of each instance type. I have published the source code of the network benchmark at <a href="https://github.com/widdix/ec2-network-benchmark" target="_blank" rel="noopener">widdix&#x2F;ec2-network-benchmark</a>.</p><p>The benchmark measured the network performance of the following instance types.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">c4</span>.large, c4.xlarge, c4.<span class="number">2</span>xlarge, c4.<span class="number">4</span>xlarge, c4.<span class="number">8</span>xlarge</span><br><span class="line"><span class="attribute">c5</span>.large, c5.xlarge, c5.<span class="number">2</span>xlarge, c5.<span class="number">4</span>xlarge, c5.<span class="number">9</span>xlarge, c5.<span class="number">18</span>xlarge</span><br><span class="line"><span class="attribute">d2</span>.xlarge, d2.<span class="number">2</span>xlarge, d2.<span class="number">4</span>xlarge, d2.<span class="number">8</span>xlarge</span><br><span class="line"><span class="attribute">g3</span>.<span class="number">4</span>xlarge, g3.<span class="number">8</span>xlarge, g3.<span class="number">16</span>xlarge</span><br><span class="line"><span class="attribute">h1</span>.<span class="number">2</span>xlarge, h1.<span class="number">4</span>xlarge, h1.<span class="number">8</span>xlarge, h1.<span class="number">16</span>xlarge</span><br><span class="line"><span class="attribute">i3</span>.large, i3.xlarge, i3.<span class="number">2</span>xlarge, i3.<span class="number">4</span>xlarge, i3.<span class="number">8</span>xlarge, i3.<span class="number">16</span>xlarge, i3.metal</span><br><span class="line"><span class="attribute">m3</span>.medium, m3.large, m3.xlarge, m3.<span class="number">2</span>xlarge</span><br><span class="line"><span class="attribute">m4</span>.large, m4.xlarge, m4.<span class="number">2</span>xlarge, m4.<span class="number">4</span>xlarge, m4.<span class="number">10</span>xlarge, m4.<span class="number">16</span>xlarge</span><br><span class="line"><span class="attribute">m5</span>.large, m5.xlarge, m5.<span class="number">2</span>xlarge, m5.<span class="number">4</span>xlarge, m5.<span class="number">12</span>xlarge, m5.<span class="number">24</span>xlarge</span><br><span class="line"><span class="attribute">p2</span>.xlarge, p2.<span class="number">8</span>xlarge, p2.<span class="number">16</span>xlarge, p3.<span class="number">2</span>xlarge, p3.<span class="number">8</span>xlarge, p3.<span class="number">16</span>xlarge</span><br><span class="line"><span class="attribute">r3</span>.large, r3.xlarge, r3.<span class="number">2</span>xlarge, r3.<span class="number">4</span>xlarge, r3.<span class="number">8</span>xlarge</span><br><span class="line"><span class="attribute">r4</span>.large, r4.xlarge, r4.<span class="number">2</span>xlarge, r4.<span class="number">4</span>xlarge, r4.<span class="number">8</span>xlarge, r4.<span class="number">16</span>xlarge</span><br><span class="line"><span class="attribute">r5</span>.large, r5.xlarge, r5.<span class="number">2</span>xlarge, r5.<span class="number">4</span>xlarge, r5.<span class="number">12</span>xlarge, r5.<span class="number">24</span>xlarge</span><br><span class="line"><span class="attribute">t1</span>.micro</span><br><span class="line"><span class="attribute">t2</span>.nano, t2.micro, t2.small, t2.medium, t2.large, t2.xlarge, t2.<span class="number">2</span>xlarge</span><br><span class="line"><span class="attribute">t3</span>.nano, t3.micro, t3.small, t3.medium, t3.large, t3.xlarge, t3.<span class="number">2</span>xlarge</span><br><span class="line"><span class="attribute">x1</span>.<span class="number">16</span>xlarge, x1.<span class="number">32</span>xlarge, x1e.xlarge, x1e.<span class="number">2</span>xlarge, x1e.<span class="number">4</span>xlarge, x1e.<span class="number">8</span>xlarge, x1e.<span class="number">16</span>xlarge, x1e.<span class="number">32</span>xlarge</span><br><span class="line"><span class="attribute">z1d</span>.large, z1d.xlarge, z1d.<span class="number">2</span>xlarge, z1d.<span class="number">3</span>xlarge, z1d.<span class="number">6</span>xlarge, z1d.<span class="number">12</span>xlarge</span><br></pre></td></tr></table></figure><p>Update: I run the network performance benchmark for <code>t3.*</code>, <code>i3.metal</code>, and <code>z1d.*</code> at September 25th, 2018.</p><h2 id="Disclaimer"><a href="#Disclaimer" class="headerlink" title="Disclaimer"></a>Disclaimer</h2><p>Please note, that the network benchmark was just a random sample and does not claim to be 100% accurate. Therefore, you should treat the EC2 Network Performance Cheat Sheet as the first guide when choosing an instance type based on your network throughput needs. If network throughput is critical to your infrastructure, you should run network benchmarks yourself to verify my results.</p><h2 id="One-more-thing"><a href="#One-more-thing" class="headerlink" title="One more thing"></a>One more thing</h2><p>Read on and learn how to <a href="https://marbot.io/blog/monitoring-ec2-network-utilization.html" target="_blank" rel="noopener">monitor the network utilization of your EC2 instances</a>. On top of that, use marbot to receive <a href="https://marbot.io/amazon-cloudwatch-alarm-to-slack.html" target="_blank" rel="noopener">CloudWatch alarms via Slack</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>2017 in Review</title>
      <link>https://cloudonaut.io/2017-in-review/</link>
      <description>
        <![CDATA[<p>The year 2017 is coming to an end. We want to thank our readers, customers, supporters, and partners. It was a pleasure to be part of an]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/retrospective/">retrospective</category>
      <guid isPermaLink="true">https://cloudonaut.io/2017-in-review/</guid>
      <pubDate>Sat, 30 Dec 2017 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The year 2017 is coming to an end. We want to thank our readers, customers, supporters, and partners. It was a pleasure to be part of an inspiring community.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/12/happynewyear@730w.webp 730w, /images/2017/12/happynewyear@730w2x.webp 1460w, /images/2017/12/happynewyear@610w.webp 610w, /images/2017/12/happynewyear@610w2x.webp 1220w, /images/2017/12/happynewyear@450w.webp 450w, /images/2017/12/happynewyear@450w2x.webp 900w, /images/2017/12/happynewyear@330w.webp 330w, /images/2017/12/happynewyear@330w2x.webp 660w, /images/2017/12/happynewyear@545w.webp 545w, /images/2017/12/happynewyear@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/12/happynewyear@730w.jpg 730w, /images/2017/12/happynewyear@730w2x.jpg 1460w, /images/2017/12/happynewyear@610w.jpg 610w, /images/2017/12/happynewyear@610w2x.jpg 1220w, /images/2017/12/happynewyear@450w.jpg 450w, /images/2017/12/happynewyear@450w2x.jpg 900w, /images/2017/12/happynewyear@330w.jpg 330w, /images/2017/12/happynewyear@330w2x.jpg 660w, /images/2017/12/happynewyear@545w.jpg 545w, /images/2017/12/happynewyear@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/12/happynewyear.jpg" alt="Happy New Year" title="Happy New Year"></picture></p><h2 id="Blog"><a href="#Blog" class="headerlink" title="Blog"></a>Blog</h2><p>We share how-tos, lessons learned and opinions on our blog <a href="https://cloudonaut.io/">cloudonaut.io</a>. Thanks for reading, sharing, and providing feedback!</p><p>A few facts and numbers.</p><ul><li>432,000 pages viewed</li><li>220,000 people visiting the site</li><li>39 articles published</li><li>40,000 written words</li></ul><p>The following articles have been read the most in 2017.</p><ul><li><a href="/integrate-sqs-and-lambda-serverless-architecture-for-asynchronous-workloads/">Integrate SQS and Lambda: serverless architecture for asynchronous workloads</a></li><li><a href="/aws-security-primer/">AWS Security Primer</a></li><li><a href="/create-a-serverless-restful-api-with-api-gateway-cloudformation-lambda-and-dynamodb/">Create a serverless RESTful API with API Gateway, CloudFormation, Lambda, and DynamoDB</a></li><li><a href="/create-a-serverless-restful-api-with-api-gateway-swagger-lambda-and-dynamodb/">Create a serverless RESTful API with API Gateway, Swagger, Lambda, and DynamoDB</a></li><li><a href="/manage-aws-ec2-ssh-access-with-iam/">Manage AWS EC2 SSH access with IAM</a></li><li><a href="/wordpress-on-aws-you-are-holding-it-wrong/">WordPress on AWS: you are holding it wrong</a></li><li><a href="/cloudformation-vs-terraform/">CloudFormation vs. Terraform</a></li><li><a href="/5-aws-mistakes-you-should-avoid/">5 AWS mistakes you should avoid</a></li><li><a href="/serverless-image-resizing-at-any-scale/">Serverless image resizing at any scale</a></li><li><a href="/6-tips-and-tricks-for-aws-command-line-ninjas/">6 tips and tricks for AWS command-line ninjas</a></li></ul><p>We do like most the following articles published in 2017.</p><ul><li><a href="/aws-velocity-series/">AWS Velocity Series</a></li><li><a href="/your-lambda-function-might-execute-twice-deal-with-it/">Your Lambda function might execute twice. Be prepared!</a></li><li><a href="/a-simple-way-to-manage-log-messages-from-containers-cloudwatch-logs/">A simple way to manage log messages from containers: CloudWatch Logs</a></li><li><a href="/engaging-your-users-with-aws-step-functions/">Engaging your users with AWS Step Functions</a></li><li><a href="/passwordless-database-authentication-for-aws-lambda/">Passwordless database authentication for AWS Lambda</a></li><li><a href="/cloudwatch-is-neglected-why-is-the-control-room-empty/">CloudWatch is neglected: Why is the control room empty?</a></li><li><a href="/ecs-vs-kubernetes/">ECS vs. Kubernets: same same but different</a></li><li><a href="/lessons-learned-serverless-chatbot-architecture-for-marbot/">Lessons learned: Serverless Chatbot architecture for marbot</a></li><li><a href="/evolution-of-the-ec2-network-performance-m3-m4-m5/">Evolution of the EC2 Network Performance: m3, m4, and m5</a></li></ul><h2 id="Book"><a href="#Book" class="headerlink" title="Book"></a>Book</h2><p style="float: left; margin-right: 1em; margin-bottom: 1em;"><img src="/images/2017/12/awsinaction2ndmeap.png" alt="AWS in Action, 2nd edition" style="max-width: 200px"></p><p>AWS is innovating fast and a lot changed since we have published our book Amazon Web Services in Action in 2015. Therefore, we decided to revise our book in 2017. We are glad to announce, that we have revised all 14 chapters of our book and also added three more chapters covering Lambda, ElastiCache, and Elastic File System (EFS). The 2nd edition of Amazon Web Services in Action is going to production within the next few weeks. A preview of our book is already available. We’d like to thank all the readers of our book.</p><p>Buy <a href="http://bit.ly/amazon-web-services-in-action-2nd-edition" target="_blank" rel="noopener">Amazon Web Services in Action, 2nd edition</a>.</p><h2 id="Product"><a href="#Product" class="headerlink" title="Product"></a>Product</h2><p style="float: left; margin-right: 1em; margin-bottom: 1em;"><img src="/images/2017/12/marbot.png" alt="AWS in Action, 2nd edition" style="max-width: 200px"></p><p>We have made massive progress with <a href="https://marbot.io/" target="_blank" rel="noopener">marbot, our chatbot ensuring you never miss an alert from Amazon Web Services</a> in 2017.</p><ul><li>Introducing marbot plus, a paid plan offering additional features and integrations.</li><li>Adding integrations to receive alerts from CloudWatch Events, ElastiCache Notifications, RDS Events, Trusted Advisor Updates, …</li><li>Providing generic endpoints to receive alerts via HTTPS or email.</li><li>Aggregating and deduplicating alerts.</li></ul><p>We’d like to thank all teams who invited marbot to join them via Slack.</p><h2 id="Open-Source"><a href="#Open-Source" class="headerlink" title="Open Source"></a>Open Source</h2><p>We do profit from Open Source in our day-to-day work. That’s why we are trying to give something back to the community by sharing the following projects with you.</p><ul><li><a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">widdix&#x2F;aws-cf-templates: Free Templates for AWS CloudFormation</a> (712 stars and 275 forks)</li><li><a href="https://github.com/widdix/aws-ec2-ssh" target="_blank" rel="noopener">widdix&#x2F;aws-ec2-ssh: Manage AWS EC2 SSH access with IAM</a> (336 stars and 130 forks)</li><li><a href="https://github.com/widdix/aws-s3-virusscan" target="_blank" rel="noopener">widdix&#x2F;aws-s3-virusscan: Free Antivirus for S3 Buckets</a> (131 stars and 28 forks)</li><li><a href="https://github.com/widdix/cfn-create-or-update" target="_blank" rel="noopener">widdix&#x2F;cfn-create-or-update: Create or update CloudFormation stack also if no updates are to be performed</a> (25 stars and 7 forks)</li><li><a href="https://github.com/widdix/cloudwatch-alarm-to-slack" target="_blank" rel="noopener">widdix&#x2F;cloudwatch-alarm-to-slack: Send CloudWatch Alarms to Slack with AWS Lambda</a> (5 stars and 1 fork)</li></ul><p>Thanks to all the contributors to one of our Open Source projects.</p><h2 id="Consulting-Projects"><a href="#Consulting-Projects" class="headerlink" title="Consulting Projects"></a>Consulting Projects</h2><p>We enjoy supporting our consulting clients with our AWS and DevOps expertise. In 2017 we have worked together with small startups, medium-sized companies, and big enterprises. We’d like to thank our consulting clients for putting their trust into our AWS and DevOps expertise.</p><p>A few technologies that we have used in 2017 to solve the real-world challenges of our clients:</p><ul><li>Kubernetes on AWS</li><li>Deployment Pipelines with GitLab</li><li>Infrastructure as Code with CloudFormation and Terraform</li><li>Elastic Container Service (ECS)</li><li>AWS Organisations and IAM</li><li>Deployment Pipelines with Jenkins and SonarQube</li><li>Collecting and processing events with AWS IoT</li><li>Monitoring and Logging with Elasticsearch and Kibana</li><li>Serverless with Lambda, API Gateway, and DynamoDB</li><li>Caching database requests with ElatiCache (Redis)</li><li>Orchestrating Lambda with Step Functions</li><li>Event-driven architectures with Kinesis</li><li>Encrypting data-at-rest with KMS</li></ul><h2 id="We-wish-you-a-Happy-New-Year"><a href="#We-wish-you-a-Happy-New-Year" class="headerlink" title="We wish you a Happy New Year."></a>We wish you a Happy New Year.</h2>]]>
      </content:encoded>
    </item>
    <item>
      <title>Evolution of the EC2 Network Performance: m3, m4, and m5</title>
      <link>https://cloudonaut.io/evolution-of-the-ec2-network-performance-m3-m4-m5/</link>
      <description>
        <![CDATA[<p>AWS announces new generations of EC2 instances from time to time. Typically, each generation offers better performance at lower costs. Th]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <guid isPermaLink="true">https://cloudonaut.io/evolution-of-the-ec2-network-performance-m3-m4-m5/</guid>
      <pubDate>Tue, 19 Dec 2017 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>AWS announces new generations of EC2 instances from time to time. Typically, each generation offers better performance at lower costs. This article discusses the networking capabilities of the general purpose instances over time. Spoiler alert: you can increase the throughput of your web application, decrease the duration of your backups from EC2 to S3, and increase the maximum network throughput between two EC2 instances without additional costs.</p><p>We have measured the networking performance of EC2 instance from three different general purpose generations:</p><ul><li>The third generation <code>m3</code> announced 2012.</li><li>The fourth generation <code>m4</code> announced 2015.</li><li>The fifth generation <code>m5</code> announced 2017 during re:Invent recently.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/12/evolution@730w.webp 730w, /images/2017/12/evolution@730w2x.webp 1460w, /images/2017/12/evolution@610w.webp 610w, /images/2017/12/evolution@610w2x.webp 1220w, /images/2017/12/evolution@450w.webp 450w, /images/2017/12/evolution@450w2x.webp 900w, /images/2017/12/evolution@330w.webp 330w, /images/2017/12/evolution@330w2x.webp 660w, /images/2017/12/evolution@545w.webp 545w, /images/2017/12/evolution@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/12/evolution@730w.jpg 730w, /images/2017/12/evolution@730w2x.jpg 1460w, /images/2017/12/evolution@610w.jpg 610w, /images/2017/12/evolution@610w2x.jpg 1220w, /images/2017/12/evolution@450w.jpg 450w, /images/2017/12/evolution@450w2x.jpg 900w, /images/2017/12/evolution@330w.jpg 330w, /images/2017/12/evolution@330w2x.jpg 660w, /images/2017/12/evolution@545w.jpg 545w, /images/2017/12/evolution@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/12/evolution.jpg" alt="Evolution of the EC2 Network Performance" title="Evolution of the EC2 Network Performance"></picture></p><p>The network throughput increased slightly between each instance generation. However, <code>m5.large</code>, <code>m5.xlarge</code>, and <code>m5.2xlarge</code> instances can burst up to 10 Gbit&#x2F;s for a short period amount of time.</p><p>The following table lists the average network throughput of m3, m4, and m5 EC2 instances in Gbit&#x2F;s. We have measured the network performance daily with the <code>iperf3</code> network benchmark tool on April 12th, 2018. See <a href="/ec2-network-performance-cheat-sheet/">EC2 Network Performance Cheat Sheet</a> if you are interested in the details of the network performance benchmark.</p><table class="table">  <thead>    <tr>      <th>INSTANCE TYPE</th>      <th>M3 FAMILY</th>      <th>M4 FAMILY</th>      <th>M5 FAMILY</th>    </tr>  </thead>  <tbody>    <tr>      <td>medium</td>      <td>0.30 Gbit/s</td>      <td></td>      <td></td>    </tr>    <tr>      <td>large</td>      <td>0.69 Gbit/s</td>      <td>0.45 Gbit/s</td>      <td>0.74 Gbit/s</td>    </tr>    <tr>      <td>xlarge</td>      <td>0.99 Gbit/s</td>      <td>0.74 Gbit/s</td>      <td>1.24 Gbit/s</td>    </tr>    <tr>      <td>2xlarge</td>      <td>0.99 Gbit/s</td>      <td>0.99 Gbit/s</td>      <td>2.49 Gbit/s</td>    </tr>    <tr>      <td>4xlarge</td>      <td></td>      <td>1.99 Gbit/s</td>      <td>4.97 Gbit/s</td>    </tr>    <tr>      <td>10xlarge</td>      <td></td>      <td>9.85 Gbit/s</td>      <td></td>    </tr>    <tr>      <td>12xlarge</td>      <td></td>      <td></td>      <td>10.04 Gbit/s</td>    </tr>    <tr>      <td>16xlarge</td>      <td></td>      <td>20.31 Gbit/s</td>      <td></td>    </tr>    <tr>      <td>24xlarge</td>      <td></td>      <td></td>      <td>22.17 Gbit/s</td>    </tr>  </tbody></table><p>Both <code>m3</code> and <code>m4</code> instances offer constant network throughput. In contrast, <code>m5.large</code>, <code>m5.xlarge</code>, and <code>m5.2xlarge</code> can burst network throughput up to 10 Gbit&#x2F;s for a short period as illustrated in the following table.</p><p>The network performance benchmarks run for 60 minutes.</p><ul><li>90th Percentile: throughput was reached for 6 minutes.</li><li>70th Percentile: throughput was reached for 18 minutes.</li><li>50th Percentile: throughput was reached for 30 minutes.</li><li>30th Percentile: throughput was reached for 42 minutes.</li></ul><table class="table">  <thead>    <tr>      <th>Instance Type</th>      <th>Maximum</th>      <th>90th Percentile</th>      <th>70th Percentile</th>      <th>50th Percentile</th>      <th>30th Percentile</th>    </tr>  </thead>  <tbody><tr>      <td>m5.large</td>      <td>10.04</td>      <td>1.43</td>      <td> 0.74</td>      <td>0.74</td>      <td>0.74</td>    </tr>    <tr>      <td>m5.xlarge</td>      <td>10.04</td>      <td>10.04</td>      <td>1.24</td>      <td>1.24</td>      <td>1.24</td>    </tr>    <tr>      <td>m5.2xlarge</td>      <td>10.04</td>      <td>10.04</td>      <td>10.04</td>      <td>2.49</td>      <td>2.49</td>    </tr>    <tr>      <td>m5.4xlarge</td>      <td>10.04</td>      <td>10.04</td>      <td>10.04</td>      <td>10.04</td>      <td>4.97</td>    </tr>    <tr>      <td>m5.12xlarge</td>      <td>10.04</td>      <td>10.04</td>      <td>10.04</td>      <td>10.04</td>      <td>10.04</td>    </tr>    <tr>      <td>m5.24xlarge</td>      <td>22.20</td>      <td>22.20</td>      <td>22.19</td>      <td>22.18</td>      <td>22.17</td>    </tr>  </tbody></table><p>As usual, making use of the latest instance generation gives you more performance for the same money. Especially, if your workload profits from a network performance burst for a few minutes, making use of the <code>m5</code> instance types can increase throughput dramatically.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS re:Invent 2017 themes</title>
      <link>https://cloudonaut.io/re-invent-2017-themes/</link>
      <description>
        <![CDATA[<p style="float: left; margin-right: 1em; margin-bottom: 1em;"><img src="/images/2017/12/re-invent-selfie.jpg" alt="re:Invent selfie" style=]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/reinvent/">reinvent</category>
      <guid isPermaLink="true">https://cloudonaut.io/re-invent-2017-themes/</guid>
      <pubDate>Fri, 01 Dec 2017 20:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p style="float: left; margin-right: 1em; margin-bottom: 1em;"><img src="/images/2017/12/re-invent-selfie.jpg" alt="re:Invent selfie" style="max-width: 250px"></p><p>I’m in Las Vegas this week. So many new features and services were announce that it’s hard to keep up-to-date. That’s why I’d like to try something different to recap re:Invent this year. Instead of listing you all the announcements, I take a step back and look at the overall picture. I identified four main themes after filtering out all the marketing hype: you will not see me talking too much about voice interfaces, AI, AR, and VR.</p><p style="clear: both;"></p><h2 id="It’s-all-about-building-blocks"><a href="#It’s-all-about-building-blocks" class="headerlink" title="It’s all about building blocks"></a>It’s all about building blocks</h2><p><strong>AWS is committed to provide us building blocks, not one size fits all solutions</strong>. Look at the container space. Besides <a href="https://aws.amazon.com/ecs/" target="_blank" rel="noopener">Amazon ECS</a> that has been around for some years, AWS announced that they will manage Kubernetes (<a href="https://aws.amazon.com/eks/" target="_blank" rel="noopener">Amazon EKS</a>) for us. ECS and EKS give you full control, you manage the EC2 instances that act as Docker hosts.  Additionally, <a href="https://aws.amazon.com/fargate/" target="_blank" rel="noopener">AWS Fargate</a> is the new service to execute your Docker containers where you are not in charge of the clusters anymore. Now you have many options to run containers.</p><h2 id="Data-goes-global-and-algorithms-go-local"><a href="#Data-goes-global-and-algorithms-go-local" class="headerlink" title="Data goes global and algorithms go local"></a>Data goes global and algorithms go local</h2><p><strong>AWS pushes relational databases to the next level and makes multi-region easier.</strong> RDS is the service to store your relational data. But compared to DynamoDB, RDS database engines do not scale very well. Aurora, a proprietary RDS engine built by AWS, is going to change this very soon. You can already add read replicas to scale reads, but Aurora will get multiple masters soon to scale writes. Besides that, DynamoDB offers replicating tables across regions. This allows you to have a single source of truth for your users in the US, Europe, and Asia.</p><p>AWS announce that we can now extract data from S3 and Glacier without needing to download the file first (<a href="https://aws.amazon.com/blogs/aws/s3-glacier-select/" target="_blank" rel="noopener">S3 and Glacier Select</a>). The trend is that you can now analyze the data directly in the data store. The algorithms move closer to the data (see also <a href="https://aws.amazon.com/redshift/spectrum/" target="_blank" rel="noopener">Redshift Spectrum</a> and <a href="https://aws.amazon.com/athena/" target="_blank" rel="noopener">Athena</a>).</p><h2 id="Evolutionary-Architectures"><a href="#Evolutionary-Architectures" class="headerlink" title="Evolutionary Architectures"></a>Evolutionary Architectures</h2><p><strong>Don’t consider your architecture as static.</strong> Your business changes, technology changes, user behavior changes. Make sure that your architecture changes as well. I highly recommend reading <a href="http://amzn.to/2j6An0Y" target="_blank" rel="noopener">Building Evolutionary Architectures</a> and following the <a href="https://aws.amazon.com/architecture/well-architected/" target="_blank" rel="noopener">AWS Well-Architected Framework</a>. This will help you to build an evolutionary architecture that solves your business problem, grows with your needs, and adapts to changes in technology. Only make architecture decisions when you have enough data to make a well grounded decision. Otherwise, choose the simplest way possible.</p><h2 id="AWS-is-still-for-developers"><a href="#AWS-is-still-for-developers" class="headerlink" title="AWS is (still) for developers"></a>AWS is (still) for developers</h2><p>I must admit, that I was looking for alternatives to AWS in the last 12 months. I had the impression that AWS is shifting away from me. Instead, they focused on their enterprise clients. But they should better call them clients that are not deeply interested in technology. But this year, AWS seems to shift back to developers. AWS released their first online IDE called <a href="https://aws.amazon.com/cloud9/" target="_blank" rel="noopener">Cloud9</a>. They added new features for Serverless architectures and made it easier for developers to get started with AI. We are back to developers composing building blocks together to solve real problems.</p><h2 id="Join-our-re-Invent-follow-up-group"><a href="#Join-our-re-Invent-follow-up-group" class="headerlink" title="Join our re:Invent follow-up group"></a>Join our re:Invent follow-up group</h2><p>During the next three weeks, we would like to scrutinize the most important announced services that are already generally available together with you. <a href="/join-our-reinvent-follow-up-group/">Read more</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Join our re:Invent follow-up group</title>
      <link>https://cloudonaut.io/join-our-reinvent-follow-up-group/</link>
      <description>
        <![CDATA[<p>Amazing, AWS released an impressive amount of new services and features during re:Invent. As the conference is coming to an end, we would]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/reinvent/">reinvent</category>
      <guid isPermaLink="true">https://cloudonaut.io/join-our-reinvent-follow-up-group/</guid>
      <pubDate>Fri, 01 Dec 2017 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Amazing, AWS released an impressive amount of new services and features during re:Invent. As the conference is coming to an end, we would like to invite you to pass the most important announcements in review.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/12/followup@730w.webp 730w, /images/2017/12/followup@730w2x.webp 1460w, /images/2017/12/followup@610w.webp 610w, /images/2017/12/followup@610w2x.webp 1220w, /images/2017/12/followup@450w.webp 450w, /images/2017/12/followup@450w2x.webp 900w, /images/2017/12/followup@330w.webp 330w, /images/2017/12/followup@330w2x.webp 660w, /images/2017/12/followup@545w.webp 545w, /images/2017/12/followup@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/12/followup@730w.jpg 730w, /images/2017/12/followup@730w2x.jpg 1460w, /images/2017/12/followup@610w.jpg 610w, /images/2017/12/followup@610w2x.jpg 1220w, /images/2017/12/followup@450w.jpg 450w, /images/2017/12/followup@450w2x.jpg 900w, /images/2017/12/followup@330w.jpg 330w, /images/2017/12/followup@330w2x.jpg 660w, /images/2017/12/followup@545w.jpg 545w, /images/2017/12/followup@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/12/followup.jpg" alt="Follow Up" title="Follow Up"></picture></p><p>During the next three weeks, we would like to scrutinize the most important announced services that are already generally available together with you.</p><ul><li>Amazon Comprehend (from December 4th to December 7th)</li><li>Amazon SageMaker (from December 11th to December 14th)</li><li>AWS Fargate (from December 18th to December 21st)</li></ul><p>On Mondays, you will receive an email with all the details needed to get started with the service. Take your time to read through the documentation and even more important get hands-on experience with the service. On Thursdays, we will schedule a video conference where we all meet up to discuss our impressions and learnings.</p><p>Send an email to <a href="mailto:&#x68;&#x65;&#x6c;&#108;&#111;&#x40;&#x63;&#x6c;&#111;&#x75;&#100;&#x6f;&#x6e;&#x61;&#x75;&#x74;&#x2e;&#105;&#111;">hello@cloudonaut.io</a> to join our re:Invent follow-up group. Only ten seats are available!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>EC2 network performance demystified: m3 and m4</title>
      <link>https://cloudonaut.io/ec2-network-performance-demystified-m3-m4/</link>
      <description>
        <![CDATA[<p>AWS offers EC2 instances in different sizes, defined by the instance type. How do you decide which instance type to use? Do you need an <]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/vpc/">vpc</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <guid isPermaLink="true">https://cloudonaut.io/ec2-network-performance-demystified-m3-m4/</guid>
      <pubDate>Thu, 30 Nov 2017 19:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>AWS offers EC2 instances in different sizes, defined by the instance type. How do you decide which instance type to use? Do you need an <code>m4.large</code> or <code>m4.xlarge</code> instance? At least the following factors should affect your decision:</p><ul><li>How much memory does the operating system and application need?</li><li>Does your application benefit from multiple CPU cores?</li><li>What is the expected network throughput going in and out your instance?</li></ul><p>It’s easy to get the information about the memory and CPU capacity of an instance type at <a href="https://aws.amazon.com/ec2/instance-types/" target="_blank" rel="noopener">Amazon EC2 Instance Types</a>. For example, an <code>m4.large</code> instance comes with two vCPUs and 8 GiB memory. But which network performance can you expect with an <code>m4.large</code> instance? AWS promises a <code>Moderate</code> network performance without any hint how to translate this vague information into GBit&#x2F;s.</p><p>I’ve been dissatisfied with the lack of information about the actual network performance of EC2 instances for a while. Therefore, I remembered the saying my father told me: “Messen heißt wissen!” which translates to “Measuring means knowledge!”.</p><p>Therefore, I’ve measured the networking performance of EC2 instances in the real world, and I’m happy to share the results with you.</p><p>Summary of the benchmarking infrastructure:</p><ul><li>benchmarking tool: iperf3</li><li>instance type of iperf3 server: <code>c5.18xlarge</code></li><li>regions: <code>us-east-1</code></li><li>period: 60 minutes per instance type at April 12th, 2018</li></ul><h2 id="m3-Instance-Family"><a href="#m3-Instance-Family" class="headerlink" title="m3 Instance Family"></a>m3 Instance Family</h2><p>The benchmark results within the <code>m3</code> instance family are very consistent with a minimal variance between different measurements. Expect a network throughput from 0.3 Gbit&#x2F;s to 1.0 Gbit&#x2F;s with <code>m3</code> instances.</p><table class="table">  <thead>    <tr>      <th>Instance Type</th>      <th>Target</th>      <th>Maximum (Gbit/s)</th>      <th>Minimum (Gbit/s)</th>      <th>Average (Gbit/s)</th>    </tr>  </thead>  <tbody>    <tr>      <td>m3.medium</td>      <td>Moderate</td>      <td>0.30</td>      <td>0.32</td>      <td>0.30</td>    </tr>    <tr>      <td>m3.large</td>      <td>Moderate</td>      <td>0.69</td>      <td>0.70</td>      <td>0.69</td>    </tr>    <tr>      <td>m3.xlarge</td>      <td>High</td>      <td>0.99</td>      <td>1.01</td>      <td>0.99</td>    </tr>    <tr>      <td>m3.2xlarge</td>      <td>High</td>      <td>0.99</td>      <td>1.01</td>      <td>0.99</td>    </tr>  </tbody></table><h2 id="m4-Instance-Family"><a href="#m4-Instance-Family" class="headerlink" title="m4 Instance Family"></a>m4 Instance Family</h2><p>With <code>m4</code> instances network throughput varies from 0.45 Gbit&#x2F;s to 20.37 Gbit&#x2F;s.</p><table class="table">  <thead>    <tr>      <th>Instance Type</th>      <th>Target</th>      <th>Maximum (Gbit/s)</th>      <th>Minimum (Gbit/s)</th>      <th>Average (Gbit/s)</th>    </tr>  </thead>  <tbody>    <tr>    <tr>      <td>m4.large</td>      <td>Moderate</td>      <td>0.45</td>      <td>0.47</td>      <td>0.45</td>    </tr>    <tr>      <td>m4.xlarge</td>      <td>High</td>      <td>0.74</td>      <td>0.81</td>      <td>0.75</td>    </tr>    <tr>      <td>m4.2xlarge</td>      <td>High</td>      <td>0.99</td>      <td>1.02</td>      <td>0.99</td>    </tr>    <tr>      <td>m4.4xlarge</td>      <td>High</td>      <td>1.99</td>      <td>2.04</td>      <td>1.99</td>    </tr>    <tr>      <td>m4.10xlarge</td>      <td>10 Gigabit/s</td>      <td>9.85</td>      <td>9.86</td>      <td>9.85</td>    </tr>    <tr>      <td>m4.16xlarge</td>      <td>25 Gigabit/s</td>      <td>19.59</td>      <td>23.25</td>      <td>20.37</td>    </tr>  </tbody></table><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>When choosing an instance type, the network performance should be one of the main criteria. Especially, for network-intensive workloads. As AWS is only providing very vague information about the networking performance of each instance types our benchmark helps you to avoid bottlenecks caused by the networking capacity of your instances.</p><p>Have a look at the <a href="/ec2-network-performance-cheat-sheet/">EC2 Network Performance Cheat Sheet</a> if you are looking for other instance types.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Meet me at AWS re:Invent and have breakfast with me</title>
      <link>https://cloudonaut.io/meet-me-at-aws-re-invent-and-have-breakfast-with-me/</link>
      <description>
        <![CDATA[<p>If you attend this year’s AWS re:Invent, this blog post is for you!</p>
<p>I will invite a group of people to have breakfast together on]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <category domain="https://cloudonaut.io/tag/reinvent/">reinvent</category>
      <guid isPermaLink="true">https://cloudonaut.io/meet-me-at-aws-re-invent-and-have-breakfast-with-me/</guid>
      <pubDate>Thu, 16 Nov 2017 20:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>If you attend this year’s AWS re:Invent, this blog post is for you!</p><p>I will invite a group of people to have breakfast together on Tuesday morning (Nov 28, 9-10am) offside the noisy halls where you could get your complimentary re:Invent breakfast. Food and drinks are on me.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/11/reinvent-breakfast@730w.webp 730w, /images/2017/11/reinvent-breakfast@730w2x.webp 1460w, /images/2017/11/reinvent-breakfast@610w.webp 610w, /images/2017/11/reinvent-breakfast@610w2x.webp 1220w, /images/2017/11/reinvent-breakfast@450w.webp 450w, /images/2017/11/reinvent-breakfast@450w2x.webp 900w, /images/2017/11/reinvent-breakfast@330w.webp 330w, /images/2017/11/reinvent-breakfast@330w2x.webp 660w, /images/2017/11/reinvent-breakfast@545w.webp 545w, /images/2017/11/reinvent-breakfast@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/11/reinvent-breakfast@730w.jpeg 730w, /images/2017/11/reinvent-breakfast@730w2x.jpeg 1460w, /images/2017/11/reinvent-breakfast@610w.jpeg 610w, /images/2017/11/reinvent-breakfast@610w2x.jpeg 1220w, /images/2017/11/reinvent-breakfast@450w.jpeg 450w, /images/2017/11/reinvent-breakfast@450w2x.jpeg 900w, /images/2017/11/reinvent-breakfast@330w.jpeg 330w, /images/2017/11/reinvent-breakfast@330w2x.jpeg 660w, /images/2017/11/reinvent-breakfast@545w.jpeg 545w, /images/2017/11/reinvent-breakfast@545w2x.jpeg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/11/reinvent-breakfast.jpeg" alt="re:Invent Breakfast" title="re:Invent Breakfast"></picture></p><p>We will speculate about announcements, discuss trends, and complain about pain points. I’m particularly interested in how you handle CloudWatch alarms, monitoring and incident management.</p><p>I’d like to meet you. No matter if you started using AWS today or five years before. No matter if you are young or old. No matter what’s your job title.</p><p><strong>If you have anything to say about AWS CloudWatch alarms, I want to have breakfast with you.</strong></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Your Lambda function might execute twice. Be prepared!</title>
      <link>https://cloudonaut.io/your-lambda-function-might-execute-twice-deal-with-it/</link>
      <description>
        <![CDATA[<p>Are you confused when scheduled Lambdas execute twice, SNS messages trigger an invocation three times, your handmade S3 inventory is out]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/dynamodb/">dynamodb</category>
      <guid isPermaLink="true">https://cloudonaut.io/your-lambda-function-might-execute-twice-deal-with-it/</guid>
      <pubDate>Thu, 09 Nov 2017 17:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Are you confused when scheduled Lambdas execute twice, SNS messages trigger an invocation three times, your handmade S3 inventory is out of date because events occurred twice? <strong>Bad news: Sooner or later, your Lambda function will be invoked multiple times.</strong> You have to be prepared! The reasons are <a href="http://docs.aws.amazon.com/lambda/latest/dg/retries-on-errors.html" target="_blank" rel="noopener">retries on errors</a> and event sources that guarantee at-least-once delivery (e.g., <a href="http://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CWE_Troubleshooting.html#RuleTriggeredMoreThanOnce" target="_blank" rel="noopener">CloudWatch Events</a>, <a href="https://aws.amazon.com/sns/faqs/" target="_blank" rel="noopener">SNS</a>, …).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/11/lambda-idempotent@730w.webp 730w, /images/2017/11/lambda-idempotent@730w2x.webp 1460w, /images/2017/11/lambda-idempotent@610w.webp 610w, /images/2017/11/lambda-idempotent@610w2x.webp 1220w, /images/2017/11/lambda-idempotent@450w.webp 450w, /images/2017/11/lambda-idempotent@450w2x.webp 900w, /images/2017/11/lambda-idempotent@330w.webp 330w, /images/2017/11/lambda-idempotent@330w2x.webp 660w, /images/2017/11/lambda-idempotent@545w.webp 545w, /images/2017/11/lambda-idempotent@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/11/lambda-idempotent@730w.png 730w, /images/2017/11/lambda-idempotent@730w2x.png 1460w, /images/2017/11/lambda-idempotent@610w.png 610w, /images/2017/11/lambda-idempotent@610w2x.png 1220w, /images/2017/11/lambda-idempotent@450w.png 450w, /images/2017/11/lambda-idempotent@450w2x.png 900w, /images/2017/11/lambda-idempotent@330w.png 330w, /images/2017/11/lambda-idempotent@330w2x.png 660w, /images/2017/11/lambda-idempotent@545w.png 545w, /images/2017/11/lambda-idempotent@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/11/lambda-idempotent.png" alt="Your Lambda function will be invoked multiple times" title="Your Lambda function will be invoked multiple times"></picture></p><p>How do you know that your Lambda function is broken (or not idempotent)? If your function is given the same input (aka event) multiple times, the function MUST produce the same result. If your function produces different results with the same input, the implementation is not idempotent, and you are in big troubles.</p><p>You may ask yourself how to fix it? Let’s work with a concrete example. Imagine a Lambda function to ensure that a user can only make a specific number request per day. A request could be an upload to an S3 bucket, sending a message, whatever. In other words, the Lambda function implements rate limiting. To do so, you need to store some state. A good place to store the state is DynamoDB. Luckily, DynamoDB offers many features to fix your problem.</p><h2 id="1-Iteration-The-not-idempotent-implementation"><a href="#1-Iteration-The-not-idempotent-implementation" class="headerlink" title="1. Iteration: The not idempotent implementation"></a>1. Iteration: The not idempotent implementation</h2><p>The first iteration provides the most simple implementation. But also a broken implementation.</p><p>The input event looks like this:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;user&quot;</span><span class="punctuation">:</span> <span class="string">&quot;u1&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>The function uses a DynamoDB table <code>ratelimit</code> with the primary key <code>id</code> (partition key). The id consists of the user and the current date (yyyy-mm-dd). Additionally, a <code>calls</code> attribute of type number is used to track the number of calls.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> dynamodb = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">DynamoDB</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-08-10&#x27;</span>&#125;);</span><br><span class="line"><span class="keyword">const</span> limit = <span class="number">8</span>;</span><br><span class="line"><span class="built_in">exports</span>.<span class="property">handler</span> = <span class="function">(<span class="params">event, context, cb</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> date = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">toISOString</span>().<span class="title function_">slice</span>(<span class="number">0</span>, <span class="number">10</span>); <span class="comment">// yyyy-mm-dd</span></span><br><span class="line">  <span class="keyword">const</span> id = <span class="string">`iteration1:<span class="subst">$&#123;event.user&#125;</span>:<span class="subst">$&#123;date&#125;</span>`</span>;</span><br><span class="line">  dynamodb.<span class="title function_">updateItem</span>(&#123;</span><br><span class="line">    <span class="title class_">TableName</span>: <span class="string">`ratelimit`</span>,</span><br><span class="line">    <span class="title class_">Key</span>: &#123;</span><br><span class="line">      <span class="attr">id</span>: &#123;<span class="attr">S</span>: id&#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="title class_">UpdateExpression</span>: <span class="string">&#x27;ADD calls :one&#x27;</span>,  <span class="comment">// increase attribute calls by one, if the calls attribute not exists, a value of 0 is assumed</span></span><br><span class="line">    <span class="title class_">ExpressionAttributeValues</span>: &#123;</span><br><span class="line">      <span class="string">&#x27;:one&#x27;</span>: &#123;<span class="attr">N</span>: <span class="string">&#x27;1&#x27;</span>&#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="title class_">ReturnValues</span>: <span class="string">&#x27;ALL_NEW&#x27;</span></span><br><span class="line">  &#125;, <span class="keyword">function</span>(<span class="params">err, data</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) &#123;</span><br><span class="line">      <span class="title function_">cb</span>(err);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> calls = <span class="built_in">parseInt</span>(data.<span class="property">Attributes</span>.<span class="property">calls</span>.<span class="property">N</span>, <span class="number">10</span>);</span><br><span class="line">      <span class="title function_">cb</span>(<span class="literal">null</span>, &#123;<span class="attr">limited</span>: calls &gt; limit&#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>The implementation is not idempotent. The <code>calls</code> attribute is incremented even if the invocation is just a retry. The implementation would limit too early in this case. Let’s fix that!</p><h2 id="2-Iteration-The-mostly-idempotent-implementation"><a href="#2-Iteration-The-mostly-idempotent-implementation" class="headerlink" title="2. Iteration: The mostly idempotent implementation"></a>2. Iteration: The mostly idempotent implementation</h2><p>Let’s try to fix the first iteration. Add a request id to the event.</p><p>The input event looks like this:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;user&quot;</span><span class="punctuation">:</span> <span class="string">&quot;u1&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;request&quot;</span><span class="punctuation">:</span> <span class="string">&quot;r1&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>All <a href="http://docs.aws.amazon.com/lambda/latest/dg/eventsources.html" target="_blank" rel="noopener">event sources</a> provide some unique id that you can use as the request id. Some examples:</p><ul><li>Kinesis: <code>Records[].eventID</code></li><li>SNS: <code>Records[].Sns.MessageId</code></li><li>API Gateway: <code>requestContext.requestId</code></li><li>Scheduled CloudWatch Event: <code>id</code></li></ul><p>The function uses the same DynamoDB table from the first iteration. Instead of the <code>calls</code> attribute, you use a <code>requests</code> attribute of type string set.</p><p>The <code>requests</code> attribute keeps track of all request ids that were already counts by the rate limiter. This way, you avoid to count the same request twice. DynamoDB also ensures that the <code>requests</code> set is limited to a certain amount of request ids.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> dynamodb = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">DynamoDB</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2012-08-10&#x27;</span>&#125;);</span><br><span class="line"><span class="keyword">const</span> limit = <span class="number">8</span>;</span><br><span class="line"><span class="built_in">exports</span>.<span class="property">handler</span> = <span class="function">(<span class="params">event, context, cb</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> date = <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">toISOString</span>().<span class="title function_">slice</span>(<span class="number">0</span>, <span class="number">10</span>);</span><br><span class="line">  <span class="keyword">const</span> id = <span class="string">`iteration2:<span class="subst">$&#123;event.user&#125;</span>:<span class="subst">$&#123;date&#125;</span>`</span>;</span><br><span class="line">  dynamodb.<span class="title function_">updateItem</span>(&#123;</span><br><span class="line">    <span class="title class_">TableName</span>: <span class="string">`ratelimit`</span>,</span><br><span class="line">    <span class="title class_">Key</span>: &#123;</span><br><span class="line">      <span class="attr">id</span>: &#123;<span class="attr">S</span>: id&#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="title class_">UpdateExpression</span>: <span class="string">&#x27;ADD requests :requests&#x27;</span>, <span class="comment">// add request to the requests set</span></span><br><span class="line">    <span class="title class_">ConditionExpression</span>: <span class="string">&#x27;attribute_not_exists (requests) OR contains(requests, :request) OR size(requests) &lt; :limit&#x27;</span>, <span class="comment">// only if requests set does not exists or request is already in set or less than $limit requests in set </span></span><br><span class="line">    <span class="title class_">ExpressionAttributeValues</span>: &#123;</span><br><span class="line">      <span class="string">&#x27;:request&#x27;</span>: &#123;<span class="attr">S</span>: event.<span class="property">request</span>&#125;,</span><br><span class="line">      <span class="string">&#x27;:requests&#x27;</span>: &#123;<span class="attr">SS</span>: [event.<span class="property">request</span>]&#125;,</span><br><span class="line">      <span class="string">&#x27;:limit&#x27;</span>: &#123;<span class="attr">N</span>: limit.<span class="title function_">toString</span>()&#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, <span class="keyword">function</span>(<span class="params">err</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) &#123;</span><br><span class="line">      <span class="keyword">if</span> (err.<span class="property">code</span> === <span class="string">&#x27;ConditionalCheckFailedException&#x27;</span>) &#123; <span class="comment">// $limit requests in set and current request is not in set </span></span><br><span class="line">        <span class="title function_">cb</span>(<span class="literal">null</span>, &#123;<span class="attr">limited</span>: <span class="literal">true</span>&#125;);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="title function_">cb</span>(err);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="title function_">cb</span>(<span class="literal">null</span>, &#123;<span class="attr">limited</span>: <span class="literal">false</span>&#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>You can invoke this function as often as you like, as long as the input stays the same, the result is the same.</p><p>Wait, no. The date (yyyy-mm-dd) is used as part of the key. So if the current date changes to the next day, the result will be different.</p><h2 id="3-Iteration-The-idempotent-implementation"><a href="#3-Iteration-The-idempotent-implementation" class="headerlink" title="3. Iteration: The idempotent implementation"></a>3. Iteration: The idempotent implementation</h2><p>Let’s fix that as well! The date has to be part of the input event as well. I added a timestamp but you could choose whatever format you like.</p><p>The input event looks like this:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;user&quot;</span><span class="punctuation">:</span> <span class="string">&quot;u1&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;request&quot;</span><span class="punctuation">:</span> <span class="string">&quot;r1&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;timestamp&quot;</span><span class="punctuation">:</span> <span class="number">1510067704370</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Some <a href="http://docs.aws.amazon.com/lambda/latest/dg/eventsources.html" target="_blank" rel="noopener">event sources</a> provide a date value out of the box. Some examples:</p><ul><li>Kinesis: not supported but a workaround is possible: put the timestamp in the record data</li><li>SNS: <code>Records[].Sns.Timestamp</code></li><li>API Gateway: not supported and a workaround is problematic because you would rely on the client’s time</li><li>Scheduled CloudWatch Event: <code>time</code></li></ul><p>The function uses the same DynamoDB table from the first iteration. You can use the implementation of the second iteration. But replace the way the <code>date</code> variable is generated:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> date = <span class="keyword">new</span> <span class="title class_">Date</span>(event.<span class="property">timestamp</span>).<span class="title function_">toISOString</span>().<span class="title function_">slice</span>(<span class="number">0</span>, <span class="number">10</span>);</span><br></pre></td></tr></table></figure><p>That’s it. Idempotent rate limiting is implemented.</p><h2 id="Summary-request-ids-as-a-savior"><a href="#Summary-request-ids-as-a-savior" class="headerlink" title="Summary: request ids as a savior"></a>Summary: request ids as a savior</h2><p>Use request identifiers to implement idempotent Lambda functions that do not break if a function invocation needs to be retried. Make sure to pass the original request id to all subsequent calls. The original request id is generated as early as possible in the chain. If possible on the client side. Avoid generating your own ids.</p><p>Use the original request id as the identity to guarantee idempotency:</p><ul><li>In DynamoDB, use it as a primary key together with a <code>ConditionExpression</code> or <code>UpdateExpression SET if_not_exists(att, :val)</code>.</li><li>In Step Functions, use it as the execution name.</li><li>For other services like Kinesis and SQS, put the original request id in the payload. The consumer can then use the original request id again.</li></ul><p>You are now equipped to implement idempotent Lambda functions using DynamoDB. If you use any other data store, think carefully about idempotency!</p><h2 id="Implementation-notes"><a href="#Implementation-notes" class="headerlink" title="Implementation notes"></a>Implementation notes</h2><p>The implementation does not work well with large limits (&gt; 5000) because the requests set will grow with each request added. Sooner or later you will hit the 400 KB limitation of a DynamoDB item.<br>You could circumvent the limitation by working with smaller timespans (e.g., yyyy-mm-dd-hh or yyyy-mm-dd-hh-mm) which also leads to a lower limit but assumes a more or less even distribution of request during the day.<br>If you need a big limit, store each request as a separate item in DynamoDB and sum them asynchronously by using a DynamoDB stream on the table. Keep the current state about if a user is limited in a separate DynamoDB table. This is not an exact rate limiter, but with large limits, you could tolerate that users can make slightly more requests that allowed.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Send CloudWatch Alarms to Slack with AWS Lambda</title>
      <link>https://cloudonaut.io/send-cloudwatch-alarms-to-slack-with-aws-lambda/</link>
      <description>
        <![CDATA[<p>As we all know, things go wrong. That’s why monitoring and alerting are essential topics. Wouldn’t it be nice, if problems in your AWS ac]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <guid isPermaLink="true">https://cloudonaut.io/send-cloudwatch-alarms-to-slack-with-aws-lambda/</guid>
      <pubDate>Wed, 01 Nov 2017 20:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>As we all know, things go wrong. That’s why monitoring and alerting are essential topics. Wouldn’t it be nice, if problems in your AWS account would show up in Slack? So you can react quickly while using your favorite messaging tool. In this blog post, you will learn how you can turn CloudWatch Alarms into Slack messages like this:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/11/cloudwatch-alarm-to-slack-example-message@730w.webp 730w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@730w2x.webp 1460w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@610w.webp 610w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@610w2x.webp 1220w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@450w.webp 450w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@450w2x.webp 900w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@330w.webp 330w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@330w2x.webp 660w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@545w.webp 545w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/11/cloudwatch-alarm-to-slack-example-message@730w.png 730w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@730w2x.png 1460w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@610w.png 610w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@610w2x.png 1220w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@450w.png 450w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@450w2x.png 900w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@330w.png 330w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@330w2x.png 660w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@545w.png 545w, /images/2017/11/cloudwatch-alarm-to-slack-example-message@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/11/cloudwatch-alarm-to-slack-example-message.png" alt="CloudWatch Alarm to Slack Architecture" title="CloudWatch Alarm to Slack Architecture"></picture></p><h2 id="How-it-works"><a href="#How-it-works" class="headerlink" title="How it works"></a>How it works</h2><p>On AWS, everything sends monitoring data (CPU utilization, estimated monthly charges, …) to CloudWatch. In CloudWatch, you define alarms to send a message to an SNS topic if the monitoring data gets out of normal bounds. Finally, you connect a Lambda function to the SNS topic to trigger a function execution. The Lambda function calls the Slack API to send a message. The following figure shows the data flow:</p><p><a href="https://cloudcraft.co/view/c3e64b88-fed3-4e06-a9c6-68606f9af6e3?key=60YKWCqMF9CDu7d5EShYrA" target="_blank" rel="noopener"><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/11/cloudwatch-alarm-to-slack-architecture@730w.webp 730w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@730w2x.webp 1460w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@610w.webp 610w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@610w2x.webp 1220w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@450w.webp 450w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@450w2x.webp 900w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@330w.webp 330w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@330w2x.webp 660w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@545w.webp 545w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/11/cloudwatch-alarm-to-slack-architecture@730w.png 730w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@730w2x.png 1460w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@610w.png 610w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@610w2x.png 1220w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@450w.png 450w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@450w2x.png 900w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@330w.png 330w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@330w2x.png 660w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@545w.png 545w, /images/2017/11/cloudwatch-alarm-to-slack-architecture@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/11/cloudwatch-alarm-to-slack-architecture.png" alt="CloudWatch Alarm to Slack Architecture" title="CloudWatch Alarm to Slack Architecture"></picture></a></p><p>To deploy the components in the figure, you will use the <a href="https://github.com/awslabs/serverless-application-model" target="_blank" rel="noopener">Serverless Application Model</a> (SAM). If you are not interested in implementing this on your own, give our Slack chatbot a try. <a href="https://marbot.io/" target="_blank" rel="noopener">Never miss an alert from your AWS infrastructure with marbot</a>!</p><h2 id="Implementing-the-Lambda-function"><a href="#Implementing-the-Lambda-function" class="headerlink" title="Implementing the Lambda function"></a>Implementing the Lambda function</h2><p>You will use Node.js to implement the Lambda function. To send a request to the Slack API, you have to make an HTTPS request. The <code>request</code> module is easy to use, but I wanted a variant of the module that returns promises to avoid <a href="https://en.wiktionary.org/wiki/callback_hell" target="_blank" rel="noopener">callback hell</a>. That’s why I used <code>request-promise-native</code>. The Slack webhook URL is passed in as an environment variable that you define later in the CloudFormation template.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> request = <span class="built_in">require</span>(<span class="string">&#x27;request-promise-native&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">sendMessage</span> = (<span class="params">message</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">request</span>(&#123;</span><br><span class="line">    <span class="attr">method</span>: <span class="string">&#x27;POST&#x27;</span>,</span><br><span class="line">    <span class="attr">url</span>: process.<span class="property">env</span>.<span class="property">WEBHOOK_URL</span>,</span><br><span class="line">    <span class="attr">body</span>: message,</span><br><span class="line">    <span class="attr">json</span>: <span class="literal">true</span>,</span><br><span class="line">  &#125;)</span><br><span class="line">    .<span class="title function_">then</span>(<span class="function">(<span class="params">body</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (body === <span class="string">&#x27;ok&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123;&#125;;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(body);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>Messages delivered from SNS to the Lambda function will look like this:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Records&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">&#123;</span></span><br><span class="line">     <span class="attr">&quot;EventSource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;aws:sns&quot;</span><span class="punctuation">,</span></span><br><span class="line">     <span class="attr">&quot;EventVersion&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">     <span class="attr">&quot;EventSubscriptionArn&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:sns:us-east-1:XXX:cw-to-slack-Topic-1B8S548158492:a0e76b10-796e-471d-82d3-0510fc89ad93&quot;</span><span class="punctuation">,</span></span><br><span class="line">     <span class="attr">&quot;Sns&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;Type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Notification&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;MessageId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[...]&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;TopicArn&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:sns:us-east-1:XXX:cw-to-slack-Topic-1B8S548158492&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;Subject&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ALARM: \&quot;cw-to-slack-Alarm-9THDKWBS1876\&quot; in US East (N. Virginia)&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;Message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&#123;\&quot;AlarmName\&quot;:\&quot;cw-to-slack-Alarm-9THDKWBS1876\&quot;,\&quot;AlarmDescription\&quot;:null,\&quot;AWSAccountId\&quot;:\&quot;XXX\&quot;,\&quot;NewStateValue\&quot;:\&quot;ALARM\&quot;,\&quot;NewStateReason\&quot;:\&quot;Threshold Crossed: 1 datapoint [3.22 (29/10/17 13:20:00)] was greater than the threshold (1.0).\&quot;,\&quot;StateChangeTime\&quot;:\&quot;2017-10-30T13:20:35.831+0000\&quot;,\&quot;Region\&quot;:\&quot;US East (N. Virginia)\&quot;,\&quot;OldStateValue\&quot;:\&quot;INSUFFICIENT_DATA\&quot;,\&quot;Trigger\&quot;:&#123;\&quot;MetricName\&quot;:\&quot;EstimatedCharges\&quot;,\&quot;Namespace\&quot;:\&quot;AWS/Billing\&quot;,\&quot;StatisticType\&quot;:\&quot;Statistic\&quot;,\&quot;Statistic\&quot;:\&quot;MAXIMUM\&quot;,\&quot;Unit\&quot;:null,\&quot;Dimensions\&quot;:[&#123;\&quot;name\&quot;:\&quot;Currency\&quot;,\&quot;value\&quot;:\&quot;USD\&quot;&#125;],\&quot;Period\&quot;:86400,\&quot;EvaluationPeriods\&quot;:1,\&quot;ComparisonOperator\&quot;:\&quot;GreaterThanThreshold\&quot;,\&quot;Threshold\&quot;:1.0,\&quot;TreatMissingData\&quot;:\&quot;\&quot;,\&quot;EvaluateLowSampleCountPercentile\&quot;:\&quot;\&quot;&#125;&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;Timestamp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2017-10-30T13:20:35.855Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;SignatureVersion&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;Signature&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[...]&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;SigningCertUrl&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[...]&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;UnsubscribeUrl&quot;</span><span class="punctuation">:</span> <span class="string">&quot;[...]&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;MessageAttributes&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="punctuation">&#125;</span></span><br><span class="line">     <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>You need to convert the format into the <a href="https://api.slack.com/docs/message-formatting" target="_blank" rel="noopener">Slack message format</a>.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">processRecord</span> = (<span class="params">record</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> subject = record.<span class="property">Sns</span>.<span class="property">Subject</span>;</span><br><span class="line">  <span class="keyword">const</span> message = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(record.<span class="property">Sns</span>.<span class="property">Message</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">sendMessage</span>(&#123;</span><br><span class="line">    <span class="attr">text</span>: subject,</span><br><span class="line">    <span class="attr">attachments</span>: [&#123;</span><br><span class="line">      <span class="attr">text</span>: message.<span class="property">NewStateReason</span>,</span><br><span class="line">      <span class="attr">fields</span>: [&#123;</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&#x27;Time&#x27;</span>,</span><br><span class="line">        <span class="attr">value</span>: message.<span class="property">StateChangeTime</span>,</span><br><span class="line">        <span class="attr">short</span>: <span class="literal">true</span>,</span><br><span class="line">      &#125;, &#123;</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&#x27;Alarm&#x27;</span>,</span><br><span class="line">        <span class="attr">value</span>: message.<span class="property">AlarmName</span>,</span><br><span class="line">        <span class="attr">short</span>: <span class="literal">true</span>,</span><br><span class="line">      &#125;, &#123;</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&#x27;Account&#x27;</span>,</span><br><span class="line">        <span class="attr">value</span>: message.<span class="property">AWSAccountId</span>,</span><br><span class="line">        <span class="attr">short</span>: <span class="literal">true</span>,</span><br><span class="line">      &#125;, &#123;</span><br><span class="line">        <span class="attr">title</span>: <span class="string">&#x27;Region&#x27;</span>,</span><br><span class="line">        <span class="attr">value</span>: message.<span class="property">Region</span>,</span><br><span class="line">        <span class="attr">short</span>: <span class="literal">true</span>,</span><br><span class="line">      &#125;],</span><br><span class="line">    &#125;],</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>Finally, each Lambda function needs a handler function. The handler function takes 3 parameters:</p><ol><li>event</li><li>comntext</li><li>callback function</li></ol><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="built_in">exports</span>.<span class="property">event</span> = <span class="function">(<span class="params">event, context, cb</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`event received: <span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(event)&#125;</span>`</span>);</span><br><span class="line">  <span class="title class_">Promise</span>.<span class="title function_">all</span>(event.<span class="property">Records</span>.<span class="title function_">map</span>(processRecord))</span><br><span class="line">    .<span class="title function_">then</span>(<span class="function">() =&gt;</span> <span class="title function_">cb</span>(<span class="literal">null</span>))</span><br><span class="line">    .<span class="title function_">catch</span>(<span class="function">(<span class="params">err</span>) =&gt;</span> <span class="title function_">cb</span>(err));</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>Now, you will use SAM and CloudFormation to define the AWS resources.</p><h2 id="Defining-the-CloudFormation-template-using-SAM"><a href="#Defining-the-CloudFormation-template-using-SAM" class="headerlink" title="Defining the CloudFormation template using SAM"></a>Defining the CloudFormation template using SAM</h2><p>First, you need some boilerplate to enable SAM and configure CloudFormation. Additionally, you will add two parameters to the template:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Transform:</span> <span class="string">&#x27;AWS::Serverless-2016-10-31&#x27;</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&#x27;CloudWatch Alarm to Slack&#x27;</span></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">Threshold:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;Alarm, if your monthly estimated charges are higher&#x27;</span></span><br><span class="line">    <span class="attr">Default:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">Number</span></span><br><span class="line">  <span class="attr">WebhookURL:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;Slack incoming webhook URL&#x27;</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br></pre></td></tr></table></figure><p>You need three resources in your template that:</p><ol><li>SNS Topic</li><li>Lambda Function</li><li>CloudWatch Alarm</li></ol><p>SAM handles to SNS subscription and also takes care of IAM roles and Lambda permissions.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Topic:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::SNS::Topic&#x27;</span></span><br><span class="line">  <span class="attr">LambdaFunction:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Serverless::Function&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Handler:</span> <span class="string">&#x27;handler.event&#x27;</span></span><br><span class="line">      <span class="attr">Runtime:</span> <span class="string">&#x27;nodejs6.10&#x27;</span></span><br><span class="line">      <span class="attr">MemorySize:</span> <span class="number">512</span></span><br><span class="line">      <span class="attr">Timeout:</span> <span class="number">30</span></span><br><span class="line">      <span class="attr">Environment:</span></span><br><span class="line">        <span class="attr">Variables:</span></span><br><span class="line">          <span class="attr">WEBHOOK_URL:</span> <span class="type">!Ref</span> <span class="string">WebhookURL</span></span><br><span class="line">      <span class="attr">Events:</span></span><br><span class="line">        <span class="attr">SNS:</span></span><br><span class="line">          <span class="attr">Type:</span> <span class="string">SNS</span></span><br><span class="line">          <span class="attr">Properties:</span></span><br><span class="line">            <span class="attr">Topic:</span> <span class="type">!Ref</span> <span class="string">Topic</span></span><br><span class="line">  <span class="attr">Alarm:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AlarmActions:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Topic</span></span><br><span class="line">      <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br><span class="line">      <span class="attr">Dimensions:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Currency</span></span><br><span class="line">          <span class="attr">Value:</span> <span class="string">USD</span></span><br><span class="line">      <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">MetricName:</span> <span class="string">EstimatedCharges</span></span><br><span class="line">      <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/Billing&#x27;</span></span><br><span class="line">      <span class="attr">Period:</span> <span class="number">86400</span></span><br><span class="line">      <span class="attr">Statistic:</span> <span class="string">Maximum</span></span><br><span class="line">      <span class="attr">Threshold:</span> <span class="type">!Ref</span> <span class="string">Threshold</span></span><br></pre></td></tr></table></figure><p>That’s it. You can find the full source code on <a href="https://github.com/widdix/cloudwatch-alarm-to-slack" target="_blank" rel="noopener">GitHub</a>. Now you can deploy the solution.</p><h2 id="Deploying-the-solution"><a href="#Deploying-the-solution" class="headerlink" title="Deploying the solution"></a>Deploying the solution</h2><h3 id="Slack-setup"><a href="#Slack-setup" class="headerlink" title="Slack setup"></a>Slack setup</h3><ol><li>Start by setting up an incoming webhook integration in your Slack workspace: <a href="https://my.slack.com/services/new/incoming-webhook/" target="_blank" rel="noopener">https://my.slack.com/services/new/incoming-webhook/</a></li><li>Select a channel or create a new one</li><li>Click on <em>Add Incoming WebHooks integration</em></li><li>You are redirected to a new page where you can see your <em>Webhook URL</em>. Copy the value; you will need it soon.</li></ol><h3 id="AWS-setup"><a href="#AWS-setup" class="headerlink" title="AWS setup"></a>AWS setup</h3><ol><li><a href="https://github.com/widdix/cloudwatch-alarm-to-slack/" target="_blank" rel="noopener">Clone</a> or <a href="https://github.com/widdix/cloudwatch-alarm-to-slack/zipball/master/" target="_blank" rel="noopener">download</a> the sample repository</li><li>Create a S3 bucket for SAM (replace <code>$UniqueSuffix</code> with e.g. your username): <code>aws --region us-east-1 s3 mb s3://cw-to-slack-$UniqueSuffix</code></li><li>Install Node.js dependencies: <code>npm install</code></li><li>Package the Lambda function code (replace <code>$UniqueSuffix</code> with e.g. your username): <code>aws --region us-east-1 cloudformation package --s3-bucket cw-to-slack-$UniqueSuffix  --template-file template.yml --output-template-file template.sam.yml</code></li><li>Deploy the CloudFormation stack (replace <code>$WebhookURL</code> with your URL from Slack): <code>aws --region us-east-1 cloudformation deploy --parameter-overrides &quot;WebhookURL=$WebhookURL&quot; --template-file template.sam.yml --stack-name cw-to-slack --capabilities CAPABILITY_IAM</code></li></ol><h2 id="Further-steps"><a href="#Further-steps" class="headerlink" title="Further steps"></a>Further steps</h2><p>Many features are missing like closing incoming alerts, escalation schedules, and grouping of similar alerts. You can start to implement them on your own, or you can <a href="https://marbot.io/" target="_blank" rel="noopener">use marbot to send CloudWatch alarms to Slack</a>.</p><p>marbot is optimized for small teams. Andreas and I build marbot during the <a href="/marbot-aws-serverless-chatbot-competition/">Serverless Chatbot Competition 2016</a>. We even <a href="/serverless-slack-bot-hero-award/">won a prize</a>!</p><p><a href="https://marbot.io/" target="_blank" rel="noopener">Take care of your CloudWatch Alarms with marbot. Start today for free</a>!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Passwordless database authentication for AWS Lambda</title>
      <link>https://cloudonaut.io/passwordless-database-authentication-for-aws-lambda/</link>
      <description>
        <![CDATA[<p>Does your serverless application need to access an RDS database? Where do you store the username and the password required to authenticat]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/rds/">rds</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/passwordless-database-authentication-for-aws-lambda/</guid>
      <pubDate>Thu, 19 Oct 2017 14:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Does your serverless application need to access an RDS database? Where do you store the username and the password required to authenticate with the database? Storing the password in plain text within your source code should not be an option. Same is true for the environment variables of your Lambda function. Using KMS to encrypt the database password is possible but cumbersome. Lucky you, there is an elegant solution to the problem of authenticating a Lambda function with an RDS database.</p><p>Instead of using a conventional database user with password make use of <a href="http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html" target="_blank" rel="noopener">IAM Database Authentication for MySQL and Amazon Aurora</a>. As shown in the following figure using an IAM role to authenticate at an RDS database is possible. You no longer have to cope with a database password, you are using the IAM role of your Lambda function instead.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/10/passwordless-database-authentication@730w.webp 730w, /images/2017/10/passwordless-database-authentication@730w2x.webp 1460w, /images/2017/10/passwordless-database-authentication@610w.webp 610w, /images/2017/10/passwordless-database-authentication@610w2x.webp 1220w, /images/2017/10/passwordless-database-authentication@450w.webp 450w, /images/2017/10/passwordless-database-authentication@450w2x.webp 900w, /images/2017/10/passwordless-database-authentication@330w.webp 330w, /images/2017/10/passwordless-database-authentication@330w2x.webp 660w, /images/2017/10/passwordless-database-authentication@545w.webp 545w, /images/2017/10/passwordless-database-authentication@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/10/passwordless-database-authentication@730w.png 730w, /images/2017/10/passwordless-database-authentication@730w2x.png 1460w, /images/2017/10/passwordless-database-authentication@610w.png 610w, /images/2017/10/passwordless-database-authentication@610w2x.png 1220w, /images/2017/10/passwordless-database-authentication@450w.png 450w, /images/2017/10/passwordless-database-authentication@450w2x.png 900w, /images/2017/10/passwordless-database-authentication@330w.png 330w, /images/2017/10/passwordless-database-authentication@330w2x.png 660w, /images/2017/10/passwordless-database-authentication@545w.png 545w, /images/2017/10/passwordless-database-authentication@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/10/passwordless-database-authentication.png" alt="Overview of passwordless database authentication" title="Overview of passwordless database authentication"></picture></p><p>The following instructions guide you through configuring IAM database authentication for a Lambda function written in Node.js accessing an RDS database cluster with Aurora (MySQL) engine.</p><p>Before we start, let’s talk about the restrictions when using IAM database authentication:</p><ul><li>Using the MySQL or Aurora RDS engine is required (MySQL &gt;&#x3D;5.6.34, MySQL &gt;&#x3D;5.7.16, Aurora &gt;1.10).</li><li>A Secure Sockets Layer (SSL) database connection is needed.</li><li>Smallest database instance types do not support IAM database authentication. <code>db.t1.micro</code> and <code>db.m1.small</code> instance types are excluded for MySQL. The <code>db.t2.small</code> instance type is excluded for Aurora.</li><li>AWS recommends creating no more than 20 database connections per second when using IAM database authentication.</li></ul><h2 id="Step-1-Enabling-IAM-database-authentication"><a href="#Step-1-Enabling-IAM-database-authentication" class="headerlink" title="Step 1: Enabling IAM database authentication"></a>Step 1: Enabling IAM database authentication</h2><p>First of all, you need to enable IAM database authentication. Type in the following command into your terminal to enable IAM database authentication for your Aurora database cluster. Replace <code>&lt;DB_CLUSER_ID&gt;</code> with the identifier of your Aurora database cluster.</p><figure class="highlight stata"><table><tr><td class="code"><pre><span class="line">aws rds modify-<span class="keyword">db</span>-<span class="keyword">cluster</span> --<span class="keyword">db</span>-<span class="keyword">cluster</span>-identifier &lt;DB_CLUSTER_ID&gt; --enable-iam-database-authentication --apply-immediately </span><br></pre></td></tr></table></figure><p>See <a href="http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html#UsingWithRDS.IAMDBAuth.Enabling" target="_blank" rel="noopener">Enabling and Disabling IAM Database Authentication</a> if you need more detailed information.</p><h2 id="Step-2-Preparing-a-database-user"><a href="#Step-2-Preparing-a-database-user" class="headerlink" title="Step 2: Preparing a database user"></a>Step 2: Preparing a database user</h2><p>Next, you need to connect to your database and create a user using the AWS authentication plugin.</p><p>The following SQL statement creates a database user named <code>lambda</code>. Instead of specifying a password, the <code>AWSAuthenticationPlugin</code> is used for identifying the user. Replace <code>&lt;DB_NAME&gt;</code> with the name of the database you want to grant the user access to.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">USER</span> <span class="string">&#x27;lambda&#x27;</span> IDENTIFIED <span class="keyword">WITH</span> AWSAuthenticationPlugin <span class="keyword">as</span> <span class="string">&#x27;RDS&#x27;</span>;</span><br><span class="line"><span class="keyword">GRANT</span> <span class="keyword">ALL</span> <span class="keyword">PRIVILEGES</span> <span class="keyword">ON</span> &lt;DB_NAME&gt;.* <span class="keyword">TO</span> <span class="string">&#x27;lambda&#x27;</span>@<span class="string">&#x27;%&#x27;</span>;</span><br><span class="line">FLUSH <span class="keyword">PRIVILEGES</span>;</span><br></pre></td></tr></table></figure><p>I’m using a database named <code>lambda_test</code>. Therefore, my SQL query looks as follows.</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">USER</span> <span class="string">&#x27;lambda&#x27;</span> IDENTIFIED <span class="keyword">WITH</span> AWSAuthenticationPlugin <span class="keyword">as</span> <span class="string">&#x27;RDS&#x27;</span>;</span><br><span class="line"><span class="keyword">GRANT</span> <span class="keyword">ALL</span> <span class="keyword">PRIVILEGES</span> <span class="keyword">ON</span> lambda_test.* <span class="keyword">TO</span> <span class="string">&#x27;lambda&#x27;</span>@<span class="string">&#x27;%&#x27;</span>;</span><br><span class="line">FLUSH <span class="keyword">PRIVILEGES</span>;</span><br></pre></td></tr></table></figure><h2 id="Step-3-Creating-an-IAM-role"><a href="#Step-3-Creating-an-IAM-role" class="headerlink" title="Step 3: Creating an IAM role"></a>Step 3: Creating an IAM role</h2><p>Probably, you have already configured an IAM role for your Lambda function. To be able to authenticate with the RDS database you need to add an IAM policy to the IAM role.</p><p>The following snippet shows a policy granting access to authenticate with an RDS database. Replace <code>&lt;REGION&gt;</code> with the region the database is running in, <code>&lt;AWS_ACCOUNT_ID&gt;</code> with the account id of your AWS account and <code>&lt;DB_RESOURCE_ID&gt;</code> with the resource id of your database cluster. Also, don’t forget to replace <code>&lt;DB_USERNAME&gt;</code> with the username <code>lambda</code> created in step 2.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2012-10-17&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Statement&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Action&quot;</span>: <span class="string">&quot;rds-db:connect&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;arn:aws:rds-db:&lt;REGION&gt;:&lt;AWS_ACCOUNT_ID&gt;:dbuser:&lt;DB_RESOURCE_ID&gt;/&lt;DB_USERNAME&gt;&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>My IAM policy looks like this, for example.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;2012-10-17&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Statement&quot;</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="string">&quot;Effect&quot;</span>: <span class="string">&quot;Allow&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Action&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;rds-db:connect&quot;</span></span><br><span class="line">      ],</span><br><span class="line">      <span class="string">&quot;Resource&quot;</span>: [</span><br><span class="line">        <span class="string">&quot;arn:aws:rds-db:eu-west-1:486555357186:dbuser:cluster-PWZUABBM2Y3H354WLBRDGBL7MI/lambda&quot;</span></span><br><span class="line">      ]</span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Warning: It might take a few minutes until changes to your IAM role are in effect. Be careful when debugging authentication issues. Sometimes getting a cup of coffee or tea is solving the problem. ;-)</strong></p><h2 id="Step-4-Connecting-to-the-database"><a href="#Step-4-Connecting-to-the-database" class="headerlink" title="Step 4: Connecting to the database"></a>Step 4: Connecting to the database</h2><p>Finally, you can write the source code for your Lambda function. Install the <code>mysql2</code> module used to establish a database connection to Aurora.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">npm</span> install mysql2@<span class="number">1</span>.<span class="number">4</span>.<span class="number">2</span></span><br></pre></td></tr></table></figure><p>The following snippet contains the source code needed to access the RDS database from your Lambda function. Note you have to edit the database connection details as well as the query (see <code>TODO</code>).</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> mysql = <span class="built_in">require</span>(<span class="string">&#x27;mysql2&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// TODO use the details of your database connection</span></span><br><span class="line"><span class="keyword">const</span> region = <span class="string">&#x27;eu-west-1&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> dbPort = <span class="number">3306</span>;</span><br><span class="line"><span class="keyword">const</span> dbUsername = <span class="string">&#x27;lambda&#x27;</span>; <span class="comment">// the name of the database user you created in step 2</span></span><br><span class="line"><span class="keyword">const</span> dbName = <span class="string">&#x27;lambda_test&#x27;</span>; <span class="comment">// the name of the database your database user is granted access to</span></span><br><span class="line"><span class="keyword">const</span> dbEndpoint = <span class="string">&#x27;lambdatest-cluster-1.cluster-c8o7oze6xoxs.eu-west-1.rds.amazonaws.com&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span>.<span class="property">handler</span> = <span class="function">(<span class="params">event, context, cb</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">var</span> signer = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="property">RDS</span>.<span class="title class_">Signer</span>();</span><br><span class="line">  signer.<span class="title function_">getAuthToken</span>(&#123; <span class="comment">// uses the IAM role access keys to create an authentication token</span></span><br><span class="line">    <span class="attr">region</span>: region,</span><br><span class="line">    <span class="attr">hostname</span>: dbEndpoint,</span><br><span class="line">    <span class="attr">port</span>: dbPort,</span><br><span class="line">    <span class="attr">username</span>: dbUsername</span><br><span class="line">  &#125;, <span class="keyword">function</span>(<span class="params">err, token</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`could not get auth token: <span class="subst">$&#123;err&#125;</span>`</span>);</span><br><span class="line">      <span class="title function_">cb</span>(err);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">var</span> connection = mysql.<span class="title function_">createConnection</span>(&#123;</span><br><span class="line">        <span class="attr">host</span>: dbEndpoint,</span><br><span class="line">        <span class="attr">port</span>: dbPort,</span><br><span class="line">        <span class="attr">user</span>: dbUsername,</span><br><span class="line">        <span class="attr">password</span>: token,</span><br><span class="line">        <span class="attr">database</span>: dbName,</span><br><span class="line">        <span class="attr">ssl</span>: <span class="string">&#x27;Amazon RDS&#x27;</span>,</span><br><span class="line">        <span class="attr">authSwitchHandler</span>: <span class="keyword">function</span> (<span class="params">data, cb</span>) &#123; <span class="comment">// modifies the authentication handler</span></span><br><span class="line">          <span class="keyword">if</span> (data.<span class="property">pluginName</span> === <span class="string">&#x27;mysql_clear_password&#x27;</span>) &#123; <span class="comment">// authentication token is sent in clear text but connection uses SSL encryption</span></span><br><span class="line">            <span class="title function_">cb</span>(<span class="literal">null</span>, <span class="title class_">Buffer</span>.<span class="title function_">from</span>(token + <span class="string">&#x27;\0&#x27;</span>));</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;);</span><br><span class="line">      connection.<span class="title function_">connect</span>();</span><br><span class="line">      <span class="comment">// TODO replace with your SQL query</span></span><br><span class="line">      connection.<span class="title function_">query</span>(<span class="string">&#x27;SELECT * FROM lambda_test.test&#x27;</span>, <span class="keyword">function</span> (<span class="params">err, results, fields</span>) &#123;</span><br><span class="line">        connection.<span class="title function_">end</span>();</span><br><span class="line">        <span class="keyword">if</span> (err) &#123;</span><br><span class="line">          <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`could not execute query: <span class="subst">$&#123;err&#125;</span>`</span>);</span><br><span class="line">          <span class="title function_">cb</span>(err);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          <span class="title function_">cb</span>(<span class="literal">undefined</span>, results);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>The IAM database authentication is superseding handling the database password within your serverless application. All you need is to attach an IAM role to your Lambda function.</p><p>Using an authentication token instead of a password increases security:</p><ul><li>You don’t have to store the password in your source code or the Lambda function’s environment variables.</li><li>The authentication token is a generated secret (Signature Version 4 signing process).</li><li>The authentication token has a limited lifetime (15 minutes).</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>Engaging your users with AWS Step Functions</title>
      <link>https://cloudonaut.io/engaging-your-users-with-aws-step-functions/</link>
      <description>
        <![CDATA[<p>Imagine a new user signs up for your service. You send an automated welcome message to your new user explaining how the service works. Bu]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/serverless/">Serverless</category>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/step-functions/">step-functions</category>
      <guid isPermaLink="true">https://cloudonaut.io/engaging-your-users-with-aws-step-functions/</guid>
      <pubDate>Fri, 13 Oct 2017 13:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Imagine a new user signs up for your service. You send an automated welcome message to your new user explaining how the service works. But what if your user struggles with the first steps? You want to send a second email with additional information. To abstract this a little bit, the following steps are needed:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/10/engaging_your_users_steps@730w.webp 730w, /images/2017/10/engaging_your_users_steps@730w2x.webp 1460w, /images/2017/10/engaging_your_users_steps@610w.webp 610w, /images/2017/10/engaging_your_users_steps@610w2x.webp 1220w, /images/2017/10/engaging_your_users_steps@450w.webp 450w, /images/2017/10/engaging_your_users_steps@450w2x.webp 900w, /images/2017/10/engaging_your_users_steps@330w.webp 330w, /images/2017/10/engaging_your_users_steps@330w2x.webp 660w, /images/2017/10/engaging_your_users_steps@545w.webp 545w, /images/2017/10/engaging_your_users_steps@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/10/engaging_your_users_steps@730w.png 730w, /images/2017/10/engaging_your_users_steps@730w2x.png 1460w, /images/2017/10/engaging_your_users_steps@610w.png 610w, /images/2017/10/engaging_your_users_steps@610w2x.png 1220w, /images/2017/10/engaging_your_users_steps@450w.png 450w, /images/2017/10/engaging_your_users_steps@450w2x.png 900w, /images/2017/10/engaging_your_users_steps@330w.png 330w, /images/2017/10/engaging_your_users_steps@330w2x.png 660w, /images/2017/10/engaging_your_users_steps@545w.png 545w, /images/2017/10/engaging_your_users_steps@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/10/engaging_your_users_steps.png" alt="Steps to engage your users" title="Steps to engage your users"></picture></p><ol><li>Send a welcome message to the new user.</li><li>Wait some time.</li><li>Check if the user completed the initial steps.<br>  a. If yes, done.<br>  b. If no, continue.</li><li>Send a message with additional information to the new user.</li><li>Wait some time.</li><li>Check if the user completed the initial steps.<br>  a. If yes, done.<br>  b. If no, continue.</li><li>Send a message to the new user offering a Chime call.</li></ol><p><strong>This is nothing more than a state machine.</strong> It has a start (new user signed up) and an end (the last message was sent) and a few state transitions in between. With AWS Step Functions, you can implement a state machine. To do so, you have to translate the steps into the right format and implement the business logic. I will use AWS Lambda to implement the business logic in this post. Let’s get started.</p><h2 id="Anatomy-of-a-state-machine-in-AWS-Step-Functions"><a href="#Anatomy-of-a-state-machine-in-AWS-Step-Functions" class="headerlink" title="Anatomy of a state machine in AWS Step Functions"></a>Anatomy of a state machine in AWS Step Functions</h2><p>A state machine in AWS Step Functions can take input data in JSON and consists of states:</p><ul><li>There is one start state that gets the input when starting the state machine.</li><li>Each state can either be an end state or will point to the next state.</li><li>There are one or many end states.</li><li>A state is of a specific type.</li><li>By default, the input of a state is outputted. Some states change this.</li></ul><p>In this example, four different state types are used, but there are <a href="http://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-states.html" target="_blank" rel="noopener">much more</a>. The four used state types are:</p><table class="table table-striped table-responsive"><thead><tr><th>Type</th><th>Description</th></tr></thead><tbody><tr><td>Task</td><td>Calls a Lambda function.The event of the Lambda function is the input of the state. By default, the output of the Lambda function is the output of the state. If the Lambda function fails, it can be retried.</td></tr><tr><td>Wait</td><td>Waits for a specific amount of time in seconds. You are not billed for the waiting time.</td></tr><tr><td>Choice</td><td>So far, a state has only one next state. But sometimes you need to make a choice (e.g., if the user completed initial steps, then ..., else ...). Depending on a precondition, you can have several next states.</td></tr><tr><td>Succeed</td><td>Indicates a successful end of a state machine.</td></tr></tbody></table><p>Now, you have to map the engaging steps to states.</p><h2 id="Example-state-machine-in-AWS-Step-Functions"><a href="#Example-state-machine-in-AWS-Step-Functions" class="headerlink" title="Example state machine in AWS Step Functions"></a>Example state machine in AWS Step Functions</h2><p>The start state is <code>SendMessage1</code>.</p><table class="table table-striped table-responsive"><thead><tr><th>Id</th><th>Type</th><th>Description</th><th>Next</th></tr></thead><tbody><tr><td><code>SendMessage1</code></td><td>Task</td><td>Send a welcome message to the new user.</td><td><code>Wait1</code></td></tr><tr><td><code>Wait1</code></td><td>Wait</td><td>Wait some time.</td><td><code>FetchActivityCount1</code></td></tr><tr><td><code>FetchActivityCount1</code></td><td>Task</td><td>Fetch number of activities the new user performed.</td><td><code>CheckActivityCount1</code></td></tr><tr><td><code>CheckActivityCount1</code></td><td>Choice</td><td>Did the user completed the initial steps?</td><td>If yes, then <code>Done</code>, else <code>SendMessage2</code></td></tr><tr><td><code>SendMessage2</code></td><td>Task</td><td>Send a message with additional information to the new user.</td><td><code>Wait2</code></td></tr><tr><td><code>Wait2</code></td><td>Wait</td><td>Wait some time.</td><td><code>FetchActivityCount2</code></td></tr><tr><td><code>FetchActivityCount2</code></td><td>Task</td><td>Fetch number of activities the new user performed.</td><td><code>CheckActivityCount2</code></td></tr><tr><td><code>CheckActivityCount2</code></td><td>Choice</td><td>Did the user completed the initial steps?</td><td>If yes, then <code>Done</code>, else <code>SendMessage3</code></td></tr><tr><td><code>SendMessage3</code></td><td>Task</td><td>Send a message to the new user offering a Chime call.</td><td><code>Done</code></td></tr><tr><td><code>Done</code></td><td>Succeed</td><td>Done.</td><td>-</td></tr></tbody></table><p>Now, the state machine is defined. <strong>Are you surprised by states <code>FetchActivityCount1</code> and <code>CheckActivityCount1</code>?</strong> The step <em>Check if the user completed the initial steps</em> was translated to two states:</p><ul><li>Task <code>FetchActivityCount1</code>: Fetch number of activities the new user performed.</li><li>Choice <code>CheckActivityCount1</code>: Did the user completed the initial steps?.</li></ul><p>The reason for this is that a state can either do something (like getting the number of activities performed by the user from the database) or it can make a choice. You can not do both in a single state. Also, the Lambda function cannot perform that choice for you. Only the state machine can make a choice based on input data.</p><p>Now, the business logic (states of type Task) needs to be implemented.</p><h2 id="Implementing-tasks"><a href="#Implementing-tasks" class="headerlink" title="Implementing tasks"></a>Implementing tasks</h2><p>A task can either call a Lambda function or an <a href="http://docs.aws.amazon.com/step-functions/latest/dg/concepts-activities.html" target="_blank" rel="noopener">activity</a>. If your business logic cannot be implemented with Lambda, you can fall back to activities. I will not cover activities in this example.</p><h3 id="Send-welcome-message"><a href="#Send-welcome-message" class="headerlink" title="Send welcome message"></a>Send welcome message</h3><p>I provide a dummy implementation here in Node.js that fails in 30% of the time to demonstrate how retries work.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span>.<span class="property">handler</span> = <span class="function">(<span class="params">event, context, cb</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(event));</span><br><span class="line">  <span class="keyword">if</span> (<span class="title class_">Math</span>.<span class="title function_">random</span>() &lt; <span class="number">0.3</span>) &#123; <span class="comment">// fail 30% of the time</span></span><br><span class="line">    <span class="title function_">cb</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;error happened&#x27;</span>));</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">cb</span>(<span class="literal">null</span>, &#123;&#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="Fetch-number-of-activities"><a href="#Fetch-number-of-activities" class="headerlink" title="Fetch number of activities"></a>Fetch number of activities</h3><p>I provide a dummy implementation here in Node.js that fails in 30% of the time and returns that the user did not complete any activities in 50% of the time.</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span>.<span class="property">handler</span> = <span class="function">(<span class="params">event, context, cb</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(event));</span><br><span class="line">  <span class="keyword">if</span> (<span class="title class_">Math</span>.<span class="title function_">random</span>() &lt; <span class="number">0.3</span>) &#123;</span><br><span class="line">    <span class="title function_">cb</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;error happened&#x27;</span>)); <span class="comment">// fail 30% of the time</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">cb</span>(<span class="literal">null</span>, &#123;<span class="attr">activities</span>: <span class="title class_">Date</span>.<span class="title function_">now</span>() % <span class="number">2</span>&#125;); <span class="comment">// return zero 50% of the time</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>So far, the state machine is not really defined in a machine readable format. You will change this in the next section.</p><h2 id="Translate-the-state-machine-to-JSON"><a href="#Translate-the-state-machine-to-JSON" class="headerlink" title="Translate the state machine to JSON"></a>Translate the state machine to JSON</h2><p>State machines are defined in a JSON document like this:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Comment&quot;</span><span class="punctuation">:</span> <span class="string">&quot;AWS Step Functions Example&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;StartAt&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SendMessage1&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;States&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    </span><br><span class="line"></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>The <code>StartAt</code> property defines the first state in the state machine. Let’s see how states are defined.</p><p>The first state is <code>SendMessage1</code> of type <code>Task</code>:</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line"><span class="string">&quot;SendMessage1&quot;</span>: &#123;</span><br><span class="line">  <span class="string">&quot;Type&quot;</span>: <span class="string">&quot;Task&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;&lt;Lambda ARN&gt;&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Retry&quot;</span>: [&#123;</span><br><span class="line">    <span class="string">&quot;ErrorEquals&quot;</span>: [<span class="string">&quot;States.TaskFailed&quot;</span>],</span><br><span class="line">    <span class="string">&quot;IntervalSeconds&quot;</span>: <span class="number">2</span>,</span><br><span class="line">    <span class="string">&quot;MaxAttempts&quot;</span>: <span class="number">16</span>,</span><br><span class="line">    <span class="string">&quot;BackoffRate&quot;</span>: <span class="number">2</span></span><br><span class="line">  &#125;],</span><br><span class="line">  <span class="string">&quot;Next&quot;</span>: <span class="string">&quot;Wait1&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>The <code>Resource</code> property contains the ARN of the Lambda function (e.g., <code>arn:aws:lambda:$region:$account-id:function:$function-name</code>).</li><li>The <code>Retry</code> property defines that if the Lambda function returns an error.<ul><li>The first retry is performed after <code>IntervalSeconds</code>.</li><li>The next retries performed after <code>IntervalSeconds*BackoffRate*NoOfRetry</code>.</li><li>Only retry <code>MaxAttempts</code> times.</li></ul></li><li>The <code>Next</code> property points to the next state.</li></ul><p>Now, the message is sent, so we have to wait.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="attr">&quot;Wait1&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Wait&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Seconds&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Next&quot;</span><span class="punctuation">:</span> <span class="string">&quot;FetchActivityCount1&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>After that, we have to get the number of activities the user did (e.g., query a database).</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="attr">&quot;FetchActivityCount1&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Task&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&lt;Lambda ARN&gt;&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Next&quot;</span><span class="punctuation">:</span> <span class="string">&quot;CheckActivityCount1&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>After we have the information, it’s time to make a decision:</p><figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="string">&quot;CheckActivityCount1&quot;</span>: &#123;</span><br><span class="line">  <span class="string">&quot;Type&quot;</span>: <span class="string">&quot;Choice&quot;</span>,</span><br><span class="line">  <span class="string">&quot;Choices&quot;</span>: [&#123;</span><br><span class="line">    <span class="string">&quot;Variable&quot;</span>: <span class="string">&quot;<span class="variable">$</span>.activities&quot;</span>,</span><br><span class="line">    <span class="string">&quot;NumericEquals&quot;</span>: <span class="number">0</span>,</span><br><span class="line">    <span class="string">&quot;Next&quot;</span>: <span class="string">&quot;SendMessage2&quot;</span></span><br><span class="line">  &#125;],</span><br><span class="line">  <span class="string">&quot;Default&quot;</span>: <span class="string">&quot;Done&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>The <code>Choices</code> property defines an array of rules. Each rule:<ul><li>Selects a property from the input using JsonPath in <code>Variable</code>.</li><li>Compares it, e.g., with <code>NumericEquals</code> or <a href="http://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-choice-state.html#amazon-states-language-choice-state-rules" target="_blank" rel="noopener">many others</a>.</li><li>Defines the next state in <code>Next</code>.</li></ul></li><li>The <code>Default</code> property indicates the state of no other state was selected in <code>Choices</code>.</li></ul><p>Finally, the last state is reached.</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="attr">&quot;Done&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Succeed&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Now it’s time to wire everything together with CloudFormation.</p><h2 id="CloudFormation-template"><a href="#CloudFormation-template" class="headerlink" title="CloudFormation template"></a>CloudFormation template</h2><p>The following is only an excerpt of the <a href="https://github.com/widdix/aws-step-functions-example" target="_blank" rel="noopener">full CloudFormation template</a>.</p><figure class="highlight nix"><table><tr><td class="code"><pre><span class="line">---</span><br><span class="line"><span class="params">AWSTemplateFormatVersion:</span> &#x27;<span class="number">201</span>0-<span class="number">0</span>9-<span class="number">09</span>&#x27;</span><br><span class="line"><span class="params">Description:</span> &#x27;AWS Step Functions Example&#x27;</span><br><span class="line"><span class="params">Resources:</span></span><br><span class="line">  <span class="comment"># Step functions state machine</span></span><br><span class="line">  <span class="params">StateMachineOnboardingInstall:</span></span><br><span class="line">    <span class="params">Type:</span> &#x27;AWS::StepFunctions::StateMachine&#x27;</span><br><span class="line">    <span class="params">Properties:</span></span><br><span class="line">      <span class="params">DefinitionString:</span> <span class="operator">!</span>Sub |</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="string">&quot;Comment&quot;</span>: <span class="string">&quot;AWS Step Functions Example&quot;</span>,</span><br><span class="line">          <span class="string">&quot;StartAt&quot;</span>: <span class="string">&quot;SendMessage1&quot;</span>,</span><br><span class="line">          <span class="string">&quot;Version&quot;</span>: <span class="string">&quot;1.0&quot;</span>,</span><br><span class="line">          <span class="string">&quot;States&quot;</span>: &#123;</span><br><span class="line">            <span class="string">&quot;SendMessage1&quot;</span>: &#123;</span><br><span class="line">              <span class="string">&quot;Type&quot;</span>: <span class="string">&quot;Task&quot;</span>,</span><br><span class="line">              <span class="string">&quot;Resource&quot;</span>: <span class="string">&quot;<span class="subst">$&#123;FunctionSendMessage.Arn&#125;</span>&quot;</span>,</span><br><span class="line">              <span class="string">&quot;Retry&quot;</span>: [&#123;</span><br><span class="line">                <span class="string">&quot;ErrorEquals&quot;</span>: [<span class="string">&quot;States.TaskFailed&quot;</span>],</span><br><span class="line">                <span class="string">&quot;IntervalSeconds&quot;</span>: <span class="number">2</span>,</span><br><span class="line">                <span class="string">&quot;MaxAttempts&quot;</span>: <span class="number">16</span>,</span><br><span class="line">                <span class="string">&quot;BackoffRate&quot;</span>: <span class="number">2</span></span><br><span class="line">              &#125;],</span><br><span class="line">              <span class="string">&quot;Next&quot;</span>: <span class="string">&quot;Wait1&quot;</span></span><br><span class="line">            &#125;,</span><br><span class="line">            <span class="comment"># [...]</span></span><br><span class="line">            <span class="string">&quot;Done&quot;</span>: &#123;</span><br><span class="line">              <span class="string">&quot;Type&quot;</span>: <span class="string">&quot;Succeed&quot;</span></span><br><span class="line">            &#125;</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      <span class="params">RoleArn:</span> <span class="operator">!</span>GetAtt &#x27;RoleOnboardingInstall.Arn&#x27;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  <span class="comment"># Lambda functions</span></span><br><span class="line">  <span class="params">FunctionSendMessage:</span></span><br><span class="line">    <span class="params">Type:</span> &#x27;AWS::Lambda::Function&#x27;</span><br><span class="line">    <span class="params">Properties:</span></span><br><span class="line">      <span class="comment"># [...]</span></span><br><span class="line">  <span class="params">FunctionFetchActivityCount:</span></span><br><span class="line">    <span class="params">Type:</span> &#x27;AWS::Lambda::Function&#x27;</span><br><span class="line">    <span class="params">Properties:</span></span><br><span class="line">      <span class="comment"># [...]</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  <span class="comment"># IAM roles</span></span><br><span class="line">  <span class="params">RoleOnboardingInstall:</span></span><br><span class="line">    <span class="params">Type:</span> &#x27;AWS::IAM::Role&#x27;</span><br><span class="line">    <span class="params">Properties:</span></span><br><span class="line">      <span class="params">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="params">Version:</span> &#x27;<span class="number">201</span>2-<span class="number">1</span>0-<span class="number">17</span>&#x27;</span><br><span class="line">        <span class="params">Statement:</span></span><br><span class="line">        <span class="operator">-</span> <span class="params">Effect:</span> Allow</span><br><span class="line">          <span class="params">Principal:</span></span><br><span class="line">            <span class="params">Service:</span> <span class="operator">!</span>Sub &#x27;states.$&#123;AWS::Region&#125;.amazonaws.com&#x27;</span><br><span class="line">          <span class="params">Action:</span> &#x27;sts:AssumeRole&#x27;</span><br><span class="line">      <span class="params">Policies:</span></span><br><span class="line">      <span class="operator">-</span> <span class="params">PolicyName:</span> lambda</span><br><span class="line">        <span class="params">PolicyDocument:</span></span><br><span class="line">          <span class="params">Statement:</span></span><br><span class="line">          <span class="operator">-</span> <span class="params">Effect:</span> Allow</span><br><span class="line">            <span class="params">Action:</span> &#x27;lambda:InvokeFunction&#x27;</span><br><span class="line">            <span class="params">Resource:</span></span><br><span class="line">            <span class="operator">-</span> <span class="operator">!</span>GetAtt &#x27;FunctionSendMessage.Arn&#x27;</span><br><span class="line">            <span class="operator">-</span> <span class="operator">!</span>GetAtt &#x27;FunctionFetchActivityCount.Arn&#x27;</span><br><span class="line">  <span class="params">RoleSendMessage:</span></span><br><span class="line">    <span class="params">Type:</span> &#x27;AWS::IAM::Role&#x27;</span><br><span class="line">    <span class="params">Properties:</span></span><br><span class="line">      <span class="params">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="params">Version:</span> &#x27;<span class="number">201</span>2-<span class="number">1</span>0-<span class="number">17</span>&#x27;</span><br><span class="line">        <span class="params">Statement:</span></span><br><span class="line">        <span class="operator">-</span> <span class="params">Effect:</span> Allow</span><br><span class="line">          <span class="params">Principal:</span></span><br><span class="line">            <span class="params">Service:</span> &#x27;lambda.amazonaws.com&#x27;</span><br><span class="line">          <span class="params">Action:</span> &#x27;sts:AssumeRole&#x27;</span><br><span class="line">      <span class="params">ManagedPolicyArns:</span></span><br><span class="line">      <span class="operator">-</span> &#x27;arn:aws:iam::aws:policy<span class="operator">/</span>service-role<span class="operator">/</span>AWSLambdaBasicExecutionRole&#x27;</span><br><span class="line">  <span class="params">RoleFetchActivityCount:</span></span><br><span class="line">    <span class="params">Type:</span> &#x27;AWS::IAM::Role&#x27;</span><br><span class="line">    <span class="params">Properties:</span></span><br><span class="line">      <span class="comment"># [...]</span></span><br></pre></td></tr></table></figure><p style="float: left;"><img src="/images/2017/10/aws_step_functions_state_machine.png" alt="AWS Step Functions State Machine"></p><h2 id="Installation"><a href="#Installation" class="headerlink" title="Installation"></a>Installation</h2><p>Download the <a href="https://github.com/widdix/aws-step-functions-example" target="_blank" rel="noopener">source code</a> an create a stack:</p><figure class="highlight dsconfig"><table><tr><td class="code"><pre><span class="line"><span class="string">aws</span> <span class="string">cloudformation</span> <span class="built_in">create-stack</span> <span class="built_in">--stack-name</span> <span class="string">example</span> <span class="built_in">--template-body</span> <span class="string">file</span>://<span class="string">template</span>.<span class="string">yml</span> <span class="built_in">--capabilities</span> <span class="string">CAPABILITY_IAM</span></span><br><span class="line"><span class="string">aws</span> <span class="string">cloudformation</span> <span class="string">wait</span> <span class="string">stack-create-complete</span> <span class="built_in">--stack-name</span> <span class="string">example</span></span><br></pre></td></tr></table></figure><p>After some minutes, CloudFormation created a bunch of Lambda functions, IAM roles, and a State Machine for you.</p><h2 id="Creating-an-execution"><a href="#Creating-an-execution" class="headerlink" title="Creating an execution"></a>Creating an execution</h2><p>To create a state machine execution:</p><ol><li>Visit the <a href="https://console.aws.amazon.com/states/home" target="_blank" rel="noopener">Step Functions Management Console</a>.</li><li>Click on the only state machine.</li><li>Press the <strong>New execution</strong> button.</li><li>Supply an <strong>Execution id</strong> (e.g., 1).</li><li>Press the <strong>Start Execution</strong> button.</li></ol><p>Depending on chance, you will take one of many paths trough the state machine (keep in mind that Lambdas fail in 30% of the time and return no or one activity by chance). Therefore, our execution graph will likely look slightly different.</p><p style="clear: both;"></p><p>One thing that I want to highlight is the retry mechanism for <code>Task</code> states. Below the <strong>Visual Workflow</strong>, you can see a full log of the execution. Mine looked like this:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/10/aws_step_functions_execution_log@730w.webp 730w, /images/2017/10/aws_step_functions_execution_log@730w2x.webp 1460w, /images/2017/10/aws_step_functions_execution_log@610w.webp 610w, /images/2017/10/aws_step_functions_execution_log@610w2x.webp 1220w, /images/2017/10/aws_step_functions_execution_log@450w.webp 450w, /images/2017/10/aws_step_functions_execution_log@450w2x.webp 900w, /images/2017/10/aws_step_functions_execution_log@330w.webp 330w, /images/2017/10/aws_step_functions_execution_log@330w2x.webp 660w, /images/2017/10/aws_step_functions_execution_log@545w.webp 545w, /images/2017/10/aws_step_functions_execution_log@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/10/aws_step_functions_execution_log@730w.png 730w, /images/2017/10/aws_step_functions_execution_log@730w2x.png 1460w, /images/2017/10/aws_step_functions_execution_log@610w.png 610w, /images/2017/10/aws_step_functions_execution_log@610w2x.png 1220w, /images/2017/10/aws_step_functions_execution_log@450w.png 450w, /images/2017/10/aws_step_functions_execution_log@450w2x.png 900w, /images/2017/10/aws_step_functions_execution_log@330w.png 330w, /images/2017/10/aws_step_functions_execution_log@330w2x.png 660w, /images/2017/10/aws_step_functions_execution_log@545w.png 545w, /images/2017/10/aws_step_functions_execution_log@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/10/aws_step_functions_execution_log.png" alt="AWS Step Functions Execution Log" title="AWS Step Functions Execution Log"></picture></p><ul><li>In line 11, the Lambda has executed the first time, but it failed in line 12 at 8:22:43.</li><li>In line 14, the Lambda has executed again at 8:22:45 (exactly 2 seconds later, as defined in the <code>Retry</code> property!).</li><li>Line 15 tells us that this time, the Lambda executed without an error.</li></ul><p>Keep in mind that your log will look different. But you likely see log types of <code>LambdaFunctionFailed</code>. If no, create a few more execution and look at them. </p><h2 id="Clean-up"><a href="#Clean-up" class="headerlink" title="Clean up"></a>Clean up</h2><p>Don’t forget to cleanup the CloudFormation stack:</p><figure class="highlight dsconfig"><table><tr><td class="code"><pre><span class="line"><span class="string">aws</span> <span class="string">cloudformation</span> <span class="built_in">delete-stack</span> <span class="built_in">--stack-name</span> <span class="string">example</span></span><br><span class="line"><span class="string">aws</span> <span class="string">cloudformation</span> <span class="string">wait</span> <span class="string">stack-delete-complete</span> <span class="built_in">--stack-name</span> <span class="string">example</span></span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>In this post you learned, that:</p><ul><li>You can implement state machines with AWS Step Functions.</li><li>Each state can do different things depending on the <code>Type</code> of the state.</li><li>A Lambda function can be called from a state of type <code>Task</code> and can be retried in the case of a failure.</li><li>The <code>Choice</code> state type can select the next state based in input data.</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>A simple way to manage log messages from containers: CloudWatch Logs</title>
      <link>https://cloudonaut.io/a-simple-way-to-manage-log-messages-from-containers-cloudwatch-logs/</link>
      <description>
        <![CDATA[<p>Gone are the days when administrators logged into their machines to access log files. Containers and virtual machines are launched and te]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/docker/">Docker</category>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <guid isPermaLink="true">https://cloudonaut.io/a-simple-way-to-manage-log-messages-from-containers-cloudwatch-logs/</guid>
      <pubDate>Sat, 30 Sep 2017 10:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Gone are the days when administrators logged into their machines to access log files. Containers and virtual machines are launched and terminated dynamically to scale based on demand, to deploy new versions, or to recover from failure nowadays. Collecting, monitoring and analyzing log messages in a centralized data storage has become a minimum requirement for production-ready systems.</p><p>You will learn how to use CloudWatch Logs to manage log messages from thousands of containers in the following.</p><h2 id="Simple-Example"><a href="#Simple-Example" class="headerlink" title="Simple Example"></a>Simple Example</h2><p>Go through the following steps to send your first log message from your container to CloudWatch Logs.</p><ol><li>Open <a href="https://console.aws.amazon.com/cloudwatch/home#logs:" target="_blank" rel="noopener">CloudWatch Logs</a> in the Management Console.</li><li>Create a log group name <code>docker-logs</code>.</li><li>Go to <a href="https://console.aws.amazon.com/iam/home#/roles" target="_blank" rel="noopener">IAM</a> and create a role for the use with EC2 named <code>docker-logs</code> and attach the <code>CloudWatchLogsFullAccess</code> policy. <em>Note: do not use the <code>CloudWatchLogsFullAccess</code> policy for production workloads. Restrict access to the specific resource and actions instead.</em></li><li>Launch an <a href="https://console.aws.amazon.com/ec2/v2/home#LaunchInstanceWizard:" target="_blank" rel="noopener">EC2 Instance</a> based on the <code>Amazon Linux AMI 2017.03.*</code>, select the IAM role ‘docker-logs’, and attach a security group allowing SSH access.</li><li>Log into the EC2 Instance via SSH.</li><li><code>yum install docker</code> to install Docker.</li><li><code>service docker start</code> to start Docker.</li><li>Start a container with <code>docker run --log-driver=awslogs --log-opt awslogs-group=docker-logs alpine echo &#39;a cloudonaut.io example&#39;</code></li><li>Open your <a href="https://console.aws.amazon.com/cloudwatch/home#logEventViewer:group=docker-logs;start=PT5M" target="_blank" rel="noopener">CloudWatch Logs group</a> to find your log message as illustrated in the following screenshot.</li></ol><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/09/cloudwatch-logs-screenshot@730w.webp 730w, /images/2017/09/cloudwatch-logs-screenshot@730w2x.webp 1460w, /images/2017/09/cloudwatch-logs-screenshot@610w.webp 610w, /images/2017/09/cloudwatch-logs-screenshot@610w2x.webp 1220w, /images/2017/09/cloudwatch-logs-screenshot@450w.webp 450w, /images/2017/09/cloudwatch-logs-screenshot@450w2x.webp 900w, /images/2017/09/cloudwatch-logs-screenshot@330w.webp 330w, /images/2017/09/cloudwatch-logs-screenshot@330w2x.webp 660w, /images/2017/09/cloudwatch-logs-screenshot@545w.webp 545w, /images/2017/09/cloudwatch-logs-screenshot@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/09/cloudwatch-logs-screenshot@730w.png 730w, /images/2017/09/cloudwatch-logs-screenshot@730w2x.png 1460w, /images/2017/09/cloudwatch-logs-screenshot@610w.png 610w, /images/2017/09/cloudwatch-logs-screenshot@610w2x.png 1220w, /images/2017/09/cloudwatch-logs-screenshot@450w.png 450w, /images/2017/09/cloudwatch-logs-screenshot@450w2x.png 900w, /images/2017/09/cloudwatch-logs-screenshot@330w.png 330w, /images/2017/09/cloudwatch-logs-screenshot@330w2x.png 660w, /images/2017/09/cloudwatch-logs-screenshot@545w.png 545w, /images/2017/09/cloudwatch-logs-screenshot@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/09/cloudwatch-logs-screenshot.png" alt="CloudWatch Logs Screenshot" title="CloudWatch Logs Screenshot"></picture></p><p>How did your container send a log message to CloudWatch Logs? Read on.</p><h2 id="What-is-CloudWatch-Logs"><a href="#What-is-CloudWatch-Logs" class="headerlink" title="What is CloudWatch Logs?"></a>What is CloudWatch Logs?</h2><p>CloudWatch Logs is a managed service offered by AWS providing scalable, easy-to-use, and highly available log management. I do like to use CloudWatch Logs to collect, monitor, and analyze your log messages because of its simplicity. AWS covers the basics of log management. Don’t expect a full-blown solution like Elasticsearch&#x2F;Kibana, Sumo Logic or Splunk.</p><p>The following figure shows the main elements of CloudWatch Logs:</p><ul><li><strong>Group</strong> a bucket for your log messages, comparable with an S3 bucket.</li><li><strong>Stream</strong> processes up to 5 MB per second, use multiple streams to scale log data ingestion.</li><li><strong>Event</strong> contains the log message.</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/09/cloudwatch-logs-concepts@730w.webp 730w, /images/2017/09/cloudwatch-logs-concepts@730w2x.webp 1460w, /images/2017/09/cloudwatch-logs-concepts@610w.webp 610w, /images/2017/09/cloudwatch-logs-concepts@610w2x.webp 1220w, /images/2017/09/cloudwatch-logs-concepts@450w.webp 450w, /images/2017/09/cloudwatch-logs-concepts@450w2x.webp 900w, /images/2017/09/cloudwatch-logs-concepts@330w.webp 330w, /images/2017/09/cloudwatch-logs-concepts@330w2x.webp 660w, /images/2017/09/cloudwatch-logs-concepts@545w.webp 545w, /images/2017/09/cloudwatch-logs-concepts@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/09/cloudwatch-logs-concepts@730w.png 730w, /images/2017/09/cloudwatch-logs-concepts@730w2x.png 1460w, /images/2017/09/cloudwatch-logs-concepts@610w.png 610w, /images/2017/09/cloudwatch-logs-concepts@610w2x.png 1220w, /images/2017/09/cloudwatch-logs-concepts@450w.png 450w, /images/2017/09/cloudwatch-logs-concepts@450w2x.png 900w, /images/2017/09/cloudwatch-logs-concepts@330w.png 330w, /images/2017/09/cloudwatch-logs-concepts@330w2x.png 660w, /images/2017/09/cloudwatch-logs-concepts@545w.png 545w, /images/2017/09/cloudwatch-logs-concepts@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/09/cloudwatch-logs-concepts.png" alt="CloudWatch Logs Concepts" title="CloudWatch Logs Concepts"></picture></p><p>Additionally, you can use metric filters to monitor incoming log messages. CloudWatch logs are priced per amount of ingested data, stored data and transferred data. See <a href="https://aws.amazon.com/cloudwatch/pricing/" target="_blank" rel="noopener">CloudWatch Pricing</a> for details.</p><p>CloudWatch Logs offers a REST API to ingest data. But how to transfer a log message from your container to CloudWatch Logs?</p><h2 id="Docker-logging-drivers"><a href="#Docker-logging-drivers" class="headerlink" title="Docker logging drivers"></a>Docker logging drivers</h2><p>Docker can forward log messages from <code>stdout</code> and <code>stderr</code> to different targets. You can use the following built-in <em>logdrivers</em>: <code>none</code>, <code>json-file</code>, <code>syslog</code>, <code>journald</code>, <code>gelf</code>, <code>fluentd</code>, <strong><code>awslogs</code></strong>, <code>splunk</code>, <code>etwlogs</code>, and <code>gcplogs</code>. See <a href="https://docs.docker.com/engine/admin/logging/overview/#supported-logging-drivers" target="_blank" rel="noopener">Supported logging drivers</a> for details.</p><p>Use the <code>awslogs</code> logging driver to send logs from your container to CloudWatch Logs without the need of installing any additional log agents. As shown in the following figure the container writes log messages to <code>stdout</code> and <code>stderr</code>. The Docker Daemon receives the log messages and uses the logging driver <code>awslogs</code> to forward all log messages to CloudWatch Logs.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/09/docker-logdriver@730w.webp 730w, /images/2017/09/docker-logdriver@730w2x.webp 1460w, /images/2017/09/docker-logdriver@610w.webp 610w, /images/2017/09/docker-logdriver@610w2x.webp 1220w, /images/2017/09/docker-logdriver@450w.webp 450w, /images/2017/09/docker-logdriver@450w2x.webp 900w, /images/2017/09/docker-logdriver@330w.webp 330w, /images/2017/09/docker-logdriver@330w2x.webp 660w, /images/2017/09/docker-logdriver@545w.webp 545w, /images/2017/09/docker-logdriver@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/09/docker-logdriver@730w.png 730w, /images/2017/09/docker-logdriver@730w2x.png 1460w, /images/2017/09/docker-logdriver@610w.png 610w, /images/2017/09/docker-logdriver@610w2x.png 1220w, /images/2017/09/docker-logdriver@450w.png 450w, /images/2017/09/docker-logdriver@450w2x.png 900w, /images/2017/09/docker-logdriver@330w.png 330w, /images/2017/09/docker-logdriver@330w2x.png 660w, /images/2017/09/docker-logdriver@545w.png 545w, /images/2017/09/docker-logdriver@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/09/docker-logdriver.png" alt="Docker Logdriver" title="Docker Logdriver"></picture></p><p>Back to the example from above. Let’s have a deeper look at the command you used to start your container and send a log message to CloudWatch Logs.</p><figure class="highlight livescript"><table><tr><td class="code"><pre><span class="line">docker run <span class="string">\</span> <span class="comment"># start a container</span></span><br><span class="line">--log-driver=awslogs <span class="string">\</span> <span class="comment"># use the log driver awslogs</span></span><br><span class="line">--log-opt awslogs-group=docker-logs <span class="string">\</span> <span class="comment"># send log messages to log group docker-logs</span></span><br><span class="line">alpine <span class="string">\</span> <span class="comment"># use the super small docker image alpine</span></span><br><span class="line">echo <span class="string">&#x27;a cloudonaut.io example&#x27;</span> <span class="comment"># write a cloudonaut.io example to stdout</span></span><br></pre></td></tr></table></figure><p>That’s all you need to send log messages from a single container to CloudWatch Logs. But how to send log messages from hundreds of containers to CloudWatch Logs? Learn how to integrate CloudWatch Logs with ECS (EC2 Container Service).</p><h2 id="ECS-Example"><a href="#ECS-Example" class="headerlink" title="ECS Example"></a>ECS Example</h2><p>ECS allows you to run container workloads on a fleet of EC2 instances. The following example is an excerpt from our collection of <a href="https://templates.cloudonaut.io/en/stable/ecs/" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a>.</p><p>To launch a container in your ECS cluster, you need to create a task definition. The task definition contains the configuration for your container. Part of the task definition is the <code>LogConfiguration</code> configuring the logging driver of your container.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># [...]</span></span><br><span class="line"><span class="attr">TaskDefinition:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::ECS::TaskDefinition&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Family:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line">    <span class="attr">NetworkMode:</span> <span class="string">bridge</span></span><br><span class="line">    <span class="attr">ContainerDefinitions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">main</span></span><br><span class="line">      <span class="attr">Image:</span> <span class="type">!Ref</span> <span class="string">Image</span></span><br><span class="line">      <span class="attr">Memory:</span> <span class="number">128</span></span><br><span class="line">      <span class="attr">PortMappings:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">ContainerPort:</span> <span class="number">80</span></span><br><span class="line">        <span class="attr">Protocol:</span> <span class="string">tcp</span></span><br><span class="line">      <span class="attr">Essential:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">LogConfiguration:</span> <span class="comment"># configures the logging driver</span></span><br><span class="line">        <span class="attr">LogDriver:</span> <span class="string">awslogs</span> <span class="comment"># use logging driver awslogs to forward log messages to CloudWatch Logs </span></span><br><span class="line">        <span class="attr">Options:</span></span><br><span class="line">          <span class="attr">&#x27;awslogs-region&#x27;:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::Region&#x27;</span> <span class="comment"># the AWS region</span></span><br><span class="line">          <span class="attr">&#x27;awslogs-group&#x27;:</span> <span class="comment"># the log group to send log messages to</span></span><br><span class="line">            <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentClusterStack&#125;-LogGroup&#x27;</span></span><br><span class="line">          <span class="attr">&#x27;awslogs-stream-prefix&#x27;:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span> <span class="comment"># a prefix for the log stream</span></span><br><span class="line"><span class="comment"># [...]</span></span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>When looking for an easy way to manage your container logs on AWS, CloudWatch Logs is a good choice. Docker comes with a built-in logging driver for CloudWatch Logs: <code>awslogs</code>. No need to install and run any additional log collecting agents. CloudWatch Logs scales automatically so you can use it for a single container or thousands of containers running on ECS.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Amazon Web Services in Action Second Edition is in the works</title>
      <link>https://cloudonaut.io/amazon-web-services-in-action-second-edition-is-in-the-works/</link>
      <description>
        <![CDATA[<p>We released our book Amazon Web Services in Action in September 2015. But AWS didn’t rest on its laurels. Every day, new features are rel]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/amazon-web-services-in-action-second-edition-is-in-the-works/</guid>
      <pubDate>Mon, 25 Sep 2017 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>We released our book Amazon Web Services in Action in September 2015. But AWS didn’t rest on its laurels. Every day, new features are released that can make your life easier. It’s time to revise our book so you can continue to build effective solutions with the latest features. We’re excited to share the first revised chapters of our book with you.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/09/aws-in-action-2nd-meap@730w.webp 730w, /images/2017/09/aws-in-action-2nd-meap@730w2x.webp 1460w, /images/2017/09/aws-in-action-2nd-meap@610w.webp 610w, /images/2017/09/aws-in-action-2nd-meap@610w2x.webp 1220w, /images/2017/09/aws-in-action-2nd-meap@450w.webp 450w, /images/2017/09/aws-in-action-2nd-meap@450w2x.webp 900w, /images/2017/09/aws-in-action-2nd-meap@330w.webp 330w, /images/2017/09/aws-in-action-2nd-meap@330w2x.webp 660w, /images/2017/09/aws-in-action-2nd-meap@545w.webp 545w, /images/2017/09/aws-in-action-2nd-meap@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/09/aws-in-action-2nd-meap@730w.png 730w, /images/2017/09/aws-in-action-2nd-meap@730w2x.png 1460w, /images/2017/09/aws-in-action-2nd-meap@610w.png 610w, /images/2017/09/aws-in-action-2nd-meap@610w2x.png 1220w, /images/2017/09/aws-in-action-2nd-meap@450w.png 450w, /images/2017/09/aws-in-action-2nd-meap@450w2x.png 900w, /images/2017/09/aws-in-action-2nd-meap@330w.png 330w, /images/2017/09/aws-in-action-2nd-meap@330w2x.png 660w, /images/2017/09/aws-in-action-2nd-meap@545w.png 545w, /images/2017/09/aws-in-action-2nd-meap@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/09/aws-in-action-2nd-meap.png" alt="Amazon Web Services in Action, Second Edition" title="Amazon Web Services in Action, Second Edition"></picture></p><p>We will revise all existing fourteen chapters, add three new chapters about Lambda, EFS, and ElastiCache, and we switch from JSON to YAML in all CloudFormation examples.</p><p>Our goal remains: we give you a guided and deep introduction to the most important parts of Amazon Web Services and the new possibilities of cloud computing.</p><p>Use promo code mlwittig4 to get 50% off <a href="http://bit.ly/amazon-web-services-in-action-2nd-edition" target="_blank" rel="noopener">Amazon Web Services in Action, Second Edition</a> and start reading while we are still working on the book.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>A non-technical way to reduce your AWS bill within 5 minutes</title>
      <link>https://cloudonaut.io/a-non-technical-way-to-reduce-your-aws-bill-within-5-minutes/</link>
      <description>
        <![CDATA[<p>Are you looking for ways to reduce your monthly AWS bill? Usually, I do share technical tips and tricks to do so: <a href="/3-simple-ways]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/costs/">costs</category>
      <guid isPermaLink="true">https://cloudonaut.io/a-non-technical-way-to-reduce-your-aws-bill-within-5-minutes/</guid>
      <pubDate>Fri, 01 Sep 2017 18:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Are you looking for ways to reduce your monthly AWS bill? Usually, I do share technical tips and tricks to do so: <a href="/3-simple-ways-of-saving-up-to-90-of-ec2-costs/">3 simple ways of saving up to 90% of EC2 costs</a> or <a href="/serverless-cache-for-dynamodb-with-elasticache/">Performance boost and cost savings for DynamoDB</a>. But today, I will share a nontechnical way to reduce your monthly AWS bill by around 15 percent.</p><p><em>Disclaimer: I’m an expert in all things AWS, Serverless and DevOps and not a tax consultant. You should double check and discuss the following facts with your tax consultant.</em></p><p>By default, AWS charges value added taxes (VAT) for all customers based in the European Union. By entering your VAT registration number, you can stop AWS from adding VAT to your monthly bill. That’s reducing the invoice amount by 16 percent for a German business, for example.</p><p>You may have to self-account for VAT on your local VAT return. But usually, a business can make use of input tax deduction which means you don’t have to pay VAT on your AWS usage.</p><p>How to enter your VAT registration number? You have to go through the following process within all your AWS accounts. <strong>Even if you use consolidated billing with a single master account, you still need to enter your VAT registration number in all the member accounts.</strong></p><ol><li>Log into the AWS Management Console.</li><li>Go to the <a href="https://console.aws.amazon.com/billing/home#/tax" target="_blank" rel="noopener">Tax Settings</a></li><li>Click on <a href="https://console.aws.amazon.com/billing/home#/tax/vat" target="_blank" rel="noopener">Tax Registration Numbers</a></li><li>Click the <code>Add</code> link.</li><li>Enter the VAT registration number and the details of the business.</li><li>Press the <code>Update</code> button.</li><li>Wait a few days until AWS verified your VAT registration number.</li></ol><p><strong>Opening Tax Settings</strong><br><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/09/aws-vat-1@730w.webp 730w, /images/2017/09/aws-vat-1@730w2x.webp 1460w, /images/2017/09/aws-vat-1@610w.webp 610w, /images/2017/09/aws-vat-1@610w2x.webp 1220w, /images/2017/09/aws-vat-1@450w.webp 450w, /images/2017/09/aws-vat-1@450w2x.webp 900w, /images/2017/09/aws-vat-1@330w.webp 330w, /images/2017/09/aws-vat-1@330w2x.webp 660w, /images/2017/09/aws-vat-1@545w.webp 545w, /images/2017/09/aws-vat-1@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/09/aws-vat-1@730w.png 730w, /images/2017/09/aws-vat-1@730w2x.png 1460w, /images/2017/09/aws-vat-1@610w.png 610w, /images/2017/09/aws-vat-1@610w2x.png 1220w, /images/2017/09/aws-vat-1@450w.png 450w, /images/2017/09/aws-vat-1@450w2x.png 900w, /images/2017/09/aws-vat-1@330w.png 330w, /images/2017/09/aws-vat-1@330w2x.png 660w, /images/2017/09/aws-vat-1@545w.png 545w, /images/2017/09/aws-vat-1@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/09/aws-vat-1.png" alt="Opening Tax Settings" title="Opening Tax Settings"></picture></p><p><strong>Adding VAT registration number</strong><br><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/09/aws-vat-2@730w.webp 730w, /images/2017/09/aws-vat-2@730w2x.webp 1460w, /images/2017/09/aws-vat-2@610w.webp 610w, /images/2017/09/aws-vat-2@610w2x.webp 1220w, /images/2017/09/aws-vat-2@450w.webp 450w, /images/2017/09/aws-vat-2@450w2x.webp 900w, /images/2017/09/aws-vat-2@330w.webp 330w, /images/2017/09/aws-vat-2@330w2x.webp 660w, /images/2017/09/aws-vat-2@545w.webp 545w, /images/2017/09/aws-vat-2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/09/aws-vat-2@730w.png 730w, /images/2017/09/aws-vat-2@730w2x.png 1460w, /images/2017/09/aws-vat-2@610w.png 610w, /images/2017/09/aws-vat-2@610w2x.png 1220w, /images/2017/09/aws-vat-2@450w.png 450w, /images/2017/09/aws-vat-2@450w2x.png 900w, /images/2017/09/aws-vat-2@330w.png 330w, /images/2017/09/aws-vat-2@330w2x.png 660w, /images/2017/09/aws-vat-2@545w.png 545w, /images/2017/09/aws-vat-2@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/09/aws-vat-2.png" alt="Adding VAT registration numbe" title="Adding VAT registration numbe"></picture></p><p><strong>Typing in VAT registration number and business details</strong><br><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/09/aws-vat-3@730w.webp 730w, /images/2017/09/aws-vat-3@730w2x.webp 1460w, /images/2017/09/aws-vat-3@610w.webp 610w, /images/2017/09/aws-vat-3@610w2x.webp 1220w, /images/2017/09/aws-vat-3@450w.webp 450w, /images/2017/09/aws-vat-3@450w2x.webp 900w, /images/2017/09/aws-vat-3@330w.webp 330w, /images/2017/09/aws-vat-3@330w2x.webp 660w, /images/2017/09/aws-vat-3@545w.webp 545w, /images/2017/09/aws-vat-3@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/09/aws-vat-3@730w.png 730w, /images/2017/09/aws-vat-3@730w2x.png 1460w, /images/2017/09/aws-vat-3@610w.png 610w, /images/2017/09/aws-vat-3@610w2x.png 1220w, /images/2017/09/aws-vat-3@450w.png 450w, /images/2017/09/aws-vat-3@450w2x.png 900w, /images/2017/09/aws-vat-3@330w.png 330w, /images/2017/09/aws-vat-3@330w2x.png 660w, /images/2017/09/aws-vat-3@545w.png 545w, /images/2017/09/aws-vat-3@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/09/aws-vat-3.png" alt="Typing in VAT registration number and business details" title="Typing in VAT registration number and business details"></picture></p><p>A similar option may exist for your business based outside the European Union as well. Check the <a href="https://aws.amazon.com/tax-help/" target="_blank" rel="noopener">AWS Tax Help</a> for details.</p><p><em>Thanks to <a href="https://x.com/hoegertn" target="_blank" rel="noopener">Thorsten Höger</a> for reviewing this article.</em></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>The Cloud Switch: IoT Button, Lambda, and CloudFormation</title>
      <link>https://cloudonaut.io/the-cloud-switch-iot-button-lambda-and-cloudformation/</link>
      <description>
        <![CDATA[<p>Last one out turns off the light. What works for boring light bulbs can be adopted to your cloud infrastructure as well. Are you using a]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/iot/">iot</category>
      <guid isPermaLink="true">https://cloudonaut.io/the-cloud-switch-iot-button-lambda-and-cloudformation/</guid>
      <pubDate>Wed, 23 Aug 2017 11:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Last one out turns off the light. What works for boring light bulbs can be adopted to your cloud infrastructure as well. Are you using a development and testing environment that is only used during working hours? Why not turning off the cloud infrastructure with the press of a button when the last one leaves the office?</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/08/light_bulb@730w.webp 730w, /images/2017/08/light_bulb@730w2x.webp 1460w, /images/2017/08/light_bulb@610w.webp 610w, /images/2017/08/light_bulb@610w2x.webp 1220w, /images/2017/08/light_bulb@450w.webp 450w, /images/2017/08/light_bulb@450w2x.webp 900w, /images/2017/08/light_bulb@330w.webp 330w, /images/2017/08/light_bulb@330w2x.webp 660w, /images/2017/08/light_bulb@545w.webp 545w, /images/2017/08/light_bulb@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/08/light_bulb@730w.jpg 730w, /images/2017/08/light_bulb@730w2x.jpg 1460w, /images/2017/08/light_bulb@610w.jpg 610w, /images/2017/08/light_bulb@610w2x.jpg 1220w, /images/2017/08/light_bulb@450w.jpg 450w, /images/2017/08/light_bulb@450w2x.jpg 900w, /images/2017/08/light_bulb@330w.jpg 330w, /images/2017/08/light_bulb@330w2x.jpg 660w, /images/2017/08/light_bulb@545w.jpg 545w, /images/2017/08/light_bulb@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/08/light_bulb.jpg" alt="Last one out turns off the light." title="Last one out turns off the light."></picture></p><p>Sounds like magic? It is not. Launching and terminating your cloud infrastructure with the press of a button can be achieved by combining the following building blocks:</p><ul><li>AWS IoT Button: the physical button connected to your Wifi sends events to AWS.</li><li>AWS Lambda: the runtime environment to execute a small piece of Node.js code triggered by events from the IoT button.</li><li>AWS CloudFormation: used to create and delete all parts of your development and testing infrastructure in an automated way.</li></ul><p>Before you get started. Order an <a href="https://amzn.to/2xbmKTt" target="_blank" rel="noopener">AWS IoT Button</a>. After your friendly parcel carrier handed over the package with your own IoT Button you are ready to go.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/08/iotbutton@730w.webp 730w, /images/2017/08/iotbutton@730w2x.webp 1460w, /images/2017/08/iotbutton@610w.webp 610w, /images/2017/08/iotbutton@610w2x.webp 1220w, /images/2017/08/iotbutton@450w.webp 450w, /images/2017/08/iotbutton@450w2x.webp 900w, /images/2017/08/iotbutton@330w.webp 330w, /images/2017/08/iotbutton@330w2x.webp 660w, /images/2017/08/iotbutton@545w.webp 545w, /images/2017/08/iotbutton@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/08/iotbutton@730w.jpg 730w, /images/2017/08/iotbutton@730w2x.jpg 1460w, /images/2017/08/iotbutton@610w.jpg 610w, /images/2017/08/iotbutton@610w2x.jpg 1220w, /images/2017/08/iotbutton@450w.jpg 450w, /images/2017/08/iotbutton@450w2x.jpg 900w, /images/2017/08/iotbutton@330w.jpg 330w, /images/2017/08/iotbutton@330w2x.jpg 660w, /images/2017/08/iotbutton@545w.jpg 545w, /images/2017/08/iotbutton@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/08/iotbutton.jpg" alt="IoT Button" title="IoT Button"></picture></p><h2 id="Creating-a-CloudFormation-template"><a href="#Creating-a-CloudFormation-template" class="headerlink" title="Creating a CloudFormation template"></a>Creating a CloudFormation template</h2><p>First of all, you need a CloudFormation template describing your development and testing environment. Upload your template to S3 and note down its URL.</p><p>Looking for inspiration? Check out our collection of <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">useful CloudFormation templates</a>.</p><h2 id="Creating-an-IAM-Role"><a href="#Creating-an-IAM-Role" class="headerlink" title="Creating an IAM Role"></a>Creating an IAM Role</h2><p>The Lambda function needs to be authorized to create and delete CloudFormation stacks and all the resources defined within the template on your behalf.</p><p>Create a new IAM Role to do so.</p><ol><li>Go to <a href="https://console.aws.amazon.com/iam/" target="_blank" rel="noopener">Identity and Access Management (IAM)</a>.</li><li>Select <code>Roles</code> from the sub navigation.</li><li>Press the <code>Create new role</code> button.</li><li>Select <code>AWS Lambda</code> as role type.</li><li>Select the <code>AdministratorAccess</code> policy and proceed with the next step of the wizard.</li><li>Type in <code>iotbutton-cloudformation</code> as role name and proceed with the next step of the wizard.</li><li>Press the <code>Create role</code> button to create the role and finish the wizard.</li></ol><h2 id="Creating-a-Lambda-function"><a href="#Creating-a-Lambda-function" class="headerlink" title="Creating a Lambda function"></a>Creating a Lambda function</h2><p>Start the <a href="https://eu-west-1.console.aws.amazon.com/lambda/home?region=eu-west-1#/create/configure-triggers" target="_blank" rel="noopener">Lambda Wizard</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/08/iotbutton_lambda_trigger@730w.webp 730w, /images/2017/08/iotbutton_lambda_trigger@730w2x.webp 1460w, /images/2017/08/iotbutton_lambda_trigger@610w.webp 610w, /images/2017/08/iotbutton_lambda_trigger@610w2x.webp 1220w, /images/2017/08/iotbutton_lambda_trigger@450w.webp 450w, /images/2017/08/iotbutton_lambda_trigger@450w2x.webp 900w, /images/2017/08/iotbutton_lambda_trigger@330w.webp 330w, /images/2017/08/iotbutton_lambda_trigger@330w2x.webp 660w, /images/2017/08/iotbutton_lambda_trigger@545w.webp 545w, /images/2017/08/iotbutton_lambda_trigger@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/08/iotbutton_lambda_trigger@730w.png 730w, /images/2017/08/iotbutton_lambda_trigger@730w2x.png 1460w, /images/2017/08/iotbutton_lambda_trigger@610w.png 610w, /images/2017/08/iotbutton_lambda_trigger@610w2x.png 1220w, /images/2017/08/iotbutton_lambda_trigger@450w.png 450w, /images/2017/08/iotbutton_lambda_trigger@450w2x.png 900w, /images/2017/08/iotbutton_lambda_trigger@330w.png 330w, /images/2017/08/iotbutton_lambda_trigger@330w2x.png 660w, /images/2017/08/iotbutton_lambda_trigger@545w.png 545w, /images/2017/08/iotbutton_lambda_trigger@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/08/iotbutton_lambda_trigger.png" alt="IoT Button: ENV" title="IoT Button: ENV"></picture></p><ol><li>Select the trigger type <code>AWS IoT</code>.</li><li>Choose IoT Type <code>IoT Button</code>.</li><li>Enter the Device Serial Number of your IoT Button.</li><li>Press the <code>Generate certificate and keys</code> and follow the shown steps to configure your IoT Button.</li><li>Select the <code>Enable trigger</code> option and press the <code>Next</code> button to proceed with the next step of the wizard.</li></ol><p>The next step asks you for the Node.js code.</p><ul><li>Type in <code>iotbutton-cloudformation</code> as the Name of the function and <code>Last one out turns off the light.</code> as description.</li><li>Choose <code>Node.js 6.10</code> as the runtime environment.</li><li>Copy and past the following Node.js code.</li></ul><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> cloudformation = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">CloudFormation</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2010-05-15&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> stackName = process.<span class="property">env</span>.<span class="property">STACK_NAME</span>;</span><br><span class="line"><span class="keyword">var</span> templateUrl = process.<span class="property">env</span>.<span class="property">TEMPLATE_URL</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">handler</span> = <span class="keyword">function</span>(<span class="params">event, context, callback</span>) &#123;  </span><br><span class="line">  <span class="keyword">if</span> (event.<span class="property">clickType</span> ===<span class="string">&#x27;DOUBLE&#x27;</span>) &#123;</span><br><span class="line">    cloudformation.<span class="title function_">deleteStack</span>(&#123;</span><br><span class="line">      <span class="title class_">StackName</span>: stackName</span><br><span class="line">    &#125;, <span class="keyword">function</span>(<span class="params">err, res</span>) &#123;</span><br><span class="line">      <span class="title function_">callback</span>(err);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (event.<span class="property">clickType</span> === <span class="string">&#x27;SINGLE&#x27;</span>) &#123;</span><br><span class="line">    cloudformation.<span class="title function_">createStack</span>(&#123;</span><br><span class="line">      <span class="title class_">StackName</span>: stackName,</span><br><span class="line">      <span class="title class_">Capabilities</span>: [<span class="string">&#x27;CAPABILITY_IAM&#x27;</span>],</span><br><span class="line">      <span class="title class_">Tags</span>: [</span><br><span class="line">        &#123;</span><br><span class="line">          <span class="title class_">Key</span>: <span class="string">&#x27;Name&#x27;</span>,</span><br><span class="line">          <span class="title class_">Value</span>: stackName</span><br><span class="line">        &#125;</span><br><span class="line">      ],</span><br><span class="line">      <span class="title class_">TemplateURL</span>: templateUrl</span><br><span class="line">    &#125;, <span class="keyword">function</span>(<span class="params">err, res</span>) &#123;</span><br><span class="line">      <span class="title function_">callback</span>(err);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">callback</span>(<span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`click type <span class="subst">$&#123;event.clickType&#125;</span> not supported`</span>));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Add two environment variables.</p><figure class="highlight awk"><table><tr><td class="code"><pre><span class="line"><span class="regexp">//</span> STACK_NAME = name of your CloudFormation stack</span><br><span class="line">STACK_NAME = vpc-development-and-testing</span><br><span class="line"></span><br><span class="line"><span class="regexp">//</span> TEMPLATE_URL = S3 URL of your CloudFormation template</span><br><span class="line">TEMPLATE_URL = https:<span class="regexp">//</span>s3-eu-west-<span class="number">1</span>.amazonaws.com<span class="regexp">/widdix-aws-cf-templates-releases-eu-west-1/</span>stable<span class="regexp">/vpc/</span>vpc-<span class="number">2</span>azs.yaml</span><br></pre></td></tr></table></figure><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/08/iotbutton_lambda_env@730w.webp 730w, /images/2017/08/iotbutton_lambda_env@730w2x.webp 1460w, /images/2017/08/iotbutton_lambda_env@610w.webp 610w, /images/2017/08/iotbutton_lambda_env@610w2x.webp 1220w, /images/2017/08/iotbutton_lambda_env@450w.webp 450w, /images/2017/08/iotbutton_lambda_env@450w2x.webp 900w, /images/2017/08/iotbutton_lambda_env@330w.webp 330w, /images/2017/08/iotbutton_lambda_env@330w2x.webp 660w, /images/2017/08/iotbutton_lambda_env@545w.webp 545w, /images/2017/08/iotbutton_lambda_env@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/08/iotbutton_lambda_env@730w.png 730w, /images/2017/08/iotbutton_lambda_env@730w2x.png 1460w, /images/2017/08/iotbutton_lambda_env@610w.png 610w, /images/2017/08/iotbutton_lambda_env@610w2x.png 1220w, /images/2017/08/iotbutton_lambda_env@450w.png 450w, /images/2017/08/iotbutton_lambda_env@450w2x.png 900w, /images/2017/08/iotbutton_lambda_env@330w.png 330w, /images/2017/08/iotbutton_lambda_env@330w2x.png 660w, /images/2017/08/iotbutton_lambda_env@545w.png 545w, /images/2017/08/iotbutton_lambda_env@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/08/iotbutton_lambda_env.png" alt="IoT Button: ENV" title="IoT Button: ENV"></picture></p><ul><li>Type in <code>index.handler</code> as the handler.</li><li>Select <code>Choose an existing role</code>.</li><li>Select the IAM Role <code>iotbutton-cloudformation</code> that you created in the previous section.</li></ul><p>Click <code>Next</code> to proceed with the next step.</p><p>A summary is shown for review. Click <code>Create function</code> to create the function and finish the wizard.</p><p>That’s all. You are ready for a first test.</p><h2 id="Turn-on-the-lights"><a href="#Turn-on-the-lights" class="headerlink" title="Turn on the lights!"></a>Turn on the lights!</h2><p>There is only one thing you need to do now: press your IoT Button! Switch to the <a href="https://eu-west-1.console.aws.amazon.com/cloudformation/home" target="_blank" rel="noopener">CloudFormation service overview</a> to watch your development and test environment being created.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/08/iotbutton_cf_create@730w.webp 730w, /images/2017/08/iotbutton_cf_create@730w2x.webp 1460w, /images/2017/08/iotbutton_cf_create@610w.webp 610w, /images/2017/08/iotbutton_cf_create@610w2x.webp 1220w, /images/2017/08/iotbutton_cf_create@450w.webp 450w, /images/2017/08/iotbutton_cf_create@450w2x.webp 900w, /images/2017/08/iotbutton_cf_create@330w.webp 330w, /images/2017/08/iotbutton_cf_create@330w2x.webp 660w, /images/2017/08/iotbutton_cf_create@545w.webp 545w, /images/2017/08/iotbutton_cf_create@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/08/iotbutton_cf_create@730w.png 730w, /images/2017/08/iotbutton_cf_create@730w2x.png 1460w, /images/2017/08/iotbutton_cf_create@610w.png 610w, /images/2017/08/iotbutton_cf_create@610w2x.png 1220w, /images/2017/08/iotbutton_cf_create@450w.png 450w, /images/2017/08/iotbutton_cf_create@450w2x.png 900w, /images/2017/08/iotbutton_cf_create@330w.png 330w, /images/2017/08/iotbutton_cf_create@330w2x.png 660w, /images/2017/08/iotbutton_cf_create@545w.png 545w, /images/2017/08/iotbutton_cf_create@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/08/iotbutton_cf_create.png" alt="IoT Button: CloudFormation" title="IoT Button: CloudFormation"></picture></p><h2 id="Turn-off-the-lights"><a href="#Turn-off-the-lights" class="headerlink" title="Turn off the lights!"></a>Turn off the lights!</h2><p>After your development and test environment has reached the state <code>CREATE_COMPLETE</code> you can delete your infrastructure by pressing your IoT Button twice.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>We are turning off the lights to save costs and protect the environment. AWS is offering computing, storage, and networking infrastructure on-demand. Turning off unused cloud resources has the same effect than turning off the lights: it saves you money and reduces environmental impact. By combining an AWS IoT Button, AWS Lambda, and AWS CloudFormation, you can create and delete your development and test environment with the push of a button.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>CloudFormation vs Terraform in 2022</title>
      <link>https://cloudonaut.io/cloudformation-vs-terraform/</link>
      <description>CloudFormation and Terraform are powerful and mature IaC tools. Learn about the differences! The winner is?</description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/infrastructure-as-code/">Infrastructure as Code</category>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <category domain="https://cloudonaut.io/tag/terraform/">terraform</category>
      <guid isPermaLink="true">https://cloudonaut.io/cloudformation-vs-terraform/</guid>
      <pubDate>Fri, 04 Aug 2017 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The most reliable way to automate creating, updating, and deleting your cloud resources is to describe the target state of your infrastructure and use a tool to apply it to the current state of your infrastructure (see <a href="/understanding-infrastructure-as-code/">Understanding Infrastructure as Code</a>). <a href="https://aws.amazon.com/cloudformation/" target="_blank" rel="noopener">AWS CloudFormation</a> and <a href="https://www.terraform.io/" target="_blank" rel="noopener">Terraform</a> are the most valuable tools to implement Infrastructure as Code on AWS.</p><p>I have worked with CloudFormation and Terraform in various projects. You will learn about the differences between CloudFormation and Terraform in this article.</p><blockquote><p>Do you prefer listening to a podcast episode over reading a blog post? Here you go!</p><iframe class="lozad" data-src="https://cloudonaut.podigee.io/8-cloudformation-vs-terraform/embed?context=external&theme=default" style="border: 0" border="0" height="144" width="100%"></iframe></blockquote><p>Before we start, both tools are following a very similar approach.</p><ol><li>You define a template (CloudFormation) or configuration (Terraform) describing the target state of your infrastructure.</li><li>The tool (CloudFormation or Terraform) calculates the necessary steps to reach the defined target.</li><li>The tool (CloudFormation or Terraform) executes the changes.</li></ol><p>But what are the differences between both tools?</p><h2 id="Scope"><a href="#Scope" class="headerlink" title="Scope"></a>Scope</h2><p>CloudFormation covers most parts of AWS and needs some time to support new service capabilities. Terraform covers most AWS resources as well and is often faster than CloudFormation when it comes to supporting new AWS features. On top of that, Terraform supports other cloud providers as well as 3rd party services. A shortened list of vendors supported by Terraform: <a href="https://registry.terraform.io/providers/hashicorp/google/latest/docs" target="_blank" rel="noopener">Google Cloud Platform</a>, <a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs" target="_blank" rel="noopener">Azure</a>, <a href="https://registry.terraform.io/providers/integrations/github/latest/docs" target="_blank" rel="noopener">GitHub</a>, <a href="https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs" target="_blank" rel="noopener">GitLab</a>, <a href="https://registry.terraform.io/providers/DataDog/datadog/latest/docs" target="_blank" rel="noopener">Datadog</a>, <a href="https://developer.hashicorp.com/terraform/language/providers" target="_blank" rel="noopener">many more</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/08/cf-tf-scope@730w.webp 730w, /images/2017/08/cf-tf-scope@730w2x.webp 1460w, /images/2017/08/cf-tf-scope@610w.webp 610w, /images/2017/08/cf-tf-scope@610w2x.webp 1220w, /images/2017/08/cf-tf-scope@450w.webp 450w, /images/2017/08/cf-tf-scope@450w2x.webp 900w, /images/2017/08/cf-tf-scope@330w.webp 330w, /images/2017/08/cf-tf-scope@330w2x.webp 660w, /images/2017/08/cf-tf-scope@545w.webp 545w, /images/2017/08/cf-tf-scope@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/08/cf-tf-scope@730w.png 730w, /images/2017/08/cf-tf-scope@730w2x.png 1460w, /images/2017/08/cf-tf-scope@610w.png 610w, /images/2017/08/cf-tf-scope@610w2x.png 1220w, /images/2017/08/cf-tf-scope@450w.png 450w, /images/2017/08/cf-tf-scope@450w2x.png 900w, /images/2017/08/cf-tf-scope@330w.png 330w, /images/2017/08/cf-tf-scope@330w2x.png 660w, /images/2017/08/cf-tf-scope@545w.png 545w, /images/2017/08/cf-tf-scope@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/08/cf-tf-scope.png" alt="Scope" title="Scope"></picture></p><p>Depending on your infrastructure, a big or at least small plus for Terraform.</p><blockquote><p>Update: CloudFormation released <a href="https://aws.amazon.com/blogs/aws/cloudformation-update-cli-third-party-resource-support-registry/" target="_blank" rel="noopener">Resource Providers</a> in November 2019 to allow 3rd party integrations. So far, this new feature is not used very much and not very user-friendly.</p></blockquote><h2 id="License-and-Support"><a href="#License-and-Support" class="headerlink" title="License and Support"></a>License and Support</h2><p>CloudFormation is a service offered by AWS for free. The AWS support plans include support for CloudFormation. </p><p>Terraform is an Open Source project. Hashicorp, the company behind Terraform, is offering support plans as well.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/08/cf-tf-licence@730w.webp 730w, /images/2017/08/cf-tf-licence@730w2x.webp 1460w, /images/2017/08/cf-tf-licence@610w.webp 610w, /images/2017/08/cf-tf-licence@610w2x.webp 1220w, /images/2017/08/cf-tf-licence@450w.webp 450w, /images/2017/08/cf-tf-licence@450w2x.webp 900w, /images/2017/08/cf-tf-licence@330w.webp 330w, /images/2017/08/cf-tf-licence@330w2x.webp 660w, /images/2017/08/cf-tf-licence@545w.webp 545w, /images/2017/08/cf-tf-licence@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/08/cf-tf-licence@730w.png 730w, /images/2017/08/cf-tf-licence@730w2x.png 1460w, /images/2017/08/cf-tf-licence@610w.png 610w, /images/2017/08/cf-tf-licence@610w2x.png 1220w, /images/2017/08/cf-tf-licence@450w.png 450w, /images/2017/08/cf-tf-licence@450w2x.png 900w, /images/2017/08/cf-tf-licence@330w.png 330w, /images/2017/08/cf-tf-licence@330w2x.png 660w, /images/2017/08/cf-tf-licence@545w.png 545w, /images/2017/08/cf-tf-licence@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/08/cf-tf-licence.png" alt="License and Support" title="License and Support"></picture></p><p>When already subscribed to an AWS support plan, that might be a plus for CloudFormation. If you prefer Open Source, that is a plus for Terraform.</p><h2 id="State-Management"><a href="#State-Management" class="headerlink" title="State Management"></a>State Management</h2><p>Both tools need to keep track of all the resources under management. CloudFormation is managing its state with so-called stacks. By default, Terraform is storing its state on disk. Terraform is offering remote state as well, for example, based on <a href="https://developer.hashicorp.com/terraform/language/backend/s3" target="_blank" rel="noopener">S3 and DynamoDB</a> or <a href="https://developer.hashicorp.com/terraform/cloud-docs" target="_blank" rel="noopener">Terraform Cloud</a>. It is advisable to use remote state when multiple users are working on the same infrastructure in parallel.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/08/cf-tf-state@730w.webp 730w, /images/2017/08/cf-tf-state@730w2x.webp 1460w, /images/2017/08/cf-tf-state@610w.webp 610w, /images/2017/08/cf-tf-state@610w2x.webp 1220w, /images/2017/08/cf-tf-state@450w.webp 450w, /images/2017/08/cf-tf-state@450w2x.webp 900w, /images/2017/08/cf-tf-state@330w.webp 330w, /images/2017/08/cf-tf-state@330w2x.webp 660w, /images/2017/08/cf-tf-state@545w.webp 545w, /images/2017/08/cf-tf-state@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/08/cf-tf-state@730w.png 730w, /images/2017/08/cf-tf-state@730w2x.png 1460w, /images/2017/08/cf-tf-state@610w.png 610w, /images/2017/08/cf-tf-state@610w2x.png 1220w, /images/2017/08/cf-tf-state@450w.png 450w, /images/2017/08/cf-tf-state@450w2x.png 900w, /images/2017/08/cf-tf-state@330w.png 330w, /images/2017/08/cf-tf-state@330w2x.png 660w, /images/2017/08/cf-tf-state@545w.png 545w, /images/2017/08/cf-tf-state@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/08/cf-tf-state.png" alt="State" title="State"></picture></p><p>CloudFormation manages state within the managed service out-of-the-box, which is a small plus compared to Terraform, where you need to configure remote state yourself.</p><h2 id="Modularization"><a href="#Modularization" class="headerlink" title="Modularization"></a>Modularization</h2><p>Infrastructure for a typical web application consists of a lot of resources: VPC, Subnets, Security Groups, Auto Scaling Group, Elastic Load Balancer, to name a few. Specifying all these resources in a single blueprint will cause you headaches when maintaining the system in the future. Using small modules that you stick together as needed is a common approach. Terraform comes with native support for <a href="https://developer.hashicorp.com/terraform/language/modules" target="_blank" rel="noopener">modules</a>. You can find open-source modules in the <a href="https://registry.terraform.io/" target="_blank" rel="noopener">Terraform Registry</a>. CloudFormation does not have first-class support for modules. It provides some features that you can use to modularize your templates, but it is up to you to do so. The biggest challenge is to pass values from one “module” to another. The export feature of CloudFormation provides a way to share outputs, but they are not allowed to change! CloudFormation lacks a central place where templates are shared.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/08/cf-tf-modularization@730w.webp 730w, /images/2017/08/cf-tf-modularization@730w2x.webp 1460w, /images/2017/08/cf-tf-modularization@610w.webp 610w, /images/2017/08/cf-tf-modularization@610w2x.webp 1220w, /images/2017/08/cf-tf-modularization@450w.webp 450w, /images/2017/08/cf-tf-modularization@450w2x.webp 900w, /images/2017/08/cf-tf-modularization@330w.webp 330w, /images/2017/08/cf-tf-modularization@330w2x.webp 660w, /images/2017/08/cf-tf-modularization@545w.webp 545w, /images/2017/08/cf-tf-modularization@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/08/cf-tf-modularization@730w.png 730w, /images/2017/08/cf-tf-modularization@730w2x.png 1460w, /images/2017/08/cf-tf-modularization@610w.png 610w, /images/2017/08/cf-tf-modularization@610w2x.png 1220w, /images/2017/08/cf-tf-modularization@450w.png 450w, /images/2017/08/cf-tf-modularization@450w2x.png 900w, /images/2017/08/cf-tf-modularization@330w.png 330w, /images/2017/08/cf-tf-modularization@330w2x.png 660w, /images/2017/08/cf-tf-modularization@545w.png 545w, /images/2017/08/cf-tf-modularization@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/08/cf-tf-modularization.png" alt="Modularization" title="Modularization"></picture></p><p>Handling modules with Terraform is simple. CloudFormation is lacking a standard way to implement “modules”. I’d award Terraform with a plus for usability.</p><h2 id="Verify-Changes"><a href="#Verify-Changes" class="headerlink" title="Verify Changes"></a>Verify Changes</h2><p>CloudFormation and Terraform do not only allow you to create your infrastructure from scratch automatically. You can use both tools to update your infrastructure later as well.</p><p>CloudFormation offers <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-changesets.html" target="_blank" rel="noopener">change sets</a> that you can use to verify changes. Terraform provides a command named <code>plan</code>, which gives you a very detailed overview of what will be modified if you apply your blueprint.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/08/cf-tf-verify-changes@730w.webp 730w, /images/2017/08/cf-tf-verify-changes@730w2x.webp 1460w, /images/2017/08/cf-tf-verify-changes@610w.webp 610w, /images/2017/08/cf-tf-verify-changes@610w2x.webp 1220w, /images/2017/08/cf-tf-verify-changes@450w.webp 450w, /images/2017/08/cf-tf-verify-changes@450w2x.webp 900w, /images/2017/08/cf-tf-verify-changes@330w.webp 330w, /images/2017/08/cf-tf-verify-changes@330w2x.webp 660w, /images/2017/08/cf-tf-verify-changes@545w.webp 545w, /images/2017/08/cf-tf-verify-changes@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/08/cf-tf-verify-changes@730w.png 730w, /images/2017/08/cf-tf-verify-changes@730w2x.png 1460w, /images/2017/08/cf-tf-verify-changes@610w.png 610w, /images/2017/08/cf-tf-verify-changes@610w2x.png 1220w, /images/2017/08/cf-tf-verify-changes@450w.png 450w, /images/2017/08/cf-tf-verify-changes@450w2x.png 900w, /images/2017/08/cf-tf-verify-changes@330w.png 330w, /images/2017/08/cf-tf-verify-changes@330w2x.png 660w, /images/2017/08/cf-tf-verify-changes@545w.png 545w, /images/2017/08/cf-tf-verify-changes@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/08/cf-tf-verify-changes.png" alt="Verify Changes" title="Verify Changes"></picture></p><p>Terraform presents a detailed and readable summary of the changes that will be applied. That’s a big plus compared to the basic overview CloudFormation is providing with a change set.</p><h2 id="Wait-Conditions"><a href="#Wait-Conditions" class="headerlink" title="Wait Conditions"></a>Wait Conditions</h2><p>It is useful to be able to add wait conditions to your infrastructure automation from time to time. For example, if you want to wait until a service has been started on a virtual machine. Using <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-waitcondition.html" target="_blank" rel="noopener">wait conditions</a> allows you to wait for signals sent via HTTPS before dependent resources are created or updated when using CloudFormation.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/08/cf-tf-wait-conditions@730w.webp 730w, /images/2017/08/cf-tf-wait-conditions@730w2x.webp 1460w, /images/2017/08/cf-tf-wait-conditions@610w.webp 610w, /images/2017/08/cf-tf-wait-conditions@610w2x.webp 1220w, /images/2017/08/cf-tf-wait-conditions@450w.webp 450w, /images/2017/08/cf-tf-wait-conditions@450w2x.webp 900w, /images/2017/08/cf-tf-wait-conditions@330w.webp 330w, /images/2017/08/cf-tf-wait-conditions@330w2x.webp 660w, /images/2017/08/cf-tf-wait-conditions@545w.webp 545w, /images/2017/08/cf-tf-wait-conditions@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/08/cf-tf-wait-conditions@730w.png 730w, /images/2017/08/cf-tf-wait-conditions@730w2x.png 1460w, /images/2017/08/cf-tf-wait-conditions@610w.png 610w, /images/2017/08/cf-tf-wait-conditions@610w2x.png 1220w, /images/2017/08/cf-tf-wait-conditions@450w.png 450w, /images/2017/08/cf-tf-wait-conditions@450w2x.png 900w, /images/2017/08/cf-tf-wait-conditions@330w.png 330w, /images/2017/08/cf-tf-wait-conditions@330w2x.png 660w, /images/2017/08/cf-tf-wait-conditions@545w.png 545w, /images/2017/08/cf-tf-wait-conditions@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/08/cf-tf-wait-conditions.png" alt="Wait Conditions" title="Wait Conditions"></picture></p><p>Being able to use wait conditions is a plus for CloudFormation. Terraform does not support wait conditions.</p><h2 id="Rolling-Update"><a href="#Rolling-Update" class="headerlink" title="Rolling Update"></a>Rolling Update</h2><p>What happens when you change a Launch Configuration of an Auto Scaling Group within your blueprint? When using an <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html" target="_blank" rel="noopener">update policy</a>, CloudFormation will perform a rolling update, including a rollback in case of a failure. Terraform does not support rolling updates for Auto Scaling Groups out-of-the-box. With Terraform’s <a href="https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy" target="_blank" rel="noopener">create_before_destroy</a>, you can implement a way to deploy new AMIs without downtime in a blue&#x2F;green fashion, but you will run into issues if you have a dynamic number (auto-scaling) of instances in the ASG.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/08/cf-tf-rolling-update@730w.webp 730w, /images/2017/08/cf-tf-rolling-update@730w2x.webp 1460w, /images/2017/08/cf-tf-rolling-update@610w.webp 610w, /images/2017/08/cf-tf-rolling-update@610w2x.webp 1220w, /images/2017/08/cf-tf-rolling-update@450w.webp 450w, /images/2017/08/cf-tf-rolling-update@450w2x.webp 900w, /images/2017/08/cf-tf-rolling-update@330w.webp 330w, /images/2017/08/cf-tf-rolling-update@330w2x.webp 660w, /images/2017/08/cf-tf-rolling-update@545w.webp 545w, /images/2017/08/cf-tf-rolling-update@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/08/cf-tf-rolling-update@730w.png 730w, /images/2017/08/cf-tf-rolling-update@730w2x.png 1460w, /images/2017/08/cf-tf-rolling-update@610w.png 610w, /images/2017/08/cf-tf-rolling-update@610w2x.png 1220w, /images/2017/08/cf-tf-rolling-update@450w.png 450w, /images/2017/08/cf-tf-rolling-update@450w2x.png 900w, /images/2017/08/cf-tf-rolling-update@330w.png 330w, /images/2017/08/cf-tf-rolling-update@330w2x.png 660w, /images/2017/08/cf-tf-rolling-update@545w.png 545w, /images/2017/08/cf-tf-rolling-update@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/08/cf-tf-rolling-update.png" alt="Rolling Update" title="Rolling Update"></picture></p><p>Supporting rolling updates for Auto Scaling Groups is a plus for CloudFormation.</p><h2 id="Rollback-Safeguards"><a href="#Rollback-Safeguards" class="headerlink" title="Rollback &amp; Safeguards"></a>Rollback &amp; Safeguards</h2><p>When CloudFormation fails to modify your infrastructure, it rolls back to the previous working state automatically (you can <a href="https://aws.amazon.com/blogs/aws/new-for-aws-cloudformation-quickly-retry-stack-operations-from-the-point-of-failure/" target="_blank" rel="noopener">disable rollbacks</a> if you wish to speed up development). Terraform does not support rollbacks out of the box. Either you decide to fix the problem and deploy it again, or you have to apply the previous configuration yourself.</p><p>Both CloudFormation and Terraform support a “prevent from deletion” feature. This is handy to ensure that a resource can never be deleted by accident. CloudFormation goes one step further and can perform a backup of many data stores before it deletes or replaces them.</p><h2 id="Handle-Existing-Resources"><a href="#Handle-Existing-Resources" class="headerlink" title="Handle Existing Resources"></a>Handle Existing Resources</h2><p>CloudFormation can import existing resources for a <a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resource-import-supported-resources.html" target="_blank" rel="noopener">small number of resource types</a>. Terraform allows you to import existing resources. On top of that data, providers enable you to query attributes from existing resources.</p><p>Handling existing resources is better supported in Terraform.</p><h2 id="Secrets"><a href="#Secrets" class="headerlink" title="Secrets"></a>Secrets</h2><p>CloudFormation can import encrypted secrets from AWS Secrets Manager and AWS SSM. The secret values are never stored in CloudFormation. Terraform supports reading secrets from remote backends and environment files. The big risk is that Terraform always stores the unencrypted secret value in the state file!</p><p>CloudFormation is better at handling secrets such as database passwords!</p><h2 id="Loops-and-conditions"><a href="#Loops-and-conditions" class="headerlink" title="Loops and conditions"></a>Loops and conditions</h2><p>CloudFormation comes with first-class support to enable or disable resources if a condition is met. There is no way to loop in CloudFormation over an array natively. Terraform comes with support for loops and also uses those loops to enable or disable a resource if a condition is met.</p><p>Conditions are slightly easier in CloudFormation, while loops are only possible in Terraform.</p><h2 id="Tagging-of-resources"><a href="#Tagging-of-resources" class="headerlink" title="Tagging of resources"></a>Tagging of resources</h2><p>CloudFormation can tag many resources in a stack with a set of tags out-of-the-box. In Terraform, you can configure <a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block" target="_blank" rel="noopener">default_tags</a> for the <code>aws</code> provider to achieve the same.</p><h2 id="Linting"><a href="#Linting" class="headerlink" title="Linting"></a>Linting</h2><p>You can find linters for both CloudFormation and Terraform. The <a href="https://github.com/aws-cloudformation/cfn-python-lint" target="_blank" rel="noopener">CloudFormation Linter</a> catches many errors and ensures certain best practices across your templates. <a href="https://github.com/terraform-linters/tflint" target="_blank" rel="noopener">TFLint</a> does the same for Terraform.</p><p>While using both tools to spin up AWS infrastructure, I believe that the CloudFormation Linter detects more issues.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>CloudFormation and Terraform are both powerful and mature tools. Going through the differences listed above will help you to make a decision. I select the tool depending on the requirements for every project. If there is no clear winner, I tend to use CloudFormation.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>CloudWatch is neglected: Why is the control room empty?</title>
      <link>https://cloudonaut.io/cloudwatch-is-neglected-why-is-the-control-room-empty/</link>
      <description>
        <![CDATA[<p>CloudWatch is the most undervalued service on AWS. It’s like an empty control room. All data is there, but no one is looking at it.</p>
<]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudwatch/">cloudwatch</category>
      <guid isPermaLink="true">https://cloudonaut.io/cloudwatch-is-neglected-why-is-the-control-room-empty/</guid>
      <pubDate>Tue, 18 Jul 2017 20:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>CloudWatch is the most undervalued service on AWS. It’s like an empty control room. All data is there, but no one is looking at it.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/07/empty_control_room@730w.webp 730w, /images/2017/07/empty_control_room@730w2x.webp 1460w, /images/2017/07/empty_control_room@610w.webp 610w, /images/2017/07/empty_control_room@610w2x.webp 1220w, /images/2017/07/empty_control_room@450w.webp 450w, /images/2017/07/empty_control_room@450w2x.webp 900w, /images/2017/07/empty_control_room@330w.webp 330w, /images/2017/07/empty_control_room@330w2x.webp 660w, /images/2017/07/empty_control_room@545w.webp 545w, /images/2017/07/empty_control_room@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/07/empty_control_room@730w.jpg 730w, /images/2017/07/empty_control_room@730w2x.jpg 1460w, /images/2017/07/empty_control_room@610w.jpg 610w, /images/2017/07/empty_control_room@610w2x.jpg 1220w, /images/2017/07/empty_control_room@450w.jpg 450w, /images/2017/07/empty_control_room@450w2x.jpg 900w, /images/2017/07/empty_control_room@330w.jpg 330w, /images/2017/07/empty_control_room@330w2x.jpg 660w, /images/2017/07/empty_control_room@545w.jpg 545w, /images/2017/07/empty_control_room@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/07/empty_control_room.jpg" alt="Empty control room" title="Empty control room"></picture></p><p>Together with IAM and VPC, CloudWatch provides the basis for modern infrastructure. CloudWatch combines an extensive set of functionality that could also be divided into three dedicated services: Metrics, Logging, and Events. Let me explain why you should take CloudWatch more serious and make use of your control room.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/07/cloudwatch@730w.webp 730w, /images/2017/07/cloudwatch@730w2x.webp 1460w, /images/2017/07/cloudwatch@610w.webp 610w, /images/2017/07/cloudwatch@610w2x.webp 1220w, /images/2017/07/cloudwatch@450w.webp 450w, /images/2017/07/cloudwatch@450w2x.webp 900w, /images/2017/07/cloudwatch@330w.webp 330w, /images/2017/07/cloudwatch@330w2x.webp 660w, /images/2017/07/cloudwatch@545w.webp 545w, /images/2017/07/cloudwatch@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/07/cloudwatch@730w.png 730w, /images/2017/07/cloudwatch@730w2x.png 1460w, /images/2017/07/cloudwatch@610w.png 610w, /images/2017/07/cloudwatch@610w2x.png 1220w, /images/2017/07/cloudwatch@450w.png 450w, /images/2017/07/cloudwatch@450w2x.png 900w, /images/2017/07/cloudwatch@330w.png 330w, /images/2017/07/cloudwatch@330w2x.png 660w, /images/2017/07/cloudwatch@545w.png 545w, /images/2017/07/cloudwatch@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/07/cloudwatch.png" alt="CloudWatch" title="CloudWatch"></picture></p><h2 id="Metrics"><a href="#Metrics" class="headerlink" title="Metrics"></a>Metrics</h2><p>A metric represents a time series such as CPU utilization, network usage, or AWS costs. A metric stored numeric data together with a time-stamp. Most AWS services report data to CloudWatch where it is aggregated by the minute and persisted. You can retrieve the minute-by-minute data, or you can retrieve statistics such as 10-minute sum, 1-day average, but also 1-hour 99% percentile.</p><p>The CloudWatch Management Console provides a graphical way to represent metrics in charts. The following figure shows such a chart.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/07/cloudwatch_metric@730w.webp 730w, /images/2017/07/cloudwatch_metric@730w2x.webp 1460w, /images/2017/07/cloudwatch_metric@610w.webp 610w, /images/2017/07/cloudwatch_metric@610w2x.webp 1220w, /images/2017/07/cloudwatch_metric@450w.webp 450w, /images/2017/07/cloudwatch_metric@450w2x.webp 900w, /images/2017/07/cloudwatch_metric@330w.webp 330w, /images/2017/07/cloudwatch_metric@330w2x.webp 660w, /images/2017/07/cloudwatch_metric@545w.webp 545w, /images/2017/07/cloudwatch_metric@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/07/cloudwatch_metric@730w.png 730w, /images/2017/07/cloudwatch_metric@730w2x.png 1460w, /images/2017/07/cloudwatch_metric@610w.png 610w, /images/2017/07/cloudwatch_metric@610w2x.png 1220w, /images/2017/07/cloudwatch_metric@450w.png 450w, /images/2017/07/cloudwatch_metric@450w2x.png 900w, /images/2017/07/cloudwatch_metric@330w.png 330w, /images/2017/07/cloudwatch_metric@330w2x.png 660w, /images/2017/07/cloudwatch_metric@545w.png 545w, /images/2017/07/cloudwatch_metric@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/07/cloudwatch_metric.png" alt="CloudWatch Metric" title="CloudWatch Metric"></picture></p><p>Besides <a href="http://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CW_Support_For_AWS.html" target="_blank" rel="noopener">many AWS services</a> that send data to CloudWatch, you can also send your data which is stored in so called custom metrics. A custom metric is similar to the provided AWS metrics; the only difference is that you sent the data (e.g. using an <a href="http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CloudWatch.html#putMetricData-property" target="_blank" rel="noopener">SDK</a> or the <a href="http://docs.aws.amazon.com/cli/latest/reference/cloudwatch/put-metric-data.html" target="_blank" rel="noopener">CLI</a>).</p><p>The first 15 days, CloudWatch keeps the minute-by-minute data. The next 48 days, CloudWatch keeps a resolution of 5 minutes. The next 392 days CloudWatch keeps a resolution of 1 hour. After that (455 days in total) the data is deleted.</p><p>Available statistics are:</p><ul><li>SampleCount: Number of data points (actual value does not matter)</li><li>Average</li><li>Sum</li><li>Minimum &#x2F; Maximum</li><li>Percentile (values between p0.0 and p100)<ul><li>p0.0 should be the Minimum</li><li>p50 should be the median</li><li>p100 should be the Maximum</li></ul></li></ul><p>Looking at charts can be helpful, but you may also want to automate this process.</p><h3 id="Alarm"><a href="#Alarm" class="headerlink" title="Alarm"></a>Alarm</h3><p>A CloudWatch Alarm observes a metric. As soon as the metric (or a statistic of the metric) crosses a threshold, the alarm triggers an action. One popular action is to send a message to an SNS topic. You can subscribe to the topic via email to get notified if an alarm is triggered. You can also trigger a scale-up action to react automatically to capacity shortages or execute more sophisticated logic in a Lambda function.</p><p>A basic alarm is shown in the following figure.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/07/cloudwatch_alarm@730w.webp 730w, /images/2017/07/cloudwatch_alarm@730w2x.webp 1460w, /images/2017/07/cloudwatch_alarm@610w.webp 610w, /images/2017/07/cloudwatch_alarm@610w2x.webp 1220w, /images/2017/07/cloudwatch_alarm@450w.webp 450w, /images/2017/07/cloudwatch_alarm@450w2x.webp 900w, /images/2017/07/cloudwatch_alarm@330w.webp 330w, /images/2017/07/cloudwatch_alarm@330w2x.webp 660w, /images/2017/07/cloudwatch_alarm@545w.webp 545w, /images/2017/07/cloudwatch_alarm@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/07/cloudwatch_alarm@730w.png 730w, /images/2017/07/cloudwatch_alarm@730w2x.png 1460w, /images/2017/07/cloudwatch_alarm@610w.png 610w, /images/2017/07/cloudwatch_alarm@610w2x.png 1220w, /images/2017/07/cloudwatch_alarm@450w.png 450w, /images/2017/07/cloudwatch_alarm@450w2x.png 900w, /images/2017/07/cloudwatch_alarm@330w.png 330w, /images/2017/07/cloudwatch_alarm@330w2x.png 660w, /images/2017/07/cloudwatch_alarm@545w.png 545w, /images/2017/07/cloudwatch_alarm@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/07/cloudwatch_alarm.png" alt="CloudWatch Alarm" title="CloudWatch Alarm"></picture></p><p>When defining an alarm, you can also set more sophisticated rules than just a threshold. For example, you can specify that the threshold must be reached multiple times in a row and how missing data should be interpreted. Imagine a machine that sends a custom metric, when this machine breaks, the metric is no longer published which should be an error. On the other hand, you may only publish a metric if something happens, where no data means 0.</p><p>Back to visuals. Humans are good at finding patterns in data. Let’s explore better ways to visualize metrics.</p><h2 id="Dashboard"><a href="#Dashboard" class="headerlink" title="Dashboard"></a>Dashboard</h2><p>So many metrics are stored in CloudWatch. But only a few of them matter to you. Why not keep the most important metrics in one place? This place can be shared across your team. Your team can get more visibility into the running infrastructure which is a real motivation to feel responsible. A CloudWatch Dashboard is a board with 24x24 tiles that you can fully configure to display CloudWatch metrics. You can either display the latest value of a metric, a simple line graph of one or more metrics, or a stacked area graph of multiple metrics. All metrics display the same time range. The following figure shows one of my dashboards.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/07/cloudwatch_dashboard@730w.webp 730w, /images/2017/07/cloudwatch_dashboard@730w2x.webp 1460w, /images/2017/07/cloudwatch_dashboard@610w.webp 610w, /images/2017/07/cloudwatch_dashboard@610w2x.webp 1220w, /images/2017/07/cloudwatch_dashboard@450w.webp 450w, /images/2017/07/cloudwatch_dashboard@450w2x.webp 900w, /images/2017/07/cloudwatch_dashboard@330w.webp 330w, /images/2017/07/cloudwatch_dashboard@330w2x.webp 660w, /images/2017/07/cloudwatch_dashboard@545w.webp 545w, /images/2017/07/cloudwatch_dashboard@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/07/cloudwatch_dashboard@730w.png 730w, /images/2017/07/cloudwatch_dashboard@730w2x.png 1460w, /images/2017/07/cloudwatch_dashboard@610w.png 610w, /images/2017/07/cloudwatch_dashboard@610w2x.png 1220w, /images/2017/07/cloudwatch_dashboard@450w.png 450w, /images/2017/07/cloudwatch_dashboard@450w2x.png 900w, /images/2017/07/cloudwatch_dashboard@330w.png 330w, /images/2017/07/cloudwatch_dashboard@330w2x.png 660w, /images/2017/07/cloudwatch_dashboard@545w.png 545w, /images/2017/07/cloudwatch_dashboard@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/07/cloudwatch_dashboard.png" alt="CloudWatch Dashboard" title="CloudWatch Dashboard"></picture></p><p>I used a combination of custom metrics, and AWS provided metrics. Together with line graphs and stacked area charts.</p><p>Good news: <a href="https://aws.amazon.com/blogs/aws/new-api-cloudformation-support-for-amazon-cloudwatch-dashboards/" target="_blank" rel="noopener">CloudFormation now also support the creation of CloudWatch Dashboards</a>!</p><h2 id="Logs"><a href="#Logs" class="headerlink" title="Logs"></a>Logs</h2><p>CloudWatch Logs is a place to store and index all your logs. You can use the <a href="http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AgentReference.html" target="_blank" rel="noopener">CloudWatch Logs Agent</a> to stream the content of log files on your EC2 instances right into CloudWatch Logs. Logs are grouped in so called Groups, inside a group, multiple Streams capture the actual log data. You can define a retention period for a log group to delete log files if they age.</p><p>You can <a href="http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html" target="_blank" rel="noopener">search</a> a log group using full-text search but also more structured queries if you know the structure of your logs.</p><p>Wouldn’t it be nice if you could observe your logs automatically?</p><h3 id="Metric-Filter"><a href="#Metric-Filter" class="headerlink" title="Metric Filter"></a>Metric Filter</h3><p>You can define a Metric Filter using a search query that is applied to all incoming log data. If the query matches a log line, a custom metric is incremented for you. I hope you see how the loop is closed? Define an alarm on the custom metric on you can get alerts if a log line matches your search query.</p><h3 id="Subscription-Filter"><a href="#Subscription-Filter" class="headerlink" title="Subscription Filter"></a>Subscription Filter</h3><p>Sometimes, metric filters are not powerful enough. If you need to execute more sophisticated logic, you can subscribe to a log group. Each entry that matches the query:</p><ul><li>invokes a Lambda function</li><li>is stored in a Kinesis stream. You can analyze the stream with the Kinesis Client Library or Big Data Tools like Spark</li><li>is stored in a Kinesis Firehose. Firehose can deliver to S3 or ElastiSearch where you can use different tools to analyze the data</li></ul><h2 id="Events"><a href="#Events" class="headerlink" title="Events"></a>Events</h2><p>Your AWS infrastructure changes always. Resources are added and removed. CloudWatch Events provide a way to react to such changes. It provides an event stream of your AWS account where many AWS services publish events. E.g. EC2 publishes an event when an instance state changes (e.g. from running to terminated), the Management Console publishes Login events, and <a href="http://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html" target="_blank" rel="noopener">much more</a>.</p><p>You may ask how this is different to CloudTrail? CloudWatch Events are much faster. CloudTrail records all API activity on your AWS account but only guarantees to deliver once every 15 minutes.</p><p>Like custom metrics, you can also publish <a href="http://docs.aws.amazon.com/AmazonCloudWatch/latest/events/AddEventsPutEvents.html" target="_blank" rel="noopener">custom events</a>.</p><h3 id="Rule"><a href="#Rule" class="headerlink" title="Rule"></a>Rule</h3><p>A CloudWatch Event Rule is similar to an alarm. The rule defines what kind of events you are interested in and what action is triggered if an event arrives that matches the condition. You can again send a message to an SNS topic, but also trigger a Lambda function to execute more serious logic.</p><h3 id="Bus"><a href="#Bus" class="headerlink" title="Bus"></a>Bus</h3><p>A CloudWatch Event Bus is the most recent new feature of CloudWatch. Now you can receive events from another AWS account. The sender account creates a rule to forward the events to the account that owns the bus. Buses make sense in a <a href="/your-single-aws-account-is-a-serious-risk/">multi-account setup</a>.</p><h2 id="Notifications-Escalations"><a href="#Notifications-Escalations" class="headerlink" title="Notifications &amp; Escalations"></a>Notifications &amp; Escalations</h2><p>As soon as a CloudWatch Alarm or Rule is triggered, you are on your own. No AWS service can help you to manage alerts that your infrastructure is firing. Sending all those alerts via email is not very sustainable. Not a single person should be responsible for closing alerts. Also, an email list will drive you crazy: The whole team will be interrupted on each alert. What you need is a clever way to distribute the alerts across your team while minimizing the time it takes to close an alert. One solution to this problem is our chatbot marbot. <a href="https://marbot.io/" target="_blank" rel="noopener">marbot ensures your small team never misses an alert from Amazon Web Services</a>. If your team is not small, you may want to look at <a href="https://www.opsgenie.com/" target="_blank" rel="noopener">OpsGenie</a> or <a href="https://www.pagerduty.com/" target="_blank" rel="noopener">PagerDuty</a>.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>CloudWatch provides insights into your running infrastructure.</p><ul><li>Metrics are published by AWS services or by your applications. They can contain all kinds of numeric values attached to a time-stamp. </li><li>Alarms observe metrics and trigger actions if a threshold is reached</li><li>Dashboards visualize a set of metrics</li><li>Logs store and index your log files in a central place</li><li>Filters run a continuous query on your logs and trigger actions if a match is found</li><li>Subscription Filter provide a way to forward logs to other services for analytics like Kinesis or Lambda</li><li>Events provide a near real-time stream of changes in your AWS account</li><li>Rules trigger actions if an event matches a pattern</li><li>Buses can receive events from other AWS accounts</li><li>Notifications &amp; Escalations are not handled by CloudWatch. You need a 3rd party solution</li></ul><p>I hope that you are sitting inside your AWS control room now and see its value.</p><p>Special thanks to <a href="https://x.com/flomotlik" target="_blank" rel="noopener">@flomotlik</a> and <a href="https://x.com/s0enke" target="_blank" rel="noopener">@s0enke</a> for reviewing this blog post.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Maintaining an Open Source library of production-ready CloudFormation templates</title>
      <link>https://cloudonaut.io/maintaining-an-os-library-of-production-ready-cloudformation-templates/</link>
      <description>
        <![CDATA[<p>CloudFormation is the standard to provision AWS resources. But developing a template is a lot of work. Let’s speed up development and mai]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <guid isPermaLink="true">https://cloudonaut.io/maintaining-an-os-library-of-production-ready-cloudformation-templates/</guid>
      <pubDate>Mon, 17 Jul 2017 08:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>CloudFormation is the standard to provision AWS resources. But developing a template is a lot of work. Let’s speed up development and maintenance by working together on high-quality templates:</p><ul><li>reviewed by experts</li><li>secure</li><li>highly available</li><li>scalable</li><li>easy to deploy and update</li><li>built-in monitoring, logging, and visibility</li><li>documented</li><li>automatically tested</li></ul><p>As a maintainer of <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a>, I recently reflected on the project. I came up with the following questions to see how things are going.</p><h2 id="How-to-keep-stacks-updated"><a href="#How-to-keep-stacks-updated" class="headerlink" title="How to keep stacks updated?"></a>How to keep stacks updated?</h2><p>Many of our clients use our <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a>. Sometimes they create a stack on their own; sometimes I help them to create the first stacks. But many of those stacks get never updated after creation. That’s a shame in three ways:<br>2. There are security fixes</p><ol><li>There are bugs</li><li>There are improvements<br>I though about a way to make it super <strong>easy to create and update stacks</strong>. At the moment I experiment with pipelines (CodePipeline) for each template. But I have not yet found an easy way to</li></ol><ul><li>define acceptance and production environments in a general way that works for all templates</li><li>distribute the changes either from a repo or directly from S3 (should we host the templates or should the user clone them?)</li></ul><h2 id="How-to-assure-that-templates-are-working"><a href="#How-to-assure-that-templates-are-working" class="headerlink" title="How to assure that templates are working?"></a>How to assure that templates are working?</h2><p>In February 2017, I <a href="https://github.com/widdix/aws-cf-templates/commit/fdd1612f0323deaa7f00e6c3396bc31402cd964d#diff-9271d1e8533e20df7fd09db69d1ad683" target="_blank" rel="noopener">added a test suite</a> to the project. Before that, we maintained the tests in a private repository. But now the tests are also Open Source. What do we test? E.g.:</p><ul><li>Is <a href="https://github.com/widdix/aws-cf-templates/blob/master/test/src/test/java/de/widdix/awscftemplates/ATest.java#L37" target="_blank" rel="noopener">SSH working from the outside</a></li><li>Is the <a href="https://github.com/widdix/aws-cf-templates/blob/master/test/src/test/java/de/widdix/awscftemplates/wordpress/TestWordpressHA.java#L49" target="_blank" rel="noopener">expected HTTP response returned</a><br>So each time the master branch changes, we run the entire test suite and create many CloudFormation stacks which take hours to complete and costs us hundreds of US-Dollars each month. But this has paid off. We can make changes to the templates and can be sure that everything is still working.<br>We are always trying to improve the tests and reduce the time it takes to run them. At the moment, we can not start the tests automatically on new pull requests, and we are a bit concerned because of potential abuse (each PR create AWS charges on our end). The test suite is written in Java, and large parts are developed only for this project. I’m thinking about launching a project to make it easy to test CloudFormatiom templates. Both by looking at the template but also by looking at the stack. Let me know if this is interesting to you!</li></ul><h2 id="What-is-a-production-ready-template"><a href="#What-is-a-production-ready-template" class="headerlink" title="What is a production-ready template?"></a>What is a production-ready template?</h2><p>I make mistakes. That’s why I always request a <strong>review by another expert</strong>. The posts on this blog, each <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">template</a>, all <a href="/hot-off-the-press-amazon-web-services-in-action-second-edition/">pages of AWS in Action</a> were reviewed by my brother Andreas. He always catches something that I have missed. He adds a new perspective to the problem. He questions the whole approach and asks me to solve it differently. You can imagine that this drives my crazy from time to time. But in the end, the result is always better than before. Even if you don’t have a brother or sister with similar interests, you can still use Pull Request to ask a stranger for a review. Maybe you become friends one day! Pull Requests work <a href="https://github.com/widdix/aws-cf-templates/pulls?q=is:pr+is:closed" target="_blank" rel="noopener">pretty good</a></p><p>A production-ready template needs to be <strong>secure</strong>. <a href="/aws-security-primer/">Security on AWS</a> is complex, but we always try to follow the principle of least privilege. Keep security groups as tight as possible, avoid <code>*</code> in IAM policies. It’s much easier to control Security Groups. The hard part is IAM policies.</p><p>Reliable infrastructure is king. One component to achieve this is a <strong>highly available</strong> architecture. No single point of failures means fewer troubles. This approach also enables things like rolling updates and deployments without downtime. Now you can patch your system at anytime, not only during short maintenance windows at night. AWS makes it super easy to run highly available infrastructure. Many services support it out of the box. Other services need more care. And sometimes you need to be aware of the limitations. In the <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a> project, we document limitations. Otherwise, everything is highly available by default.</p><p>Elasticity is likely of the reasons why you use AWS. From the beginning, we wanted our templates to be <strong>scalable</strong>. Our <a href="https://github.com/widdix/aws-cf-templates/tree/master/jenkins" target="_blank" rel="noopener">Jenkins</a> template adds build-agents when builds queue up. The <a href="https://github.com/widdix/aws-cf-templates/tree/master/wordpress" target="_blank" rel="noopener">WordPress</a> template adds instances when load goes up. I recently read <a href="http://www.brendangregg.com/sysperfbook.html" target="_blank" rel="noopener">Brendan Gregg’s book about Systems Performance</a> where I learned about queuing theory. One key take away was, that if the utilization goes above 80%, the waiting time explodes. That’s why we now scale up when usage hits 60%. This should be enough time for most scenarios to bring up new servers before you hit the 80%. Still, this is not an optimal solution. That’s why Andreas <a href="https://github.com/widdix/aws-cf-templates/pull/90" target="_blank" rel="noopener">started to work on a load test</a> that you can use to adjust the scaling thresholds more easily.</p><p>AWS infrastructure can become complex. And as we all know, things go wrong in complex systems. That’s why you need as much information as possible about the system that is running. Two sources are <strong>monitoring and logging</strong>. CloudWatch Metrics store monitoring information from mostly all AWS services. CloudWatch Logs is a place where you can store and search your logs. <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a> always had the CloudWatch Logs agent installed on EC2 instances. Since <a href="https://github.com/widdix/aws-cf-templates/commit/8532773925b8a76f97627b344b7cab058fad42d7#diff-80f43e96dea266dc8e877cc110ed7042" target="_blank" rel="noopener">April 2017</a>, we started to add CloudWatch Alarms. A CloudWatch Alarm observes a CloudWatch Metric. If the metric crosses a threshold, an alert is sent to an SNS topic. The <a href="https://templates.cloudonaut.io/en/stable/operations/" target="_blank" rel="noopener">Alert Topic</a> template provides the SNS topic and also defines who will receive the alerts. We support email subscriptions and also integrate with our <a href="https://marbot.io/" target="_blank" rel="noopener">chatbot marbot, ensuring you never miss an alert from Amazon Web Services</a>. Another important piece is to make monitoring and logging data visible. Since <a href="https://aws.amazon.com/blogs/aws/new-api-cloudformation-support-for-amazon-cloudwatch-dashboards/" target="_blank" rel="noopener">July 2017</a>, CloudFormation supports CloudWatch Dashboards so we can now start to add dashboards to our templates. We will work on this in the next months.</p><h2 id="How-to-reuse-and-modularize-templates"><a href="#How-to-reuse-and-modularize-templates" class="headerlink" title="How to reuse and modularize templates?"></a>How to reuse and modularize templates?</h2><p>All templates that use EC2 instances will need a VPC. All templates need a place to send alerts to. As you can see, some modules can be separated out into dedicated templates. Luckily, AWS added <a href="https://aws.amazon.com/blogs/aws/aws-cloudformation-update-yaml-cross-stack-references-simplified-substitution/" target="_blank" rel="noopener">Cross-Stack References</a> in September 2016. Since then, you only provide a reference to the parent VPC stack and the all the IDs (VPC, Subnet, Route Tables) are fetched automatically. This makes your life a lot easier and reduces a big source of error. But that’s not yet optimal; you still need to pass the stack references around. I started to experiment with a small command line tool which makes it easier to create and update stacks based on our <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a>. I’m not yet convinced, but I will continue to experiment.</p><h2 id="How-to-create-a-sustainable-library"><a href="#How-to-create-a-sustainable-library" class="headerlink" title="How to create a sustainable library?"></a>How to create a sustainable library?</h2><p>Last but not least, proper documentation is important. It makes it easy to get started, and it reduces the amount of GitHub issues. We moved to <a href="https://templates.cloudonaut.io/en/stable/" target="_blank" rel="noopener">Read the Docs</a> in May 2017, and since then we have a versioned documentation with hosted, versioned templates as well. This should make it easier for you to learn about the templates. We still can improve the docs. At the moment we assume a certain knowledge of AWS. But we could make it easier for AWS newcomers to use the templates.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>We are happy about the state of <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a>. A few things we are experimenting with at the moment:</p><ul><li>Add dashboards to all templates in separate templates to make monitoring data visible</li><li>Offer a solution to create and update a stack based on our templates with CodePipeline to make updating templates easier</li><li>Improve runtime of test suite</li><li>Find a better solution to create stacks that depend on each other (e.g. with a CLI tool)</li><li>Enable AWS newcomers to use our templates: The docs assume certain AWS knowledge at the moment</li><li>Ship load testing capabilities for templates that do auto scaling to allow you to fine tune the scale up&#x2F;down thresholds</li></ul><p>We also want to thank the people who contributed to the project: <a href="https://github.com/statik" target="_blank" rel="noopener">Elliot Murphy</a>, <a href="https://github.com/matsev" target="_blank" rel="noopener">Mattias Severson</a>, <a href="https://github.com/5290charlie" target="_blank" rel="noopener">Charlie McClung</a>, <a href="https://github.com/giphahne" target="_blank" rel="noopener">Dan Hahne</a>, <a href="https://github.com/s0enke" target="_blank" rel="noopener">Soenke Ruempler</a>, <a href="https://github.com/devjack" target="_blank" rel="noopener">Jack Skinner</a>, <a href="https://github.com/btsuhako" target="_blank" rel="noopener">Blake</a>, those who opened issued or just use our templates.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Move to the Next Level of Load Balancing on AWS</title>
      <link>https://cloudonaut.io/move-to-the-next-level-of-load-balancing-on-aws/</link>
      <description>
        <![CDATA[<p>Are you still using the Classic Load Balancer - formerly known as Elastic Load Balancer - for distributing incoming requests among a flee]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/elb/">elb</category>
      <category domain="https://cloudonaut.io/tag/alb/">alb</category>
      <guid isPermaLink="true">https://cloudonaut.io/move-to-the-next-level-of-load-balancing-on-aws/</guid>
      <pubDate>Sat, 08 Jul 2017 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Are you still using the Classic Load Balancer - formerly known as Elastic Load Balancer - for distributing incoming requests among a fleet of EC2 Instances? AWS announced the Application Load Balancer, as a new alternative to the Classic Load Balancer in November 2016. It’s about time to benefit from the next-generation load balancer!</p><p>The Classic Load Balancer, as well as the Application Load Balancer, are managed services provided by AWS offering high availability and scalability out-of-the-box.</p><p>But what are the differences between the Classic Load Balancer and the Application Load Balancer?</p><table class="table">  <thead>    <tr>      <th></th>      <th>Classic Load Balancer</th>      <th>Application Load Balancer</th>    </tr>  </thead>  <tbody>    <tr>      <td>Load Balancing Type</td>      <td>Layer 4 or Basic Layer 7</td>      <td>Advanced Layer 7</td>    </tr>    <tr>      <td>Supported Protocols</td>      <td>TCP, SSL (secure TCP), HTTP, HTTPS</td>      <td>HTTP, HTTPS, HTTP/2, WebSockets</td>    </tr>    <tr>      <td>Path- and Host-Based Routing</td>      <td>❌ n/a</td>      <td>✅ out-of-the-box</td>    </tr>    <tr>      <td>EC2 Container Service support</td>      <td>❌ n/a</td>      <td>✅ out-of-the-box</td>    </tr>    <tr>      <td>Web Application Firewall</td>      <td>❌ n/a</td>      <td>✅ out-of-the-box</td>    </tr>  </tbody></table><p>Let’s dive into the details of two of these differences.</p><h2 id="Path-and-Host-Based-Routing"><a href="#Path-and-Host-Based-Routing" class="headerlink" title="Path- and Host-Based Routing"></a>Path- and Host-Based Routing</h2><p>Are you operating load balancers (e.g. HAProxy) on top of EC2 Instances yourself to provide path- or host-based routing? The chances are high that you can simplify your infrastructure by using an Application Load Balancer.</p><p>Routing incoming requests based on path or host headers, as shown in the following figure, is possible with the Application Load Balancer by default. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/07/alb-routing@730w.webp 730w, /images/2017/07/alb-routing@730w2x.webp 1460w, /images/2017/07/alb-routing@610w.webp 610w, /images/2017/07/alb-routing@610w2x.webp 1220w, /images/2017/07/alb-routing@450w.webp 450w, /images/2017/07/alb-routing@450w2x.webp 900w, /images/2017/07/alb-routing@330w.webp 330w, /images/2017/07/alb-routing@330w2x.webp 660w, /images/2017/07/alb-routing@545w.webp 545w, /images/2017/07/alb-routing@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/07/alb-routing@730w.png 730w, /images/2017/07/alb-routing@730w2x.png 1460w, /images/2017/07/alb-routing@610w.png 610w, /images/2017/07/alb-routing@610w2x.png 1220w, /images/2017/07/alb-routing@450w.png 450w, /images/2017/07/alb-routing@450w2x.png 900w, /images/2017/07/alb-routing@330w.png 330w, /images/2017/07/alb-routing@330w2x.png 660w, /images/2017/07/alb-routing@545w.png 545w, /images/2017/07/alb-routing@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/07/alb-routing.png" alt="ALB Routing" title="ALB Routing"></picture></p><p>The following steps are needed to configure an Application Load Balancer:</p><ol><li>Add a <em>Listener</em> to your Application Load Balancer by specifying a port and protocol.</li><li>Create a <em>Target Group</em>.</li><li>Register a <em>Target</em>, consisting of an EC2 Instance ID and port, at your Target Group.</li><li>Define a <code>Listener Rule</code> mapping incoming requests based on path or host with your <code>Target Group</code>.</li></ol><h2 id="Works-with-ECS"><a href="#Works-with-ECS" class="headerlink" title="Works with ECS"></a>Works with ECS</h2><p>The EC2 Container Service (ECS) provides a container management service. ECS is distributing your containers across a fleet of EC2 Instances. To be able to send requests to one of the containers providing a particular service a client needs to know where to send its requests. But as ECS needs to launch containers dynamically based on load, during deployments or because of a failure within the cluster it is not an option to use static ports and IP addresses.</p><p>As shown in the following figure ECS is integrated with the Application Load Balancer. Whenever ECS launches a new container, a dynamic port is assigned and registered as a target at a Target Group.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/07/alb-ecs@730w.webp 730w, /images/2017/07/alb-ecs@730w2x.webp 1460w, /images/2017/07/alb-ecs@610w.webp 610w, /images/2017/07/alb-ecs@610w2x.webp 1220w, /images/2017/07/alb-ecs@450w.webp 450w, /images/2017/07/alb-ecs@450w2x.webp 900w, /images/2017/07/alb-ecs@330w.webp 330w, /images/2017/07/alb-ecs@330w2x.webp 660w, /images/2017/07/alb-ecs@545w.webp 545w, /images/2017/07/alb-ecs@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/07/alb-ecs@730w.png 730w, /images/2017/07/alb-ecs@730w2x.png 1460w, /images/2017/07/alb-ecs@610w.png 610w, /images/2017/07/alb-ecs@610w2x.png 1220w, /images/2017/07/alb-ecs@450w.png 450w, /images/2017/07/alb-ecs@450w2x.png 900w, /images/2017/07/alb-ecs@330w.png 330w, /images/2017/07/alb-ecs@330w2x.png 660w, /images/2017/07/alb-ecs@545w.png 545w, /images/2017/07/alb-ecs@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/07/alb-ecs.png" alt="ALB and ECS" title="ALB and ECS"></picture></p><p>A client sends its request to the Application Load Balancer. The Application Load Balancer is forwarding the request to a dynamic port mapped to a container running on one of the EC2 Instances that are forming the ECS cluster.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>The Application Load Balancer provides powerful features as path- and host-based routing, integration with ECS, as well as HTTP&#x2F;2 and WAF support.</p><p>It’s about time to use the Application Load Balancer for new infrastructures and migrate from Classic Load Balancers to Applications Load Balancers within existing infrastructures as well.</p><p>Want to learn more about the Application Load Balancer? In our <strong>A Cloud Guru</strong> online course <a href="https://acloud.guru/learn/aws-application-loadbalancer" target="_blank" rel="noopener">Deep Dive into Application Load Balancer</a>, we’ll introduce you to the Application Load Balance in AWS, and show you how to take advantage of its powerful features. Nine hands-on labs and console walkthroughs will increase your skills and enable you to gain practical experience with the Application Load Balancer (ALB).</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Lessons learned: Serverless Chatbot architecture for marbot</title>
      <link>https://cloudonaut.io/lessons-learned-serverless-chatbot-architecture-for-marbot/</link>
      <description>
        <![CDATA[<p><a href="https://marbot.io/" target="_blank" rel="noopener">marbot forwards alerts from AWS to your DevOps team via Slack</a>. marbot was]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/serverless/">Serverless</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/lessons-learned-serverless-chatbot-architecture-for-marbot/</guid>
      <pubDate>Fri, 30 Jun 2017 10:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><a href="https://marbot.io/" target="_blank" rel="noopener">marbot forwards alerts from AWS to your DevOps team via Slack</a>. marbot was one of the winners of the <a href="/marbot-aws-serverless-chatbot-competition/">AWS Serverless Chatbot Competition in 2016</a>. Today I want to show you how marbot works and what we learned so far.</p><p>Let’s start with the architecture diagram.</p><p><a href="/images/2017/06/marbot-architecture.png"><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/06/marbot-architecture@730w.webp 730w, /images/2017/06/marbot-architecture@730w2x.webp 1460w, /images/2017/06/marbot-architecture@610w.webp 610w, /images/2017/06/marbot-architecture@610w2x.webp 1220w, /images/2017/06/marbot-architecture@450w.webp 450w, /images/2017/06/marbot-architecture@450w2x.webp 900w, /images/2017/06/marbot-architecture@330w.webp 330w, /images/2017/06/marbot-architecture@330w2x.webp 660w, /images/2017/06/marbot-architecture@545w.webp 545w, /images/2017/06/marbot-architecture@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/06/marbot-architecture@730w.png 730w, /images/2017/06/marbot-architecture@730w2x.png 1460w, /images/2017/06/marbot-architecture@610w.png 610w, /images/2017/06/marbot-architecture@610w2x.png 1220w, /images/2017/06/marbot-architecture@450w.png 450w, /images/2017/06/marbot-architecture@450w2x.png 900w, /images/2017/06/marbot-architecture@330w.png 330w, /images/2017/06/marbot-architecture@330w2x.png 660w, /images/2017/06/marbot-architecture@545w.png 545w, /images/2017/06/marbot-architecture@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/06/marbot-architecture.png" alt="marbot Architecture" title="marbot Architecture"></picture></a><br><em>The diagram was created with <a href="https://bit.ly/2PpMc2M" target="_blank" rel="noopener">Cloudcraft - Visualize your cloud architecture like a pro</a>.</em></p><h2 id="Architecture"><a href="#Architecture" class="headerlink" title="Architecture"></a>Architecture</h2><p>The marbot API is provided by an API Gateway. We get most of your requests from:</p><ul><li>Amazon SNS: Our users use SNS topics with an HTTPS subscription to transport alerts, notifications, and incidents from within their AWS accounts to marbot.</li><li><a href="https://marbot.io/help/integration.html" target="_blank" rel="noopener">Non-AWS sources that make HTTPS calls</a> like <a href="https://newrelic.com/" target="_blank" rel="noopener">New Relic</a>, <a href="https://uptimerobot.com/" target="_blank" rel="noopener">Uptime Robot</a>, or just <code>curl</code> clients.</li><li>Slack: marbot uses the Slack Events API to drive conversations with his users and Slack Buttons to allow users to acknowledge, close, or pass alerts.</li></ul><p>The API Gateway forwards HTTP requests to one of our Lambda functions. All of them are implemented in Node.js and store their state in DynamoDB tables.</p><p>One special case is the Slack Button API. When you press a button in a Slack message, marbot has 3 seconds to respond to this message. To respond to a button press, marbot may need to make a bunch of calls to the Slack API.</p><h2 id="Learnings"><a href="#Learnings" class="headerlink" title="Learnings"></a>Learnings</h2><h3 id="Decoupling-the-process"><a href="#Decoupling-the-process" class="headerlink" title="Decoupling the process"></a>Decoupling the process</h3><p>We learned that we miss the 2-second timeout very often by looking at our CloudWatch data. To not miss the 2-second timeout, we now only put a record into a Kinesis stream that contains all relevant data before we respond to the API request. Writing to Kinesis is a quick operation, and we haven’t seen 2-second timeouts since we switched to Kinesis streams.</p><p>As soon as possible we read the Kinesis stream and process the records within a Lambda function. Kinesis comes with its challenges. If you fail to process a record, the Lambda Kinesis integration will retry this record as long as the record is deleted from the stream. All the newer records will not be processed until the failed record is deleted or you fix the bug!</p><p>We also thought about using SQS, but:</p><ul><li>there is no native SQS Lambda integration</li><li>we can not build one on our own that is serverless and responds within a second</li></ul><p>So we decided to use Kinesis knowing that an error can stop our whole processing pipeline.</p><h3 id="Resilient-remote-calls"><a href="#Resilient-remote-calls" class="headerlink" title="Resilient remote calls"></a>Resilient remote calls</h3><p>HTTP requests are hard. A lot of things can go wrong. Two things that we learned early when talking to the Slack API:</p><ol><li>Set timeouts: We use 3 seconds at the moment and think about reducing this to 2 seconds</li><li>Retry on failures like timeouts or 5XX responses.</li></ol><p>Our Node.js implementation of Slack API calls relies on the <a href="https://www.npmjs.com/package/requestretry" target="_blank" rel="noopener">requestretry</a> package:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> requestretry = <span class="built_in">require</span>(<span class="string">&#x27;requestretry&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">AWSXRay</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-xray-sdk&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">invokeSlack</span>(<span class="params">method, qs, cb</span>) &#123;</span><br><span class="line">  <span class="title function_">requestretry</span>(&#123;</span><br><span class="line">    <span class="attr">method</span>: <span class="string">&#x27;GET&#x27;</span>,</span><br><span class="line">    <span class="attr">url</span>: <span class="string">`https://slack.com/api/<span class="subst">$&#123;method&#125;</span>`</span>,</span><br><span class="line">    <span class="attr">qs</span>: qs,</span><br><span class="line">    <span class="attr">json</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">maxAttempts</span>: <span class="number">3</span>, <span class="comment">// retry only 3 times</span></span><br><span class="line">    <span class="attr">retryDelay</span>: <span class="number">100</span>, <span class="comment">// wait 0.1 seconds between two retries</span></span><br><span class="line">    <span class="attr">timeout</span>: <span class="number">3000</span>, <span class="comment">// timeout after 3 seconds</span></span><br><span class="line">    <span class="attr">httpModules</span>: &#123;</span><br><span class="line">      <span class="string">&#x27;http:&#x27;</span>: <span class="title class_">AWSXRay</span>.<span class="title function_">captureHTTPs</span>(<span class="built_in">require</span>(<span class="string">&#x27;http&#x27;</span>)), <span class="comment">// enable X-Ray tracing for http calls</span></span><br><span class="line">      <span class="string">&#x27;https:&#x27;</span>: <span class="title class_">AWSXRay</span>.<span class="title function_">captureHTTPs</span>(<span class="built_in">require</span>(<span class="string">&#x27;https&#x27;</span>)) <span class="comment">// enable X-Ray tracing for https calls</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, <span class="keyword">function</span>(<span class="params">err, res, body</span>) &#123; <span class="comment">/* ... */</span> &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>The following screenshot shows a X-Ray trace where the code retried Slack API calls because of the 3 seconds timeout.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/06/marbot-x-ray-trace@730w.webp 730w, /images/2017/06/marbot-x-ray-trace@730w2x.webp 1460w, /images/2017/06/marbot-x-ray-trace@610w.webp 610w, /images/2017/06/marbot-x-ray-trace@610w2x.webp 1220w, /images/2017/06/marbot-x-ray-trace@450w.webp 450w, /images/2017/06/marbot-x-ray-trace@450w2x.webp 900w, /images/2017/06/marbot-x-ray-trace@330w.webp 330w, /images/2017/06/marbot-x-ray-trace@330w2x.webp 660w, /images/2017/06/marbot-x-ray-trace@545w.webp 545w, /images/2017/06/marbot-x-ray-trace@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/06/marbot-x-ray-trace@730w.png 730w, /images/2017/06/marbot-x-ray-trace@730w2x.png 1460w, /images/2017/06/marbot-x-ray-trace@610w.png 610w, /images/2017/06/marbot-x-ray-trace@610w2x.png 1220w, /images/2017/06/marbot-x-ray-trace@450w.png 450w, /images/2017/06/marbot-x-ray-trace@450w2x.png 900w, /images/2017/06/marbot-x-ray-trace@330w.png 330w, /images/2017/06/marbot-x-ray-trace@330w2x.png 660w, /images/2017/06/marbot-x-ray-trace@545w.png 545w, /images/2017/06/marbot-x-ray-trace@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/06/marbot-x-ray-trace.png" alt="X-Ray trace" title="X-Ray trace"></picture></p><h3 id="Implementing-timers-on-AWS"><a href="#Implementing-timers-on-AWS" class="headerlink" title="Implementing timers on AWS"></a>Implementing timers on AWS</h3><p>For every alert that arrives in marbot, we keep a timer. 5 minutes after the alert is received we check if someone acknowledged the alert. If not, we escalate the alert to another engineer or the whole team. We have decided to use SQS queues for that. If you send a message to an SQS queue, you can set a delay. Only after the delay, the message becomes visible in the queue. Exactly what we need! The only downside to this solution is that there is no native way to connect Lambda and SQS. But with a <a href="/integrate-sqs-and-lambda-serverless-architecture-for-asynchronous-workloads/">few lines of</a> code, you can implement this on your own.</p><h3 id="Keeping-secrets-secure"><a href="#Keeping-secrets-secure" class="headerlink" title="Keeping secrets secure"></a>Keeping secrets secure</h3><p>We use git to version our source code. To communicate with the Slack API, we need to store a secret that we use to authenticate with Slack. We keep those secrets in a JSON file that is added to git as well. But we encrypt the whole file with KMS before we put it into git with the AWS CLI:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws kms encrypt --key-id XXX --plaintext fileb://config_plain.json --output text --query CiphertextBlob | <span class="built_in">base64</span> --decode &gt; config.json</span><br></pre></td></tr></table></figure><blockquote><p>Make sure to put <code>config_plain.json</code> into your <code>.gitignore</code> file!</p></blockquote><p>Outside of the Lambda handler code, we use this code snippet to decrypt the configuration:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">&#x27;fs&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">AWSXRay</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-xray-sdk&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="title class_">AWSXRay</span>.<span class="title function_">captureAWS</span>(<span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>));</span><br><span class="line"><span class="keyword">const</span> kms = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title function_">KMS</span>(&#123;<span class="attr">apiVersion</span>: <span class="string">&#x27;2014-11-01&#x27;</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> config = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="keyword">function</span>(<span class="params">resolve, reject</span>) &#123;</span><br><span class="line">  fs.<span class="title function_">readFile</span>(<span class="string">`config.json`</span>, <span class="keyword">function</span>(<span class="params">err, data</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) &#123;</span><br><span class="line">      <span class="title function_">reject</span>(err);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      kms.<span class="title function_">decrypt</span>(&#123;<span class="title class_">CiphertextBlob</span>: data&#125;, <span class="keyword">function</span>(<span class="params">err, data</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (err) &#123;</span><br><span class="line">          <span class="title function_">reject</span>(err);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="title function_">resolve</span>(<span class="title class_">JSON</span>.<span class="title function_">parse</span>(<span class="keyword">new</span> <span class="title class_">Buffer</span>(data.<span class="property">Plaintext</span>, <span class="string">&#x27;base64&#x27;</span>)));</span><br><span class="line">          &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">            <span class="title function_">reject</span>(err);</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125; </span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>Inside the Lambda handler code, you can access the config like this:</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">config</span><br><span class="line">  .<span class="title function_">then</span>(<span class="keyword">function</span>(<span class="params">c</span>) &#123;</span><br><span class="line">    <span class="comment">// do something</span></span><br><span class="line">  &#125;)</span><br><span class="line">  .<span class="title function_">catch</span>(<span class="keyword">function</span>(<span class="params">err</span>) &#123;</span><br><span class="line">    <span class="comment">// handle error</span></span><br><span class="line">  &#125;);</span><br></pre></td></tr></table></figure><p>Using this approach, you will only make one API call to KMS (for every Lambda runtime).</p><h3 id="Getting-insights"><a href="#Getting-insights" class="headerlink" title="Getting insights"></a>Getting insights</h3><p>We use custom CloudWatch metrics to get insights into:</p><ul><li>How many Slack teams installed marbot</li><li>Number of alerts and escalations created</li></ul><p>We use a CloudWatch Dashboard to display those business metrics together with some technical metrics.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/06/marbot-dashboard@730w.webp 730w, /images/2017/06/marbot-dashboard@730w2x.webp 1460w, /images/2017/06/marbot-dashboard@610w.webp 610w, /images/2017/06/marbot-dashboard@610w2x.webp 1220w, /images/2017/06/marbot-dashboard@450w.webp 450w, /images/2017/06/marbot-dashboard@450w2x.webp 900w, /images/2017/06/marbot-dashboard@330w.webp 330w, /images/2017/06/marbot-dashboard@330w2x.webp 660w, /images/2017/06/marbot-dashboard@545w.webp 545w, /images/2017/06/marbot-dashboard@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/06/marbot-dashboard@730w.png 730w, /images/2017/06/marbot-dashboard@730w2x.png 1460w, /images/2017/06/marbot-dashboard@610w.png 610w, /images/2017/06/marbot-dashboard@610w2x.png 1220w, /images/2017/06/marbot-dashboard@450w.png 450w, /images/2017/06/marbot-dashboard@450w2x.png 900w, /images/2017/06/marbot-dashboard@330w.png 330w, /images/2017/06/marbot-dashboard@330w2x.png 660w, /images/2017/06/marbot-dashboard@545w.png 545w, /images/2017/06/marbot-dashboard@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/06/marbot-dashboard.png" alt="marbot dashboard" title="marbot dashboard"></picture></p><h3 id="Deploying-the-infrastructure"><a href="#Deploying-the-infrastructure" class="headerlink" title="Deploying the infrastructure"></a>Deploying the infrastructure</h3><p>Our pipeline for deploying marbot works like this:</p><ol><li>Download dependencies (<code>npm install</code>)</li><li>Lint code</li><li>Run unit tests (we mock all external HTTP calls with <a href="https://www.npmjs.com/package/nock" target="_blank" rel="noopener">nock</a></li><li><code>cloudformation package</code></li><li><code>cloudformation deploy</code> to an integration stack</li><li>Run integration tests with <a href="https://www.npmjs.com/package/newman" target="_blank" rel="noopener">newman</a></li><li><code>cloudformation deploy</code> to a prod stack</li></ol><p>Jenkins runs the pipeline. Since our code is hosted on BitBucket, we can not easily use CodePipeline at the moment.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>ECS vs. Kubernetes: same same but different</title>
      <link>https://cloudonaut.io/ecs-vs-kubernetes/</link>
      <description>
        <![CDATA[<p>EC2 Container Service (ECS) and Kubernetes (K8s) are solving the same problem: managing containers across a cluster of hosts. The battle]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/docker/">Docker</category>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <category domain="https://cloudonaut.io/tag/kubernetes/">kubernetes</category>
      <guid isPermaLink="true">https://cloudonaut.io/ecs-vs-kubernetes/</guid>
      <pubDate>Tue, 20 Jun 2017 10:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>EC2 Container Service (ECS) and Kubernetes (K8s) are solving the same problem: managing containers across a cluster of hosts. The battle between ECS and Kubernetes reminds me of the editor war between vi and Emacs: heated discussions focusing on technical quibbles and personal beliefs. The following questions will help you to choose wisely. Questions and answers do contain my opinion on the differences between ECS and K8s based on my experiences from recent projects.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/06/ecs-vs-k8s@730w.webp 730w, /images/2017/06/ecs-vs-k8s@730w2x.webp 1460w, /images/2017/06/ecs-vs-k8s@610w.webp 610w, /images/2017/06/ecs-vs-k8s@610w2x.webp 1220w, /images/2017/06/ecs-vs-k8s@450w.webp 450w, /images/2017/06/ecs-vs-k8s@450w2x.webp 900w, /images/2017/06/ecs-vs-k8s@330w.webp 330w, /images/2017/06/ecs-vs-k8s@330w2x.webp 660w, /images/2017/06/ecs-vs-k8s@545w.webp 545w, /images/2017/06/ecs-vs-k8s@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/06/ecs-vs-k8s@730w.png 730w, /images/2017/06/ecs-vs-k8s@730w2x.png 1460w, /images/2017/06/ecs-vs-k8s@610w.png 610w, /images/2017/06/ecs-vs-k8s@610w2x.png 1220w, /images/2017/06/ecs-vs-k8s@450w.png 450w, /images/2017/06/ecs-vs-k8s@450w2x.png 900w, /images/2017/06/ecs-vs-k8s@330w.png 330w, /images/2017/06/ecs-vs-k8s@330w2x.png 660w, /images/2017/06/ecs-vs-k8s@545w.png 545w, /images/2017/06/ecs-vs-k8s@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/06/ecs-vs-k8s.png" alt="ECS vs. Kubernetes" title="ECS vs. Kubernetes"></picture></p><h2 id="Does-it-fit"><a href="#Does-it-fit" class="headerlink" title="Does it fit?"></a>Does it fit?</h2><p>A container is an isolated element. But being able to launch containers across a cluster of hosts is only a small part of the challenge. Your container lives within a universe consisting of infrastructure and services:  storage system, database, domain name service to name a few.</p><p>Where are you planning to run your containers?</p><ul><li>Amazon Web Services (AWS)</li><li>Google Cloud Platform (GCP)</li><li>other IaaS provider</li><li>on-premise</li></ul><p>Being able to integrate your container management solution into your infrastructure is key.</p><p>ECS is offering the most seamless integration between your containers and other AWS services. A few examples working out of the box:</p><ul><li>Assigning IAM roles to each container allows fine granular access control to other services.</li><li>Registering containers at external load balancers (Application Load Balancer).</li><li>Scaling EC2 instances based on cluster usage (Auto Scaling).</li><li>Collecting logs (CloudWatch Logs).</li></ul><p>Achieving a similar level of integration between K8s and AWS is a lot of work. For example, building a production-ready key-value store with <strong>etcd</strong>, needed for K8s, with high availability, encryption, and rolling updates took several weeks. Integrating K8s with load balancer and domain name system was another significant obstacle.</p><p>On the other hand, K8s offers trouble-free integration with GCP. The Google Container Engine among other things provides the following:</p><ul><li>Distributing clusters among multiple zones for high availability.</li><li>Scaling the cluster based on usage.</li><li>Providing persistent disks for containers.</li></ul><p>K8s provides the most value when using it with Google Container Engine (GKE) because of its integrations with GCP.</p><p>If you are using another IaaS provider than Amazon and Google or running your workload on-premise, K8s is your only option as ECS runs on AWS only. Building an infrastructure comparable to ECS on AWS or K8s on GCP will be a lot of work in that case.</p><h2 id="Does-it-match-your-architecture"><a href="#Does-it-match-your-architecture" class="headerlink" title="Does it match your architecture?"></a>Does it match your architecture?</h2><p>ECS and K8s are following different strategies for service discovery.</p><p>ECS is using load balancers for service discovery. External as well as internal services are accessible through load balancers. The Application Load Balancer (ALB) offers path- and host-based routing as well as internal or external connections.</p><p>K8s is using a different strategy. Only requests from outside the cluster are passing through a load balancer. A virtual IP provides access to internal services without the need for a load balancer.</p><p>If your microservice architecture relies heavily on service-to-service communication, K8s is offering less communication overhead. Otherwise, ECS is providing a plain and simple approach for a microservice architecture as well. Especially, if most of your services need to be accessible from the Internet.</p><h2 id="Who-operates-it"><a href="#Who-operates-it" class="headerlink" title="Who operates it?"></a>Who operates it?</h2><p>I would advise against operating a container cluster yourself whenever possible. Or is there any significant value a Do-It-Yourself container infrastructure is adding to your business?</p><p>The cluster management provided by ECS is a fully managed service offering high availability, scalability, and security. There are no additional fees for using ECS, and it’s covered by your AWS support plan as well. You are still responsible for the underlying infrastructure consisting of EC2 and VPC though.</p><p>The Google Container Engine (GKE) is offering a managed service as well. GKE provides a managed K8s cluster including the underlying infrastructure. If your cluster consists of more than five nodes, Google charges you around USD 100 per month for managing it.</p><h2 id="Does-it-pay-off"><a href="#Does-it-pay-off" class="headerlink" title="Does it pay off?"></a>Does it pay off?</h2><p>K8s is licensed under the Apache License 2.0 whereas ECS is a proprietary service offered by AWS. Even though, AWS published <a href="https://blox.github.io/" target="_blank" rel="noopener">Blox</a>, a collection of open source projects for container management and orchestration on ECS.</p><p>The K8s community is vibrant generating a lot of innovative solutions. The open source ecosystem offers flexibility. But do not expect production-ready solutions besides the K8s core.</p><p>Vendor lock-in is a popular argument within ECS vs. K8s discussions. I would argue that both ECS and K8s are locking you into their solutions. And even though K8s is open source, Google is a dominant contributor with interest in evolving and monetising their cloud platform.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Are you using AWS as infrastructure provider? Use ECS to manage and schedule your containers and benefit from a highly integrated and fully managed service.</p><p>Are you using GCP as infrastructure provider? Use Google Container Engine (GKE) offering a fully managed K8s cluster well integrated into the GCP infrastructure.</p><p>Are you running your workloads on-premise or by using another IaaS provider? Operating a K8s cluster yourself is probably your only option. Expect significant eff when building a highly available and scalable K8s cluster integrated with your existing infrastructure.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Amazon Elasticsearch Service revised</title>
      <link>https://cloudonaut.io/amazon-elasticsearch-service-revised/</link>
      <description>
        <![CDATA[<p><em>AWS first!</em> is one of our <a href="https://widdix.net/#principles" target="_blank" rel="noopener">consulting principles</a>. Usin]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/elasticsearch/">elasticsearch</category>
      <guid isPermaLink="true">https://cloudonaut.io/amazon-elasticsearch-service-revised/</guid>
      <pubDate>Mon, 22 May 2017 15:00:20 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><em>AWS first!</em> is one of our <a href="https://widdix.net/#principles" target="_blank" rel="noopener">consulting principles</a>. Using a managed service provided by AWS is usually offering the most bang for the buck. But there are pitfalls and downsides hidden behind shiny marketing promises of these managed services as well.</p><p><a href="https://aws.amazon.com/elasticsearch-service/" target="_blank" rel="noopener">Amazon Elasticsearch Service</a> is offering the popular open-source search and analytics engine as a service. You can benefit from many advantages when using the service as described on <a href="https://aws.amazon.com/elasticsearch-service/" target="_blank" rel="noopener">AWS’s marketing page</a>:</p><ul><li>Easy to Use</li><li>Highly Available and Secure</li><li>Easily Scalable</li></ul><p>But that’s only one part of the story. There are downsides when using the Amazon Elasticsearch Service as well. I will highlight common challenges when using the managed service in real-world scenarios as well as illustrate workarounds in this article.</p><h2 id="VPC"><a href="#VPC" class="headerlink" title="VPC"></a>VPC</h2><p>Placing a database into a VPC is possible with the Relational Database Service (SQL database) and ElastiCache (in-memory database). The Amazon Elasticsearch Service does not support the Virtual Private Cloud (VPC). Accessing the service is only possible with a connection via the Internet.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/05/elasticsearch-vpc@730w.webp 730w, /images/2017/05/elasticsearch-vpc@730w2x.webp 1460w, /images/2017/05/elasticsearch-vpc@610w.webp 610w, /images/2017/05/elasticsearch-vpc@610w2x.webp 1220w, /images/2017/05/elasticsearch-vpc@450w.webp 450w, /images/2017/05/elasticsearch-vpc@450w2x.webp 900w, /images/2017/05/elasticsearch-vpc@330w.webp 330w, /images/2017/05/elasticsearch-vpc@330w2x.webp 660w, /images/2017/05/elasticsearch-vpc@545w.webp 545w, /images/2017/05/elasticsearch-vpc@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/05/elasticsearch-vpc@730w.png 730w, /images/2017/05/elasticsearch-vpc@730w2x.png 1460w, /images/2017/05/elasticsearch-vpc@610w.png 610w, /images/2017/05/elasticsearch-vpc@610w2x.png 1220w, /images/2017/05/elasticsearch-vpc@450w.png 450w, /images/2017/05/elasticsearch-vpc@450w2x.png 900w, /images/2017/05/elasticsearch-vpc@330w.png 330w, /images/2017/05/elasticsearch-vpc@330w2x.png 660w, /images/2017/05/elasticsearch-vpc@545w.png 545w, /images/2017/05/elasticsearch-vpc@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/05/elasticsearch-vpc.png" alt="Elasticsearch running outside VPC" title="Elasticsearch running outside VPC"></picture></p><p>All traffic from EC2 instances running in a private subnet to the Elasticsearch database running outside the VPC flows through a NAT gateway. The additional traffic is causing costs and consuming a limited resource.</p><p>Using a service outside the VPC is a no go because of compliance requirements in some scenarios as well.</p><h2 id="Snapshot-and-Restore"><a href="#Snapshot-and-Restore" class="headerlink" title="Snapshot and Restore"></a>Snapshot and Restore</h2><p>Being able to restore your data is an important aspect of a managed service. Amazon Elasticsearch Service is creating snapshots of your data daily. Unfortunately, you are not able to restore a snapshot yourself. <a href="http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-managedomains.html#es-managedomains-snapshots" target="_blank" rel="noopener">Only the AWS support team can restore a snapshot</a>. Therefore, you need to create a support ticket if you want to restore your data. Wich increases the RTO (recovery time objective) and reduces your control over the restore process.</p><p>There is a workaround for this problem. Elasticsearch has built-in support for snapshots stored on S3. As illustrated in the following figure you can use AWS Lambda to trigger snapshots based on a schedule as well as cleaning up snapshots automatically.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/05/elasticsearch-snapshot-lambda@730w.webp 730w, /images/2017/05/elasticsearch-snapshot-lambda@730w2x.webp 1460w, /images/2017/05/elasticsearch-snapshot-lambda@610w.webp 610w, /images/2017/05/elasticsearch-snapshot-lambda@610w2x.webp 1220w, /images/2017/05/elasticsearch-snapshot-lambda@450w.webp 450w, /images/2017/05/elasticsearch-snapshot-lambda@450w2x.webp 900w, /images/2017/05/elasticsearch-snapshot-lambda@330w.webp 330w, /images/2017/05/elasticsearch-snapshot-lambda@330w2x.webp 660w, /images/2017/05/elasticsearch-snapshot-lambda@545w.webp 545w, /images/2017/05/elasticsearch-snapshot-lambda@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/05/elasticsearch-snapshot-lambda@730w.png 730w, /images/2017/05/elasticsearch-snapshot-lambda@730w2x.png 1460w, /images/2017/05/elasticsearch-snapshot-lambda@610w.png 610w, /images/2017/05/elasticsearch-snapshot-lambda@610w2x.png 1220w, /images/2017/05/elasticsearch-snapshot-lambda@450w.png 450w, /images/2017/05/elasticsearch-snapshot-lambda@450w2x.png 900w, /images/2017/05/elasticsearch-snapshot-lambda@330w.png 330w, /images/2017/05/elasticsearch-snapshot-lambda@330w2x.png 660w, /images/2017/05/elasticsearch-snapshot-lambda@545w.png 545w, /images/2017/05/elasticsearch-snapshot-lambda@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/05/elasticsearch-snapshot-lambda.png" alt="Triggering Elasticsearch snapshots with Lambda" title="Triggering Elasticsearch snapshots with Lambda"></picture></p><h2 id="IAM"><a href="#IAM" class="headerlink" title="IAM"></a>IAM</h2><p>The Identity and Access Management (IAM) service is used to authenticate and authorize requests to the Amazon Elasticsearch Service. Almost every part of AWS offers integrations with IAM. As you can reuse IAM users, groups, and roles it is very easy to control access to your data stored within your Elasticsearch database.</p><p>But there some downsides caused by IAM as well. It is not possible to use the standard security mechanism provided by X-Pack (formerly Shield) with Amazon Elasticsearch Service.</p><p>A lot of tools from the Elasticsearch ecosystem do not support authentication with IAM out of the box. If you want to use <a href="https://www.elastic.co/logstash" target="_blank" rel="noopener">logstash</a> to ingest log data into your Elasticsearch database, an <a href="https://github.com/awslabs/logstash-output-amazon_es" target="_blank" rel="noopener">additional plugin</a> is needed to handle the IAM-based authentication.</p><p>Amazon Elasticsearch Service offers Kibana out-of-the-box. But it is not possible to access Kibana when using IAM for authentication because your browser is not able to use IAM-based authentication. Using an <a href="https://github.com/cllunsford/aws-signing-proxy" target="_blank" rel="noopener">IAM proxy</a> as shown in the following figure allows you to work around this limitation.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/05/elasticsearch-iam-proxy@730w.webp 730w, /images/2017/05/elasticsearch-iam-proxy@730w2x.webp 1460w, /images/2017/05/elasticsearch-iam-proxy@610w.webp 610w, /images/2017/05/elasticsearch-iam-proxy@610w2x.webp 1220w, /images/2017/05/elasticsearch-iam-proxy@450w.webp 450w, /images/2017/05/elasticsearch-iam-proxy@450w2x.webp 900w, /images/2017/05/elasticsearch-iam-proxy@330w.webp 330w, /images/2017/05/elasticsearch-iam-proxy@330w2x.webp 660w, /images/2017/05/elasticsearch-iam-proxy@545w.webp 545w, /images/2017/05/elasticsearch-iam-proxy@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/05/elasticsearch-iam-proxy@730w.png 730w, /images/2017/05/elasticsearch-iam-proxy@730w2x.png 1460w, /images/2017/05/elasticsearch-iam-proxy@610w.png 610w, /images/2017/05/elasticsearch-iam-proxy@610w2x.png 1220w, /images/2017/05/elasticsearch-iam-proxy@450w.png 450w, /images/2017/05/elasticsearch-iam-proxy@450w2x.png 900w, /images/2017/05/elasticsearch-iam-proxy@330w.png 330w, /images/2017/05/elasticsearch-iam-proxy@330w2x.png 660w, /images/2017/05/elasticsearch-iam-proxy@545w.png 545w, /images/2017/05/elasticsearch-iam-proxy@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/05/elasticsearch-iam-proxy.png" alt="IAM Proxy for Elasticsearch" title="IAM Proxy for Elasticsearch"></picture></p><h2 id="Encryption"><a href="#Encryption" class="headerlink" title="Encryption"></a>Encryption</h2><p>AWS is offering encryption integrated with KMS (Key Management Service) for almost all services storing data (RDS, EBS, S3, …).  In contrast, the Amazon Elasticsearch Service does not provide server-side encryption for data-at-rest. Storing data unencrypted could be a no go if you must encrypt all data because of regulations.</p><h2 id="Instance-Types"><a href="#Instance-Types" class="headerlink" title="Instance Types"></a>Instance Types</h2><p>The Amazon Elasticsearch Service does not support all instance types. For example, I3 instances would perfectly fit for a distributed database such as Elasticsearch but are not an option when using the Amazon Elasticsearch Service.</p><h2 id="Limited-Operations"><a href="#Limited-Operations" class="headerlink" title="Limited Operations"></a>Limited Operations</h2><p>Some Elasticsearch operations are not available when using the Amazon Elasticsearch Service. For example, it is not possible to open and close indices. A process that is required to make changes to analyzers. </p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>I’d recommend using a managed service provided by AWS whenever possible. AWS is doing a great job at offering services fulfilling the needs of the majority of their customers. The Amazon Elasticsearch Service is taking away the burden of managing a distributed system. But if you are planning to use Amazon Elasticsearch Service you should consider the downsides I have experienced in real-world projects and documented in this article as well.</p><p>I’m planning to add a template to our collection of <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">CloudFormation templates</a> including workarounds for accessing Kibana (IAM Proxy) as well as Elasticsearch snapshots triggered by Lambda. Please <a href="mailto:&#97;&#110;&#x64;&#x72;&#101;&#97;&#x73;&#x40;&#119;&#105;&#100;&#x64;&#105;&#x78;&#46;&#100;&#101;">let me know</a>, if you’d be interested in such a template.</p><p><em>Thanks to <a href="https://x.com/moritzonken" target="_blank" rel="noopener">Moritz Onken</a> for providing his feedback regarding the Amazon Elasticsearch Service.</em></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Security Primer</title>
      <link>https://cloudonaut.io/aws-security-primer/</link>
      <description>
        <![CDATA[<p>I was preparing some AWS Security related training. Soon, I realized that this topic is too huge to fit into my brain. So I structured my]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/security/">Security</category>
      <category domain="https://cloudonaut.io/tag/security/">security</category>
      <category domain="https://cloudonaut.io/tag/iam/">iam</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-security-primer/</guid>
      <pubDate>Thu, 11 May 2017 20:36:20 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>I was preparing some AWS Security related training. Soon, I realized that this topic is too huge to fit into my brain. So I structured my thoughts in a mind map<sup><a href="#fn:1" id="fnref:1" class="footnote-ref">1</a></sup>. Within a couple of minutes<sup><a href="#fn:2" id="fnref:2" class="footnote-ref">2</a></sup> I came up with this:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/05/aws-security-surface@730w.webp 730w, /images/2017/05/aws-security-surface@730w2x.webp 1460w, /images/2017/05/aws-security-surface@610w.webp 610w, /images/2017/05/aws-security-surface@610w2x.webp 1220w, /images/2017/05/aws-security-surface@450w.webp 450w, /images/2017/05/aws-security-surface@450w2x.webp 900w, /images/2017/05/aws-security-surface@330w.webp 330w, /images/2017/05/aws-security-surface@330w2x.webp 660w, /images/2017/05/aws-security-surface@545w.webp 545w, /images/2017/05/aws-security-surface@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/05/aws-security-surface@730w.png 730w, /images/2017/05/aws-security-surface@730w2x.png 1460w, /images/2017/05/aws-security-surface@610w.png 610w, /images/2017/05/aws-security-surface@610w2x.png 1220w, /images/2017/05/aws-security-surface@450w.png 450w, /images/2017/05/aws-security-surface@450w2x.png 900w, /images/2017/05/aws-security-surface@330w.png 330w, /images/2017/05/aws-security-surface@330w2x.png 660w, /images/2017/05/aws-security-surface@545w.png 545w, /images/2017/05/aws-security-surface@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/05/aws-security-surface.png" alt="AWS Security Surface" title="AWS Security Surface"></picture></p><p>What is your first reaction? Mine was pretty much surprised:</p><p><img class="img-fluid" src="https://media.giphy.com/media/xUA7aQD2BSeMh6bGTu/giphy.gif" alt="Surprised face" title="Surprised face"></p><p>Let me summarize how AWS Security works to make sure you are not surprised one day.</p><blockquote><p>This post received over 200 points on <a href="https://news.ycombinator.com/item?id=14628108" target="_blank" rel="noopener">Hacker News</a>.</p></blockquote><h2 id="Account-Structure"><a href="#Account-Structure" class="headerlink" title="Account Structure"></a>Account Structure</h2><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/05/aws-security-surface-account-structure@730w.webp 730w, /images/2017/05/aws-security-surface-account-structure@730w2x.webp 1460w, /images/2017/05/aws-security-surface-account-structure@610w.webp 610w, /images/2017/05/aws-security-surface-account-structure@610w2x.webp 1220w, /images/2017/05/aws-security-surface-account-structure@450w.webp 450w, /images/2017/05/aws-security-surface-account-structure@450w2x.webp 900w, /images/2017/05/aws-security-surface-account-structure@330w.webp 330w, /images/2017/05/aws-security-surface-account-structure@330w2x.webp 660w, /images/2017/05/aws-security-surface-account-structure@545w.webp 545w, /images/2017/05/aws-security-surface-account-structure@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/05/aws-security-surface-account-structure@730w.png 730w, /images/2017/05/aws-security-surface-account-structure@730w2x.png 1460w, /images/2017/05/aws-security-surface-account-structure@610w.png 610w, /images/2017/05/aws-security-surface-account-structure@610w2x.png 1220w, /images/2017/05/aws-security-surface-account-structure@450w.png 450w, /images/2017/05/aws-security-surface-account-structure@450w2x.png 900w, /images/2017/05/aws-security-surface-account-structure@330w.png 330w, /images/2017/05/aws-security-surface-account-structure@330w2x.png 660w, /images/2017/05/aws-security-surface-account-structure@545w.png 545w, /images/2017/05/aws-security-surface-account-structure@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/05/aws-security-surface-account-structure.png" alt="AWS Security Surface: Account Structure" title="AWS Security Surface: Account Structure"></picture></p><p>In 2015, I wrote a blog post about why <a href="/your-single-aws-account-is-a-serious-risk/">Your single AWS Account is a serious risk</a>. Since then, AWS released Organizations. An Organization is a bucket for multiple AWS Accounts. The key thing to consider is Service Control Policies (SCPs). SCPs are evaluated before IAM policies. So you can restrict usage of services within an AWS account and no IAM policy in the world can change that.</p><p>The security best practices for root user rules still apply:</p><ol><li>Don’t use your root user</li><li>Activate MFA</li><li>Don’t use the Access Key</li></ol><h2 id="AWS-API"><a href="#AWS-API" class="headerlink" title="AWS API"></a>AWS API</h2><p>I needed to separate this topic into two: Authentication (who you are) and Authorization (what you are allowed to do).</p><h3 id="Authentication"><a href="#Authentication" class="headerlink" title="Authentication"></a>Authentication</h3><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/05/aws-security-surface-api-authentication@730w.webp 730w, /images/2017/05/aws-security-surface-api-authentication@730w2x.webp 1460w, /images/2017/05/aws-security-surface-api-authentication@610w.webp 610w, /images/2017/05/aws-security-surface-api-authentication@610w2x.webp 1220w, /images/2017/05/aws-security-surface-api-authentication@450w.webp 450w, /images/2017/05/aws-security-surface-api-authentication@450w2x.webp 900w, /images/2017/05/aws-security-surface-api-authentication@330w.webp 330w, /images/2017/05/aws-security-surface-api-authentication@330w2x.webp 660w, /images/2017/05/aws-security-surface-api-authentication@545w.webp 545w, /images/2017/05/aws-security-surface-api-authentication@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/05/aws-security-surface-api-authentication@730w.png 730w, /images/2017/05/aws-security-surface-api-authentication@730w2x.png 1460w, /images/2017/05/aws-security-surface-api-authentication@610w.png 610w, /images/2017/05/aws-security-surface-api-authentication@610w2x.png 1220w, /images/2017/05/aws-security-surface-api-authentication@450w.png 450w, /images/2017/05/aws-security-surface-api-authentication@450w2x.png 900w, /images/2017/05/aws-security-surface-api-authentication@330w.png 330w, /images/2017/05/aws-security-surface-api-authentication@330w2x.png 660w, /images/2017/05/aws-security-surface-api-authentication@545w.png 545w, /images/2017/05/aws-security-surface-api-authentication@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/05/aws-security-surface-api-authentication.png" alt="AWS Security Surface: API Authentication" title="AWS Security Surface: API Authentication"></picture></p><p>The thing you are familiar with is the <strong>IAM User</strong>. If you don’t use the root user (which you should not), you are most likely using AWS with IAM Users. An IAM User can (this is optional!) login to the Management Console using a Login Profile. As you know, the password should not be as trivial as your birthday, which you can enforce by a password policy. It’s also regarded good practice to activate MFA for all IAM Users with a Login Profile. If you want to use the AWS CLI you also need an Access Key which you download to your laptop. This is very sensitive information, and it’s a good practice to rotate this Access Key in regular intervals. If you are using AWS CodeCommit (managed git repo) than you also can upload your public SSH key for authentication purposes. A lot of stuff, still we have not talked about what the user is allowed to do. This will be covered in the next section.</p><p>But let’s first look at other points on the map.</p><ul><li>An <strong>IAM Group</strong> is not an identity (you can not reference the group later) it just groups IAM Users. And you can manage Authorization for the whole group instead of individual users, which is also a good practice.</li><li><strong>Cognito</strong> is niche. You can manage your own pools of users (not IAM Users) or connect with Facebook, Twitter, … and all those users can (depends on what you authorize) get access to parts (or the whole) of your AWS account.</li><li>An <strong>EC2 Instance Profile</strong> is used to authenticate an <strong>EC2 Instance</strong>. This is handy if your EC2 instances want to communicate with the AWS API. No need for Access Keys on your EC2 instances.</li><li><strong>Federation</strong> can make your life easier if you have many users that want to use AWS in your company. You can use an external Identity Provider such as your AD to authenticate users.</li><li>An <strong>IAM Role</strong> can only be assumed. You can not login. The cool thing is that not only IAM Users can assume roles. Also, AWS services can assume roles for you to work on your behalf. Even roles can assume roles which are amazing if you plan to complicate your setup. Important to keep in mind is, that a role controls who can assume her in a <strong>Trust Policy</strong>.</li><li>To make things simpler, AWS introduced <strong>Service-Linked Roles</strong>. Some AWS services manage parts of your AWS account on your behalf. E.g. EMR launches EC2 instances to run Hadoop for you. Before Service-Linked Roles, you had to create an IAM Role with the proper Trust Policy for AWS to work in your account. Service-Linked Roles come pre-configured and are managed by AWS. You only have to install them once.</li><li>Also kind of niche is <strong>IoT Things</strong>. Things like your thermostat authenticate with a certificate. A thing can (depends on what you authorize) get access to parts (or the whole) of your AWS account.</li></ul><p>This is a summary of who can get access to your AWS account.</p><h3 id="Authorization"><a href="#Authorization" class="headerlink" title="Authorization"></a>Authorization</h3><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/05/aws-security-surface-api-authorization@730w.webp 730w, /images/2017/05/aws-security-surface-api-authorization@730w2x.webp 1460w, /images/2017/05/aws-security-surface-api-authorization@610w.webp 610w, /images/2017/05/aws-security-surface-api-authorization@610w2x.webp 1220w, /images/2017/05/aws-security-surface-api-authorization@450w.webp 450w, /images/2017/05/aws-security-surface-api-authorization@450w2x.webp 900w, /images/2017/05/aws-security-surface-api-authorization@330w.webp 330w, /images/2017/05/aws-security-surface-api-authorization@330w2x.webp 660w, /images/2017/05/aws-security-surface-api-authorization@545w.webp 545w, /images/2017/05/aws-security-surface-api-authorization@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/05/aws-security-surface-api-authorization@730w.png 730w, /images/2017/05/aws-security-surface-api-authorization@730w2x.png 1460w, /images/2017/05/aws-security-surface-api-authorization@610w.png 610w, /images/2017/05/aws-security-surface-api-authorization@610w2x.png 1220w, /images/2017/05/aws-security-surface-api-authorization@450w.png 450w, /images/2017/05/aws-security-surface-api-authorization@450w2x.png 900w, /images/2017/05/aws-security-surface-api-authorization@330w.png 330w, /images/2017/05/aws-security-surface-api-authorization@330w2x.png 660w, /images/2017/05/aws-security-surface-api-authorization@545w.png 545w, /images/2017/05/aws-security-surface-api-authorization@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/05/aws-security-surface-api-authorization.png" alt="AWS Security Surface: API Authorization" title="AWS Security Surface: API Authorization"></picture></p><p>Given you, or a thing, or a machine, … authenticated successfully, they now want to do something. That’s where <strong>IAM Policies</strong> come into play. A policy can be defined inline (IAM User, IAM Group, IAM Role) or can be a separate entity (<strong>Managed Policy</strong>) that can be attached to IAM Users, IAM Groups, or IAM Roles. You can either create your own Managed Policies or use the predefined policies managed by AWS. You will most likely not follow the principle of least privileges if you use AWS managed policies. The whole policy topic seems easy to understand. The only problem is to define those policies.</p><p>Besides IAM Policies, some services use additional authorization mechanism. The services with their own authorization mechanism are:</p><ul><li><strong>SNS</strong> and <strong>SQS</strong>: A <strong>Topic Policy</strong> or <strong>Queue Policy</strong> can open a topic&#x2F;queue to things like IAM User, IAM Role, AWS Account, or just all of us. This can be handy, but you should think twice before doing so. Most likely, you can use the assume role approach to achieve the same!</li><li><strong>Glacier</strong>: Glacier comes with two types of policies, one to control access, and the other to control modification of archives. The latter is important if you have to enforce that data can not be changed for legal reasons.</li><li><strong>IoT</strong>: As mentioned, IoT Things can authenticate, and the <strong>Policy</strong> determines what they can do in your AWS account.</li><li><strong>Lambda</strong>: A <strong>Lambda Function</strong> can be opened for invocation by a <strong>Permission</strong>. This is needed if you want a “Cron” to trigger your Lambda because this “Cron” is AWS. So AWS needs permissions to invoke your function. Most likely, you can use the assume role approach to achieve the same!</li><li><strong>S3</strong>: S3 is the king of alternative ways for authorization.<ul><li>You can set <strong>ACL</strong>s on the bucket and object level to give read&#x2F;write permissions. I recommend no using them. There is no simple way to change all ACLs on the object level. And it is very hard to reason if every object can have different permissions.</li><li>You can also use a <strong>Bucket Policy</strong> to give read&#x2F;write permissions to your bucket&#x2F;objects. This makes sense if your bucket is public and you want to give read access to the world.</li><li>You can also generate pre-signed URLs to give temporary permissions to read or upload objects. This makes sense if you want users to directly upload files from their browser.</li></ul></li><li><strong>KMS</strong>: <strong>Key Policies</strong> are the primary way to control access to customer master keys (CMKs) in KMS. On top of that, you can use IAM policies to authorize. The second way to control access are <strong>Grants</strong>. With a Grant, you can allow another AWS principal (e.g. an AWS account) to use a CMK with some restrictions. You could also implement this with the Key policy, but grants are more flexible to control.</li></ul><h2 id="Service-API"><a href="#Service-API" class="headerlink" title="Service API"></a>Service API</h2><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/05/aws-security-surface-service-api@730w.webp 730w, /images/2017/05/aws-security-surface-service-api@730w2x.webp 1460w, /images/2017/05/aws-security-surface-service-api@610w.webp 610w, /images/2017/05/aws-security-surface-service-api@610w2x.webp 1220w, /images/2017/05/aws-security-surface-service-api@450w.webp 450w, /images/2017/05/aws-security-surface-service-api@450w2x.webp 900w, /images/2017/05/aws-security-surface-service-api@330w.webp 330w, /images/2017/05/aws-security-surface-service-api@330w2x.webp 660w, /images/2017/05/aws-security-surface-service-api@545w.webp 545w, /images/2017/05/aws-security-surface-service-api@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/05/aws-security-surface-service-api@730w.png 730w, /images/2017/05/aws-security-surface-service-api@730w2x.png 1460w, /images/2017/05/aws-security-surface-service-api@610w.png 610w, /images/2017/05/aws-security-surface-service-api@610w2x.png 1220w, /images/2017/05/aws-security-surface-service-api@450w.png 450w, /images/2017/05/aws-security-surface-service-api@450w2x.png 900w, /images/2017/05/aws-security-surface-service-api@330w.png 330w, /images/2017/05/aws-security-surface-service-api@330w2x.png 660w, /images/2017/05/aws-security-surface-service-api@545w.png 545w, /images/2017/05/aws-security-surface-service-api@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/05/aws-security-surface-service-api.png" alt="AWS Security Surface: Service API " title="AWS Security Surface: Service API "></picture></p><p>Some services come with their own API. Databases, like MySQL, come with their own authentication and authorization mechanism. You usually authenticate with username and password and the database user is authorized to do specific things (e.g. not <code>DROP TABLE</code>). Keep in mind that the AWS API to manage RDS still uses the auth mechanism of the AWS API.</p><p>If you want to administer your Linux or Windows virtual machine you will use SSH or RDP. For Linux machines, AWS can deploy up to one public SSH key to your instance. You can then login via SSH with the private key on your machine. For Windows machines, AWS encrypts the password with the public key, and you can use the private key to decrypt the password. Using the username and password, you can login via RDP.</p><h2 id="Network"><a href="#Network" class="headerlink" title="Network"></a>Network</h2><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/05/aws-security-surface-network@730w.webp 730w, /images/2017/05/aws-security-surface-network@730w2x.webp 1460w, /images/2017/05/aws-security-surface-network@610w.webp 610w, /images/2017/05/aws-security-surface-network@610w2x.webp 1220w, /images/2017/05/aws-security-surface-network@450w.webp 450w, /images/2017/05/aws-security-surface-network@450w2x.webp 900w, /images/2017/05/aws-security-surface-network@330w.webp 330w, /images/2017/05/aws-security-surface-network@330w2x.webp 660w, /images/2017/05/aws-security-surface-network@545w.webp 545w, /images/2017/05/aws-security-surface-network@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/05/aws-security-surface-network@730w.png 730w, /images/2017/05/aws-security-surface-network@730w2x.png 1460w, /images/2017/05/aws-security-surface-network@610w.png 610w, /images/2017/05/aws-security-surface-network@610w2x.png 1220w, /images/2017/05/aws-security-surface-network@450w.png 450w, /images/2017/05/aws-security-surface-network@450w2x.png 900w, /images/2017/05/aws-security-surface-network@330w.png 330w, /images/2017/05/aws-security-surface-network@330w2x.png 660w, /images/2017/05/aws-security-surface-network@545w.png 545w, /images/2017/05/aws-security-surface-network@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/05/aws-security-surface-network.png" alt="AWS Security Surface: Network" title="AWS Security Surface: Network"></picture></p><p>The network is where many AWS customers spend most of their time when thinking about security. But as you can see, it is much easier to understand than AWS API access. You create a VPC (no EC2-classic coverage here!) where you define your subnets. Subnets within a VPC can talk to each other (they are routed).<br>The recommended way of controlling network access are <strong>Security Groups</strong>. A Security Group is attached to ENIs (network interfaces) and controls inbound and outbound traffic. By default, Security Groups do not allow any inbound traffic but allow all outbound traffic. Besides identifying traffic by IP address ranges you can also identify traffic by the target&#x2F;origin Security Groups. Security Groups are stateful: If an inbound packet is allowed, the response is also allowed and vice versa.<br>There are also <strong>ACL</strong>s to control inbound and outbound traffic for subnets. By default, ACLs are configured to allow all inbound and outbound traffic. ACLs are stateless, so you will most likely open all high ports. I recommend not to use ACLs unless you have a reason.<br>Finally, you can control who can talk to whom on the network by configuring routes. You may want to connect your office network with your VPC. You can use a VPN connection to do so. The routing then determines which Subnets can send packages to your office and vice versa.</p><h2 id="Data-Encryption"><a href="#Data-Encryption" class="headerlink" title="Data Encryption"></a>Data Encryption</h2><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/05/aws-security-surface-data-encryption@730w.webp 730w, /images/2017/05/aws-security-surface-data-encryption@730w2x.webp 1460w, /images/2017/05/aws-security-surface-data-encryption@610w.webp 610w, /images/2017/05/aws-security-surface-data-encryption@610w2x.webp 1220w, /images/2017/05/aws-security-surface-data-encryption@450w.webp 450w, /images/2017/05/aws-security-surface-data-encryption@450w2x.webp 900w, /images/2017/05/aws-security-surface-data-encryption@330w.webp 330w, /images/2017/05/aws-security-surface-data-encryption@330w2x.webp 660w, /images/2017/05/aws-security-surface-data-encryption@545w.webp 545w, /images/2017/05/aws-security-surface-data-encryption@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/05/aws-security-surface-data-encryption@730w.png 730w, /images/2017/05/aws-security-surface-data-encryption@730w2x.png 1460w, /images/2017/05/aws-security-surface-data-encryption@610w.png 610w, /images/2017/05/aws-security-surface-data-encryption@610w2x.png 1220w, /images/2017/05/aws-security-surface-data-encryption@450w.png 450w, /images/2017/05/aws-security-surface-data-encryption@450w2x.png 900w, /images/2017/05/aws-security-surface-data-encryption@330w.png 330w, /images/2017/05/aws-security-surface-data-encryption@330w2x.png 660w, /images/2017/05/aws-security-surface-data-encryption@545w.png 545w, /images/2017/05/aws-security-surface-data-encryption@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/05/aws-security-surface-data-encryption.png" alt="AWS Security Surface: Data Encryption" title="AWS Security Surface: Data Encryption"></picture></p><p>Mostly all services that store data (EBS, S3, SQS, …) support encryption at rest. Which means that AWS will encrypt the data on-disk. This is handy if you (or your regulator) fears that another AWS customer could get access to your data because of the shared nature of the environment. E.g. if you return an EBS volume to AWS because you no longer need it, the underlying hard disk will be reused. AWS wipes the data but there is a very tiny chance that someone with big resources can reconstruct parts of the data. If you don’t trust AWS, you should better encrypt the data before you send it to the service or not use AWS at all.</p><blockquote><p>Spoiler: I’m not an expert on Data Encryption!</p></blockquote><p>When data is encrypted&#x2F;decrypted a key&#x2F;secret is needed. Those keys are managed by the <strong>Key Management Service</strong> (KMS). The way this works is that KMS owns the customer master key (CMK) and issues data keys that are then used by e.g. EBS to encrypt&#x2F;decrypt the data. The data key is stored together with the encrypted data, also encrypted. When EBS wants to decrypt the data gain (because you want to read it), it sends the encrypted data key to KMS, where the master key can decrypt the data key. The decrypted data key is returned to EBS where it is used to decrypt the data. Key is that the CMK never leaves KMS. If you delete the CMK, the encrypted data is garbage because it can no longer be decrypted.</p><p>If you don’t want that AWS owns&#x2F;knows your master key you can use a magic box called <a href="https://en.wikipedia.org/wiki/Hardware_security_module" target="_blank" rel="noopener">Hardware Security Module</a> to manage your master key. CloudHSM provides this boxes to you as a Service for ~$20,000 per year. The trick is that this magic hardware box audits access and destroys itself if someone wants to open it. The magic box also handles the encryption&#x2F;decryption part on it’s own hardware.</p><p>To make things easier again, data that is in transit also wants to be encrypted. Especially if this data travels trough the Internet. All AWS services that can expose HTTP endpoints (ELB, ALB, CloudFront, API Gateway) also support TLS&#x2F;SSL encryption with certificates managed by the <strong>Certificate Manager</strong> which renews certificates automatically at no costs.<br>If you want to connect your office with your VPC, you can use a VPN tunnel to encrypt the data.<br>The AWS API is also encrypting traffic using TSL&#x2F;SSL.<br>The open question is: What happens if you talk to your database from an EC2 instance? Or if you talk to your Application Servers. You should at least know which traffic is (not) encrypted and why.</p><h2 id="Governance"><a href="#Governance" class="headerlink" title="Governance"></a>Governance</h2><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/05/aws-security-surface-governance@730w.webp 730w, /images/2017/05/aws-security-surface-governance@730w2x.webp 1460w, /images/2017/05/aws-security-surface-governance@610w.webp 610w, /images/2017/05/aws-security-surface-governance@610w2x.webp 1220w, /images/2017/05/aws-security-surface-governance@450w.webp 450w, /images/2017/05/aws-security-surface-governance@450w2x.webp 900w, /images/2017/05/aws-security-surface-governance@330w.webp 330w, /images/2017/05/aws-security-surface-governance@330w2x.webp 660w, /images/2017/05/aws-security-surface-governance@545w.webp 545w, /images/2017/05/aws-security-surface-governance@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/05/aws-security-surface-governance@730w.png 730w, /images/2017/05/aws-security-surface-governance@730w2x.png 1460w, /images/2017/05/aws-security-surface-governance@610w.png 610w, /images/2017/05/aws-security-surface-governance@610w2x.png 1220w, /images/2017/05/aws-security-surface-governance@450w.png 450w, /images/2017/05/aws-security-surface-governance@450w2x.png 900w, /images/2017/05/aws-security-surface-governance@330w.png 330w, /images/2017/05/aws-security-surface-governance@330w2x.png 660w, /images/2017/05/aws-security-surface-governance@545w.png 545w, /images/2017/05/aws-security-surface-governance@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/05/aws-security-surface-governance.png" alt="AWS Security Surface: Governance" title="AWS Security Surface: Governance"></picture></p><p>Documentation is king. You write down all your security related rules in Confluence. The first point in your list: Activate MFA. 1 year later, you recognize that still half of your IAM Users have not activated MFA. Maybe you need something better?</p><ul><li>AWS provides a service to record <del>all (okay, this was marketing speak)</del> most of the API calls using <strong>CloudTrail</strong>. With this tool, you can exactly know who did what and when. You can also use Lambda to analyze this in near real time (5 minutes) and e.g. send an alert if a user deactivates MFA.</li><li><strong>AWS Config</strong> creates snapshots of all your AWS configuration where you can define rules that must be met.</li><li><strong>Trusted Advisor</strong> checks if you follow best practices. Make sure that you receive those emails and review the findings every week!</li><li><strong>VPC Flow Logs</strong> can record network flows in your VPC. You could analyze them to: <a href="/improve-security-groups-using-vpc-flow-logs-aws-config/">Improve Security (Groups) using VPC Flow Logs &amp; AWS Config</a></li></ul><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>This was a high level overview of the AWS Security surface. From my experience, customers spent most of their time securing their network because they know how to do this. I recommend that you shift your resources a little bit to make sure you get the AWS API Security right. With a single S3 Bucket Policy you can open your confident data to the world.</p><p>I missed a few points. Some of them are known:</p><ul><li>Patching the operating system (AMI)</li><li>Patching the application’s dependencies</li><li>Application internals (e.g. input validation)</li></ul><p>But I’m pretty confident that I missed others. <a href="mailto:&#109;&#105;&#x63;&#104;&#x61;&#x65;&#x6c;&#x40;&#x77;&#x69;&#100;&#x64;&#105;&#120;&#x2e;&#x64;&#x65;">Contact me</a> if you have feedback!</p><hr class="footnotes-sep"><div id="footnotes"><ol><li id="fn:1"><p>1. Mind map created with <a href="https://simplemind.eu/">SimpleMind</a> <a href="#fnref:1" class="footnote-backref">↩</a></p></li><li id="fn:2"><p>2. many people reviewed and improved the mind map (in no particular order): <a href="https://x.com/flomotlik">@flomotlik</a>, <a href="https://x.com/s0enke">@s0enke</a>, and <a href="https://x.com/sstatik">@sstatik</a>, <a href="https://x.com/LiatBenZur">@LiatBenZur</a>, <a href="https://x.com/JaniKarh">@JaniKarh</a>, <a href="https://x.com/evannuil">@evannuil</a>, <a href="https://x.com/jobrandstetter">@jobrandstetter</a>, and <a href="https://x.com/ngmarley">@ngmarley</a>. <a href="#fnref:2" class="footnote-backref">↩</a></p></li></ol></div>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Velocity Series: Summary</title>
      <link>https://cloudonaut.io/aws-velocity-summary/</link>
      <description>
        <![CDATA[<p>Do you want to be faster by using AWS? I see many AWS customers struggling with high expectations and some even slow down. That’s why I w]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/velocity/">velocity</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-velocity-summary/</guid>
      <pubDate>Tue, 02 May 2017 21:05:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Do you want to be faster by using AWS? I see many AWS customers struggling with high expectations and some even slow down. That’s why I wrote <a href="/aws-velocity-series/">the AWS Velocity Series</a>. Let me sum up what matters most when optimizing for speed.</p><h2 id="1-Select-your-runtime-environment"><a href="#1-Select-your-runtime-environment" class="headerlink" title="1. Select your runtime environment"></a>1. Select your runtime environment</h2><p>There are many options when it comes to running an application on AWS. EC2 based, containerized, or Serverless. Choosing the best option for your specific use case is important. Learn more in part 4 of the <a href="/aws-velocity-running-your-application/">AWS Velocity Series: Running your application</a>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/05/velocity-doors@730w.webp 730w, /images/2017/05/velocity-doors@730w2x.webp 1460w, /images/2017/05/velocity-doors@610w.webp 610w, /images/2017/05/velocity-doors@610w2x.webp 1220w, /images/2017/05/velocity-doors@450w.webp 450w, /images/2017/05/velocity-doors@450w2x.webp 900w, /images/2017/05/velocity-doors@330w.webp 330w, /images/2017/05/velocity-doors@330w2x.webp 660w, /images/2017/05/velocity-doors@545w.webp 545w, /images/2017/05/velocity-doors@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/05/velocity-doors@730w.png 730w, /images/2017/05/velocity-doors@730w2x.png 1460w, /images/2017/05/velocity-doors@610w.png 610w, /images/2017/05/velocity-doors@610w2x.png 1220w, /images/2017/05/velocity-doors@450w.png 450w, /images/2017/05/velocity-doors@450w2x.png 900w, /images/2017/05/velocity-doors@330w.png 330w, /images/2017/05/velocity-doors@330w2x.png 660w, /images/2017/05/velocity-doors@545w.png 545w, /images/2017/05/velocity-doors@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/05/velocity-doors.png" alt="Select your runtime environment" title="Select your runtime environment"></picture></p><p><strong>If you start from scratch, I recommend that you create a Serverless app.</strong> Details in part 7 of the <a href="/aws-velocity-serverless-app/">AWS Velocity Series: Serverless app</a>.<br>If you have an existing application, I recommend that you Dockerize the application and run it on ECS. Details in part 6 of the <a href="/aws-velocity-containerized-ecs-based-app-infrastructure/">AWS Velocity Series: Containerized ECS based app</a>.<br>If you can not Dockerize the application, run it in EC2. Details in part 5 of the <a href="/aws-velocity-ec2-based-app-infrastructure/">AWS Velocity Series: EC2 based app infrastructure</a>.</p><h2 id="2-Design-your-software-assembly-line"><a href="#2-Design-your-software-assembly-line" class="headerlink" title="2. Design your software assembly line"></a>2. Design your software assembly line</h2><p>The assembly line describes the work that is necessary from idea to production. <strong>If your assembly line allows manual errors, overwhelms critical resources, or does not continuously improve, you will never be fast.</strong> AWS will not be your savior. You have to save yourself. Design your software assembly and then iterate and improve it. A software assembly line could look like this:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/01/aws-velocity@730w.webp 730w, /images/2017/01/aws-velocity@730w2x.webp 1460w, /images/2017/01/aws-velocity@610w.webp 610w, /images/2017/01/aws-velocity@610w2x.webp 1220w, /images/2017/01/aws-velocity@450w.webp 450w, /images/2017/01/aws-velocity@450w2x.webp 900w, /images/2017/01/aws-velocity@330w.webp 330w, /images/2017/01/aws-velocity@330w2x.webp 660w, /images/2017/01/aws-velocity@545w.webp 545w, /images/2017/01/aws-velocity@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/01/aws-velocity@730w.png 730w, /images/2017/01/aws-velocity@730w2x.png 1460w, /images/2017/01/aws-velocity@610w.png 610w, /images/2017/01/aws-velocity@610w2x.png 1220w, /images/2017/01/aws-velocity@450w.png 450w, /images/2017/01/aws-velocity@450w2x.png 900w, /images/2017/01/aws-velocity@330w.png 330w, /images/2017/01/aws-velocity@330w2x.png 660w, /images/2017/01/aws-velocity@545w.png 545w, /images/2017/01/aws-velocity@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/01/aws-velocity.png" alt="AWS Velocity: Software assembly line" title="AWS Velocity: Software assembly line"></picture></p><ol><li>In a software project, you decide how to spend your resources. You can not work on everything at the same time.</li><li>You write code and tests, adjust configuration, add images, …</li><li>Every change is versioned in a repository.</li><li>The source code is built and packaged. Tests are executed, and static source code checkers analyze the source code.</li><li>The packaged software is deployed to AWS into a production-like environment.</li><li>Acceptance tests run against the production-like environment to ensure that your system still behaves as expected.</li><li>Now, you are ready to deploy to production.</li><li>All steps need to be monitored to know if something goes wrong and to decide based on data.</li></ol><p>Learn more in part 1 of the <a href="/aws-velocity-set-the-assembly-line-up/">AWS Velocity Series: Set the assembly line up</a></p><h2 id="3-Managed-services-first"><a href="#3-Managed-services-first" class="headerlink" title="3. Managed services first"></a>3. Managed services first</h2><p>Imagine your team is responsible for developing a web-based application that your business clients can use to forecast sales numbers.</p><p>The team’s next story is titled “full-text search”. The following tasks belong to the story:</p><ul><li>Install Elasticsearch on 5 nodes</li><li>Backup Elasticsearch</li><li>Monitor Elasticsearch</li><li>Alerting for Elasticsearch</li><li>Load data from DynamoDB to Elasticsearch initially</li><li>Stream data from DynamoDB to Elasticsearch</li><li>Add API endpoint to create search queries</li><li>Add search field to web app</li></ul><p>Six tasks are pretty generic:</p><ul><li>Install Elasticsearch on 5 nodes</li><li>Backup Elasticsearch</li><li>Monitor Elasticsearch</li><li>Alerting for Elasticsearch</li><li>Load data from DynamoDB to Elasticsearch initially</li><li>Stream data from DynamoDB to Elasticsearch</li></ul><p><strong>If you think that you are solving issues others also have, it’s time to check the AWS services offerings.</strong><br>You will find that AWS offers a fully managed <a href="https://aws.amazon.com/elasticsearch-service/" target="_blank" rel="noopener">Elastisearch Service</a>. This means no need to install, backup, or monitor Elasticsearch. You only need to set some thresholds for alerting.<br>You also find that DynamoDB offers a feature called Streams which can make your life easier to stream data from DynamoDB into other systems. Within a few minutes of research, you completed 4.5 out of 6 tasks by using a managed service. That was fast.</p><h2 id="AWS-Velocity"><a href="#AWS-Velocity" class="headerlink" title="AWS Velocity"></a>AWS Velocity</h2><p>AWS provides you many tools and services to improve your speed. You have to select and orchestrate them to support your goal. Start by looking at your software assembly line. Automate as much as possible and continue to invest in improvements. Get rid of as many tasks as possible by using managed services. And choose the runtime environment with the same mindset: limit your responsibilities to have time to differentiate your offering from your competitors.</p><h2 id="Series"><a href="#Series" class="headerlink" title="Series"></a>Series</h2><p align="center"><img src="/images/2017/01/aws-velocity-cover.png" alt="AWS Velocity Cover" style="max-width: 349px"></p><ol><li><a href="/aws-velocity-set-the-assembly-line-up/">Set the assembly line up</a></li><li><a href="/aws-velocity-local-development-environment/">Local development environment</a></li><li><a href="/aws-velocity-ci-cd-pipeline-as-code/">CI&#x2F;CD Pipeline as Code</a></li><li><a href="/aws-velocity-running-your-application/">Running your application</a></li><li>EC2 based app<br>  a. <a href="/aws-velocity-ec2-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-ec2-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li>Containerized ECS based app<br>  a. <a href="/aws-velocity-containerized-ecs-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-containerized-ecs-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li><a href="/aws-velocity-serverless-app/">Serverless app</a></li><li><strong>Summary</strong> (you are here)</li></ol><p>You can find the source code on <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">GitHub</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>New CloudFormation Template - Operational Alerts and new Docs</title>
      <link>https://cloudonaut.io/new-cloudformation-template-operations-alerts-and-docs/</link>
      <description>
        <![CDATA[<p>Today, we release v3.1.x of our <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">Free Templates for AW]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/new-cloudformation-template-operations-alerts-and-docs/</guid>
      <pubDate>Fri, 28 Apr 2017 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Today, we release v3.1.x of our <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a>. The highlights of the release are:</p><ul><li>You can now receive alerts if the infrastructure is not working with the <code>oprations/alert.yaml</code> template. Existing templates now have a <code>ParentAlertStack</code> parameter to enable alerting for the stack.</li><li>We updated the AMIs to <a href="https://aws.amazon.com/amazon-linux-ami/2017.03-release-notes/" target="_blank" rel="noopener">Amazon Linux 2017.03</a></li><li>The documentation has a new home: <a href="https://templates.cloudonaut.io/" target="_blank" rel="noopener">https://templates.cloudonaut.io/</a></li></ul><p>Starting with Version 3.1.0, we host all template versions on S3. The Links in the documentation reflect this change. The <code>stable</code> docs always point to the latest release. The <code>latest</code> docs point to the master branch which is work in progress. You will also find a separate documentation for each released version. Try it out: <a href="https://templates.cloudonaut.io/" target="_blank" rel="noopener">https://templates.cloudonaut.io/</a></p><h2 id="Operations-Alert-template"><a href="#Operations-Alert-template" class="headerlink" title="Operations Alert template"></a>Operations Alert template</h2><p>This template describes an SNS topic that can be used by many other templates to receive alerts. You can add one or multiple subscribers to this topic and they will all receive the same alerts. Supported transports are:</p><ul><li>Email</li><li>HTTP endpoint</li><li>HTTPS endpoint (can be used by <a href="https://marbot.io/" target="_blank" rel="noopener">marbot</a>)</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/04/operations-alert@730w.webp 730w, /images/2017/04/operations-alert@730w2x.webp 1460w, /images/2017/04/operations-alert@610w.webp 610w, /images/2017/04/operations-alert@610w2x.webp 1220w, /images/2017/04/operations-alert@450w.webp 450w, /images/2017/04/operations-alert@450w2x.webp 900w, /images/2017/04/operations-alert@330w.webp 330w, /images/2017/04/operations-alert@330w2x.webp 660w, /images/2017/04/operations-alert@545w.webp 545w, /images/2017/04/operations-alert@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/04/operations-alert@730w.png 730w, /images/2017/04/operations-alert@730w2x.png 1460w, /images/2017/04/operations-alert@610w.png 610w, /images/2017/04/operations-alert@610w2x.png 1220w, /images/2017/04/operations-alert@450w.png 450w, /images/2017/04/operations-alert@450w2x.png 900w, /images/2017/04/operations-alert@330w.png 330w, /images/2017/04/operations-alert@330w2x.png 660w, /images/2017/04/operations-alert@545w.png 545w, /images/2017/04/operations-alert@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/04/operations-alert.png" alt="Architecture" title="Architecture"></picture></p><p>To enable alerting on one of our templates (e.g. the <a href="https://github.com/widdix/aws-cf-templates/blob/master/vpc/vpc-ssh-bastion.yaml" target="_blank" rel="noopener">SSH Bastion Host</a>) you have to provide the <code>ParentAlertStack</code> parameter. If no such parameter exists, the template does not support alerting. </p><p><a href="https://templates.cloudonaut.io/en/stable/operations/#alert-topic" target="_blank" rel="noopener">Install the template for free</a></p><h2 id="Full-Changelog"><a href="#Full-Changelog" class="headerlink" title="Full Changelog"></a>Full Changelog</h2><ul><li><strong>[Security] Updated WordPress to 4.7.4</strong></li><li>[New template] Alert topic that integrates with all other templates for operational alerts via SNS</li><li>[Improvement] Added host based routing in ECS service template</li><li>[Improvement] Jenkins agents now get the agent label</li><li>[Improvement] Updated to Amazon Linux 2017.03</li><li>[Improvement] Using more than two Availability Zones if possible (vpc-3azs, vpc-4azs)</li><li>[Improvement] Provide a fallback Email for operational alerts</li><li>[Improvement] Moved docs to <a href="https://templates.cloudonaut.io/" target="_blank" rel="noopener">https://templates.cloudonaut.io/</a> and also host each release versioned on S3</li></ul><h2 id="Why-Free-Templates-for-AWS-CloudFormation"><a href="#Why-Free-Templates-for-AWS-CloudFormation" class="headerlink" title="Why Free Templates for AWS CloudFormation"></a>Why Free Templates for AWS CloudFormation</h2><ul><li>Speed up development and migration: reuse our templates to create complex environments for common use cases with ease.</li><li>Rely on high-quality infrastructure templates: peer-reviewed by an expert (certified AWS solutions architect Professional) and verified with automated tests.</li><li>All templates are production-ready. If no other limitations are documented, they are:<ul><li>Highly available: no single point of failure</li><li>Scalable: increase or decrease the number of instances based on load</li><li>Frictionless deployment: deliver new versions of your application automatically without downtime</li><li>Secure: using the latest operating systems and software components, follow the least privilege principle in all areas</li><li>Operations: provide tools like logging, monitoring and alerting to recognize and debug problems</li></ul></li><li>Premium Support available: Get help in case of small and big emergencies and submit a feature request.</li></ul><h2 id="Premium-Support"><a href="#Premium-Support" class="headerlink" title="Premium Support"></a>Premium Support</h2><p>We offer Premium Support for our CloudFormation templates: setting up environments based on our templates, adopting templates to specific use cases, resolving issues in production environments. <a href="https://widdix.net/" target="_blank" rel="noopener">Hire us!</a></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Velocity Series: Serverless app</title>
      <link>https://cloudonaut.io/aws-velocity-serverless-app/</link>
      <description>
        <![CDATA[<p>The API Gateway provides an HTTPS endpoint that invokes a Lambda function when a request arrives.</p>
<p><picture class="img-fluid"><sour]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/serverless/">Serverless</category>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/apigateway/">apigateway</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/velocity/">velocity</category>
      <category domain="https://cloudonaut.io/tag/codebuild/">codebuild</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-velocity-serverless-app/</guid>
      <pubDate>Mon, 24 Apr 2017 07:05:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The API Gateway provides an HTTPS endpoint that invokes a Lambda function when a request arrives.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/04/serverless-flow@730w.webp 730w, /images/2017/04/serverless-flow@730w2x.webp 1460w, /images/2017/04/serverless-flow@610w.webp 610w, /images/2017/04/serverless-flow@610w2x.webp 1220w, /images/2017/04/serverless-flow@450w.webp 450w, /images/2017/04/serverless-flow@450w2x.webp 900w, /images/2017/04/serverless-flow@330w.webp 330w, /images/2017/04/serverless-flow@330w2x.webp 660w, /images/2017/04/serverless-flow@545w.webp 545w, /images/2017/04/serverless-flow@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/04/serverless-flow@730w.png 730w, /images/2017/04/serverless-flow@730w2x.png 1460w, /images/2017/04/serverless-flow@610w.png 610w, /images/2017/04/serverless-flow@610w2x.png 1220w, /images/2017/04/serverless-flow@450w.png 450w, /images/2017/04/serverless-flow@450w2x.png 900w, /images/2017/04/serverless-flow@330w.png 330w, /images/2017/04/serverless-flow@330w2x.png 660w, /images/2017/04/serverless-flow@545w.png 545w, /images/2017/04/serverless-flow@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/04/serverless-flow.png" alt="Serverless request flow" title="Serverless request flow"></picture></p><p><em>The diagram was created with <a href="https://bit.ly/2PpMc2M" target="_blank" rel="noopener">Cloudcraft - Visualize your cloud architecture like a pro</a>.</em></p><p>As you can see, there is not much infrastructure to set up. To makes things even simpler, you will use the AWS <a href="https://github.com/awslabs/serverless-application-model" target="_blank" rel="noopener">Serverless Application Model</a> (AWS SAM) to reduce the lines of your CloudFormation template to a minimum. All CloudFormation resource types that start with <code>AWS::Serverless::</code> are transformed by SAM.</p><div class="well">  <div class="media">    <div class="media-left">      <a href="/aws-velocity-series/">        <img class="media-object raw" src="/images/2017/04/aws-velocity-cover-icon.png" alt="AWS Velocity Cover">      </a>    </div>    <div class="media-body">      <h4 class="media-heading">AWS Velocity Series</h4>      <p>Most of our clients use AWS to reduce time-to-market following an agile approach. But AWS is only one part of the solution. In this article series, I show you how we help our clients to improve velocity: the time from idea to production. <a href="/aws-velocity-series/">Discover all posts!</a></p>    </div>  </div></div><p>Let’s start to describe the needed infrastructure.</p><h2 id="Serverless-app-infrastructure"><a href="#Serverless-app-infrastructure" class="headerlink" title="Serverless app infrastructure"></a>Serverless app infrastructure</h2><p>The serverless app infrastructure for the factorial app consists of two parts:</p><ul><li>API Gateway: Provides a configurable HTTPS REST Endpoint that can trigger integrations such as Lambda when a request arrives.</li><li>Lambda function: Lambda provides a fully managed (aka Serverless) runtime for Node.js, Java, Python, and C# code. You upload your code and Lambda runs the code for you.</li></ul><p>I will start with the Lambda function. </p><h3 id="Lambda-function"><a href="#Lambda-function" class="headerlink" title="Lambda function"></a>Lambda function</h3><blockquote><p>You can follow step by step or get the full source code here: <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">https://github.com/widdix/aws-velocity</a></p></blockquote><p>Create a file <code>infrastructure/serverless.yml</code> and describe the Lambda function that is invoked on <code>GET /&#123;number&#125;</code> requests.</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;serverless.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/serverless.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Transform:</span> <span class="string">&#x27;AWS::Serverless-2016-10-31&#x27;</span> <span class="comment"># this line activates the SAM transformations!</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&#x27;Serverless&#x27;</span></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="comment"># S3Bucket and S3Key where the zipped code is located. This will be created with CodeBuild</span></span><br><span class="line">  <span class="attr">S3Bucket:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">  <span class="attr">S3Key:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">GetFactorialLambda:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Serverless::Function&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Handler:</span> <span class="string">&#x27;app/handler.factorial&#x27;</span></span><br><span class="line">      <span class="attr">Runtime:</span> <span class="string">&#x27;nodejs6.10&#x27;</span></span><br><span class="line">      <span class="attr">CodeUri:</span></span><br><span class="line">        <span class="attr">Bucket:</span> <span class="type">!Ref</span> <span class="string">S3Bucket</span></span><br><span class="line">        <span class="attr">Key:</span> <span class="type">!Ref</span> <span class="string">S3Key</span></span><br><span class="line">      <span class="attr">Events:</span></span><br><span class="line">        <span class="attr">Http:</span></span><br><span class="line">          <span class="attr">Type:</span> <span class="string">Api</span></span><br><span class="line">          <span class="attr">Properties:</span></span><br><span class="line">            <span class="attr">Path:</span> <span class="string">/&#123;n&#125;</span></span><br><span class="line">            <span class="attr">Method:</span> <span class="string">get</span></span><br><span class="line">            <span class="attr">RestApiId:</span> <span class="type">!Ref</span> <span class="string">ApiGateway</span></span><br></pre></td></tr></table></figure><p>Lambda dictates an interface that you have to follow. So far, the factorial app is based on <code>express</code> and comes with its own web server. This is no longer needed. Instead, we can have a simpler entry point into the application. Create a file <code>app/handler.js</code> with the following content.</p><figure class="highlight javascript"><figcaption><span>app&#x2F;handler.js</span><a href="https://github.com/widdix/aws-velocity/blob/master/app/handler.js">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> factorial = <span class="built_in">require</span>(<span class="string">&#x27;./lib/factorial.js&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Lambda dictates an interface: a function with 3 arguments</span></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">factorial</span> = <span class="keyword">function</span>(<span class="params">event, context, cb</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> n = <span class="built_in">parseInt</span>(event.<span class="property">pathParameters</span>.<span class="property">n</span>, <span class="number">10</span>);</span><br><span class="line">  <span class="keyword">if</span> (n &lt; <span class="number">0</span> || n &gt; <span class="number">14</span>) &#123;</span><br><span class="line">    <span class="title function_">cb</span>(<span class="literal">null</span>, &#123;</span><br><span class="line">      <span class="attr">statusCode</span>: <span class="number">400</span></span><br><span class="line">    &#125;);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="title function_">cb</span>(<span class="literal">null</span>, &#123;</span><br><span class="line">      <span class="attr">statusCode</span>: <span class="number">200</span>,</span><br><span class="line">      <span class="attr">headers</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;text/plain&#x27;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">body</span>: <span class="title function_">factorial</span>(n).<span class="title function_">toString</span>()</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>But how do you get notified if something goes wrong? Let’s add a parameter to the <code>Parameters</code> section to make the receiver configurable:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;serverless.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/serverless.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">AdminEmail:</span></span><br><span class="line">  <span class="attr">Description:</span> <span class="string">&#x27;The email address of the admin who receives alerts.&#x27;</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">String</span></span><br></pre></td></tr></table></figure><p>Alerts are triggered by a CloudWatch Alarm which can send an alert to an SNS topic. You can subscribe to this topic via an email address to receive the alerts. Let’s create an SNS topic and two alarms in the <code>Resources</code> section:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;serverless.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/serverless.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># A SNS topic is used to send alerts via Email to the value of the AdminEmail parameter </span></span><br><span class="line"><span class="attr">Alerts:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::SNS::Topic&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Subscription:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Endpoint:</span> <span class="type">!Ref</span> <span class="string">AdminEmail</span></span><br><span class="line">      <span class="attr">Protocol:</span> <span class="string">email</span></span><br><span class="line"><span class="comment"># This alarm is triggered, if the Node.js function returns or throws an Error</span></span><br><span class="line"><span class="attr">GetFactorialLambdaLambdaErrorsAlarm:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AlarmDescription:</span> <span class="string">&#x27;GET /&#123;n&#125; lambda errors&#x27;</span></span><br><span class="line">    <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/Lambda&#x27;</span></span><br><span class="line">    <span class="attr">MetricName:</span> <span class="string">Errors</span></span><br><span class="line">    <span class="attr">Dimensions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">FunctionName</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">GetFactorialLambda</span></span><br><span class="line">    <span class="attr">Statistic:</span> <span class="string">Sum</span></span><br><span class="line">    <span class="attr">Period:</span> <span class="number">60</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanOrEqualToThreshold</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Alerts</span></span><br><span class="line"><span class="comment"># This alarm is triggered, if the there are too many function invocations</span></span><br><span class="line"><span class="attr">GetFactorialLambdaLambdaThrottlesAlarm:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AlarmDescription:</span> <span class="string">&#x27;GET /&#123;n&#125; lambda throttles&#x27;</span></span><br><span class="line">    <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/Lambda&#x27;</span></span><br><span class="line">    <span class="attr">MetricName:</span> <span class="string">Throttles</span></span><br><span class="line">    <span class="attr">Dimensions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">FunctionName</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">GetFactorialLambda</span></span><br><span class="line">    <span class="attr">Statistic:</span> <span class="string">Sum</span></span><br><span class="line">    <span class="attr">Period:</span> <span class="number">60</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanOrEqualToThreshold</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Alerts</span></span><br></pre></td></tr></table></figure><p>Let’s recap what you implemented: A Lambda function that is connected to an API Gateway for <code>GET /&#123;number&#125;</code> requests. In the case of errors, you will receive an Email. All Lambda functions automatically save their logs in CloudWatch Logs.</p><p>Now, you can improve the API Gateway setup and add input validation.</p><h3 id="API-Gateway"><a href="#API-Gateway" class="headerlink" title="API Gateway"></a>API Gateway</h3><p>An implicit API Gateway is created and configured automatically when using SAM. But if you want to validate the input on the API Gateway, you have to define the API Gateway explicitly to add the API specification in more details by using <a href="https://github.com/OAI/OpenAPI-Specification" target="_blank" rel="noopener">the open standard Swagger &#x2F; OpenAPI Spec</a>. Let’s do this in the <code>Resources</code> section:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;serverless.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/serverless.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">ApiGateway:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::Serverless::Api&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">StageName:</span> <span class="string">Prod</span></span><br><span class="line">    <span class="attr">DefinitionBody:</span></span><br><span class="line">      <span class="attr">swagger:</span> <span class="string">&#x27;2.0&#x27;</span></span><br><span class="line">      <span class="attr">basePath:</span> <span class="string">&#x27;/&#x27;</span></span><br><span class="line">      <span class="attr">info:</span></span><br><span class="line">        <span class="attr">title:</span> <span class="string">Serverless</span></span><br><span class="line">      <span class="attr">schemes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">https</span></span><br><span class="line">      <span class="comment"># We want to validate the body and request parameters</span></span><br><span class="line">      <span class="attr">x-amazon-apigateway-request-validators:</span></span><br><span class="line">        <span class="attr">basic:</span></span><br><span class="line">          <span class="attr">validateRequestBody:</span> <span class="literal">true</span></span><br><span class="line">          <span class="attr">validateRequestParameters:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">paths:</span></span><br><span class="line">        <span class="string">&#x27;/&#123;n&#125;&#x27;</span><span class="string">:</span></span><br><span class="line">          <span class="attr">parameters:</span> <span class="comment"># we expect one parameter in the path of type number</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">&#x27;n&#x27;</span></span><br><span class="line">            <span class="attr">in:</span> <span class="string">path</span></span><br><span class="line">            <span class="attr">description:</span> <span class="string">&#x27;N&#x27;</span></span><br><span class="line">            <span class="attr">required:</span> <span class="literal">true</span></span><br><span class="line">            <span class="attr">type:</span> <span class="string">number</span></span><br><span class="line">          <span class="attr">get:</span></span><br><span class="line">            <span class="attr">produces:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;text/plain&#x27;</span></span><br><span class="line">            <span class="attr">responses:</span></span><br><span class="line">              <span class="attr">&#x27;200&#x27;:</span></span><br><span class="line">                <span class="attr">description:</span> <span class="string">&#x27;factorial calculated&#x27;</span></span><br><span class="line">                <span class="attr">schema:</span></span><br><span class="line">                  <span class="attr">type:</span> <span class="string">number</span></span><br><span class="line">            <span class="attr">x-amazon-apigateway-request-validator:</span> <span class="string">basic</span> <span class="comment"># enable validation for this resource</span></span><br><span class="line">            <span class="attr">x-amazon-apigateway-integration:</span> <span class="comment"># this section connect the Lambda function with the API Gateway</span></span><br><span class="line">              <span class="attr">httpMethod:</span> <span class="string">POST</span></span><br><span class="line">              <span class="attr">type:</span> <span class="string">&#x27;aws_proxy&#x27;</span></span><br><span class="line">              <span class="attr">uri:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:apigateway:$&#123;AWS::Region&#125;:lambda:path/2015-03-31/functions/$&#123;GetFactorialLambda.Arn&#125;/invocations&#x27;</span></span><br><span class="line">              <span class="attr">passthroughBehavior:</span> <span class="string">when_no_match</span></span><br></pre></td></tr></table></figure><p>If you are familiar with Swagger &#x2F; OpenApi Spec you will find nothing special besides the <code>x-*</code> parameters which are API Gateway specific. You can also monitor the API Gateway. To do so, append the following section the <code>Resources</code> section of your template:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;serverless.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/serverless.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">ApiGateway5XXErrorAlarm:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AlarmDescription:</span> <span class="string">&#x27;Api Gateway server-side errors captured&#x27;</span></span><br><span class="line">    <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/ApiGateway&#x27;</span></span><br><span class="line">    <span class="attr">MetricName:</span> <span class="string">5XXError</span></span><br><span class="line">    <span class="attr">Dimensions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">ApiName</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">ApiGateway</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Stage</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="string">Prod</span></span><br><span class="line">    <span class="attr">Statistic:</span> <span class="string">Sum</span></span><br><span class="line">    <span class="attr">Period:</span> <span class="number">60</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanOrEqualToThreshold</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Alerts</span></span><br></pre></td></tr></table></figure><p>You will now receive alerts via email if the API Gateway returns an <code>5XX</code> HTTP status code. API Gateway can also save logs to CloudWatch Logs <strong>but SAM lacks support to enable logging at the moment</strong>. </p><p>Let’s add some outputs to the stack to make it easier to connect with the API Gateway later on.</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;serverless.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/serverless.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># A CloudFormation stack can return information that is needed by other stacks or scripts.</span></span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">DNSName:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;The DNS name for the API gateway.&#x27;</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ApiGateway&#125;.execute-api.$&#123;AWS::Region&#125;.amazonaws.com&#x27;</span></span><br><span class="line">    <span class="attr">Export:</span></span><br><span class="line">      <span class="attr">Name:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-DNSName&#x27;</span></span><br><span class="line">  <span class="comment"># The URL is needed to run the acceptance test against the correct endpoint</span></span><br><span class="line">  <span class="attr">URL:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;URL to the API gateway.&#x27;</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Sub</span> <span class="string">&#x27;https://$&#123;ApiGateway&#125;.execute-api.$&#123;AWS::Region&#125;.amazonaws.com/Prod&#x27;</span></span><br><span class="line">    <span class="attr">Export:</span></span><br><span class="line">      <span class="attr">Name:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-URL&#x27;</span></span><br></pre></td></tr></table></figure><p>Now you have a production ready Serverless infrastructure defined in CloudFormation with the help of SAM. It’s time to deploy the Serverless app.</p><h2 id="Serverless-app-CI-CD-pipeline"><a href="#Serverless-app-CI-CD-pipeline" class="headerlink" title="Serverless app CI&#x2F;CD pipeline"></a>Serverless app CI&#x2F;CD pipeline</h2><p>The pipeline is based on the <a href="/aws-velocity-ci-cd-pipeline-as-code/">CI&#x2F;CD Pipeline as Code</a> part of this series. The pipeline so far stops when the application artifact (Zip file) is created. An acceptance test artifact is also created. What is missing?</p><ol><li>Create or update an acceptance environment based on the CloudFormation+SAM template <code>serverless.yml</code></li><li>Run the acceptance tests</li><li>Create or update a production environment based on the CloudFormation+SAM template <code>serverless.yml</code></li></ol><p>This is how the pipeline looks from the beginning to the end:</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/04/serverless-pipeline@730w.webp 730w, /images/2017/04/serverless-pipeline@730w2x.webp 1460w, /images/2017/04/serverless-pipeline@610w.webp 610w, /images/2017/04/serverless-pipeline@610w2x.webp 1220w, /images/2017/04/serverless-pipeline@450w.webp 450w, /images/2017/04/serverless-pipeline@450w2x.webp 900w, /images/2017/04/serverless-pipeline@330w.webp 330w, /images/2017/04/serverless-pipeline@330w2x.webp 660w, /images/2017/04/serverless-pipeline@545w.webp 545w, /images/2017/04/serverless-pipeline@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/04/serverless-pipeline@730w.png 730w, /images/2017/04/serverless-pipeline@730w2x.png 1460w, /images/2017/04/serverless-pipeline@610w.png 610w, /images/2017/04/serverless-pipeline@610w2x.png 1220w, /images/2017/04/serverless-pipeline@450w.png 450w, /images/2017/04/serverless-pipeline@450w2x.png 900w, /images/2017/04/serverless-pipeline@330w.png 330w, /images/2017/04/serverless-pipeline@330w2x.png 660w, /images/2017/04/serverless-pipeline@545w.png 545w, /images/2017/04/serverless-pipeline@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/04/serverless-pipeline.png" alt="Serverless CI/CD pipeline" title="Serverless CI/CD pipeline"></picture></p><p>In the EC2 and ECS examples, I used <code>CREATE_UPDATE</code> in the CodePipeline to create or update the CloudFormation stack. At this point, SAM works only if you use CloudFormation Change Sets. Therefore I have to switch from a single <code>CREATE_UPDATE</code> step to <code>CreateChangeSet</code> and <code>ApplyChangeSet</code> in this example. </p><p>Copy the <code>deploy/pipeline.yml</code> file to <code>deploy/pipeline_serverless.yml</code> to get the starting point right. If you don’t have the <code>deploy/pipeline.yml</code> file you can download it from <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">https://github.com/widdix/aws-velocity</a>.</p><h3 id="Acceptance-stage"><a href="#Acceptance-stage" class="headerlink" title="Acceptance stage"></a>Acceptance stage</h3><p>The acceptance stage consists of a CloudFormation stack based on <code>infrastructure/serverless.yml</code> and the execution of the acceptance tests. To create the CloudFormation stack, you first have to provide a few parameters. Create a file <code>infrastructure/serverless.json</code> with the following content:</p><figure class="highlight json"><figcaption><span>infrastructure&#x2F;serverless.json</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/serverless.json">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Parameters&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;S3Bucket&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="attr">&quot;Fn::GetArtifactAtt&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;App&quot;</span><span class="punctuation">,</span> <span class="string">&quot;BucketName&quot;</span><span class="punctuation">]</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;S3Key&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="attr">&quot;Fn::GetArtifactAtt&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;App&quot;</span><span class="punctuation">,</span> <span class="string">&quot;ObjectKey&quot;</span><span class="punctuation">]</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;AdminEmail&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your@email.com&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Make sure to change the value of the <code>AdminEmail</code> parameter. Look at the <code>S3Bucket</code> and <code>S3Key</code> parameter value. This is the way of getting the artifact location in CodePipeline.</p><p>To run the acceptance tests, you need another CodeBuild project, add the following resources to the <code>Resources</code> section of <code>deploy/pipeline_serverless.yml</code>:</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline_serverless.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline_serverless.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">RunAcceptanceCodeBuildRole:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">      <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">      <span class="attr">Statement:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">        <span class="attr">Principal:</span></span><br><span class="line">          <span class="attr">Service:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;codebuild.amazonaws.com&#x27;</span></span><br><span class="line">        <span class="attr">Action:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    <span class="attr">Policies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">ServiceRole</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">CloudWatchLogsPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogGroup&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogStream&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">CodeCommitPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;codecommit:GitPull&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">S3GetObjectPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;s3:GetObject&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;s3:GetObjectVersion&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">S3PutObjectPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">&#x27;Allow&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;s3:PutObject&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">CloudFormation</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">CloudFormation</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;cloudformation:DescribeStacks&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:cloudformation:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:stack/$&#123;AWS::StackName&#125;-acceptance/*&#x27;</span></span><br><span class="line"><span class="attr">RunAcceptanceProject:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodeBuild::Project&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Artifacts:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">    <span class="attr">Environment:</span></span><br><span class="line">      <span class="attr">ComputeType:</span> <span class="string">&#x27;BUILD_GENERAL1_SMALL&#x27;</span></span><br><span class="line">      <span class="attr">Image:</span> <span class="string">&#x27;aws/codebuild/nodejs:6.3.1&#x27;</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">&#x27;LINUX_CONTAINER&#x27;</span></span><br><span class="line">    <span class="attr">Name:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-run-acceptance&#x27;</span></span><br><span class="line">    <span class="attr">ServiceRole:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;RunAcceptanceCodeBuildRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">Source:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">      <span class="attr">BuildSpec:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">        version: 0.1</span></span><br><span class="line"><span class="string">        phases:</span></span><br><span class="line"><span class="string">          build: # execute acceptance tests against the acceptance stack</span></span><br><span class="line"><span class="string">            commands:</span></span><br><span class="line"><span class="string">            - &#x27;cd acceptance/ &amp;&amp; ENDPOINT=`aws cloudformation describe-stacks --stack-name $&#123;AWS::StackName&#125;-acceptance --query &quot;Stacks[0].Outputs[?OutputKey==&#x27;&#x27;URL&#x27;&#x27;].OutputValue&quot; --output text` ./node_modules/jasmine-node/bin/jasmine-node .&#x27;</span></span><br><span class="line"><span class="string"></span>    <span class="attr">TimeoutInMinutes:</span> <span class="number">10</span></span><br></pre></td></tr></table></figure><p>Now the CodeBuild project needs to be called in the pipeline, therefore change <code>Pipeline</code> resource in the file <code>deploy/pipeline_serverless.yml</code> to:</p><ol><li>deploy the CloudFormation stack suffixed with <code>-acceptance</code></li><li>run the acceptance tests</li></ol><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline_serverless.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline_serverless.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">Pipeline:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodePipeline::Pipeline&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">BuildAndTestAcceptance</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">AcceptanceProject</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="attr">OutputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Acceptance</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br><span class="line">    <span class="comment"># NEW STUFF!</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Acceptance</span></span><br><span class="line">      <span class="attr">Actions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">CreateChangeSet</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Deploy</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CloudFormation</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ActionMode:</span> <span class="string">CHANGE_SET_REPLACE</span></span><br><span class="line">          <span class="attr">Capabilities:</span> <span class="string">CAPABILITY_IAM</span></span><br><span class="line">          <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CloudFormationRole.Arn&#x27;</span></span><br><span class="line">          <span class="attr">ChangeSetName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-acceptance&#x27;</span></span><br><span class="line">          <span class="attr">StackName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-acceptance&#x27;</span></span><br><span class="line">          <span class="attr">TemplatePath:</span> <span class="string">&#x27;Source::infrastructure/serverless.yml&#x27;</span></span><br><span class="line">          <span class="attr">TemplateConfiguration:</span> <span class="string">&#x27;Source::infrastructure/serverless.json&#x27;</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">App</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">ApplyChangeSet</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Deploy</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CloudFormation</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ActionMode:</span> <span class="string">CHANGE_SET_EXECUTE</span></span><br><span class="line">          <span class="attr">Capabilities:</span> <span class="string">CAPABILITY_IAM</span></span><br><span class="line">          <span class="attr">ChangeSetName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-acceptance&#x27;</span></span><br><span class="line">          <span class="attr">StackName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-acceptance&#x27;</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">2</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Test</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">RunAcceptanceProject</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Acceptance</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">3</span></span><br></pre></td></tr></table></figure><p>The acceptance stage is now ready.</p><h3 id="Production-stage"><a href="#Production-stage" class="headerlink" title="Production stage"></a>Production stage</h3><p>The production stage is pretty simple, just one CloudFormation stack. Change the <code>Pipeline</code> resource in the file <code>deploy/pipeline_serverless.yml</code> to add a new stage that looks familiar to the acceptance stage:</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline_serverless.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline_serverless.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">Pipeline:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodePipeline::Pipeline&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Test</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">RunAcceptanceProject</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Acceptance</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">3</span></span><br><span class="line">    <span class="comment"># NEW STUFF!  </span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Production</span></span><br><span class="line">      <span class="attr">Actions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">CreateChangeSet</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Deploy</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CloudFormation</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ActionMode:</span> <span class="string">CHANGE_SET_REPLACE</span></span><br><span class="line">          <span class="attr">Capabilities:</span> <span class="string">CAPABILITY_IAM</span></span><br><span class="line">          <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CloudFormationRole.Arn&#x27;</span></span><br><span class="line">          <span class="attr">ChangeSetName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-production&#x27;</span></span><br><span class="line">          <span class="attr">StackName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-production&#x27;</span></span><br><span class="line">          <span class="attr">TemplatePath:</span> <span class="string">&#x27;Source::infrastructure/serverless.yml&#x27;</span></span><br><span class="line">          <span class="attr">TemplateConfiguration:</span> <span class="string">&#x27;Source::infrastructure/serverless.json&#x27;</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">App</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">ApplyChangeSet</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Deploy</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CloudFormation</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ActionMode:</span> <span class="string">CHANGE_SET_EXECUTE</span></span><br><span class="line">          <span class="attr">Capabilities:</span> <span class="string">CAPABILITY_IAM</span></span><br><span class="line">          <span class="attr">ChangeSetName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-production&#x27;</span></span><br><span class="line">          <span class="attr">StackName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-production&#x27;</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">2</span></span><br></pre></td></tr></table></figure><p>Now the application is deployed to production with confidence and without disturbing the users. Try it and run the pipeline!</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Let’s use my production-ready definition to summarize how each point is implemented:</p><ul><li>Highly available: API Gateway and Lambda are both highly available services out of the box. So your solution is HA as well.</li><li>Scalable:  API Gateway and Lambda are both scaled automatically by AWS for you. You have not to manage anything here!</li><li>Frictionless deployment: The new zip file created by CodeBuild is deployed with CloudFormation by changing the value of the parameter which is passed down to the Lambda resource.</li><li>Secure: HTTPS by default. AWS cares about patching. You only need to care about the IAM permissions of your Lambda function. In this case is has only the default permissions to write to CloudWatch Logs.</li><li>Operations: All logs are stored in CloudWatch Logs, important metrics are monitored, and alarms are defined.</li></ul><p>If you now have the impression that deploying and running a Serverless app is easy you are right. </p><h2 id="Series"><a href="#Series" class="headerlink" title="Series"></a>Series</h2><p align="center"><img src="/images/2017/01/aws-velocity-cover.png" alt="AWS Velocity Cover" style="max-width: 349px"></p><ol><li><a href="/aws-velocity-set-the-assembly-line-up/">Set the assembly line up</a></li><li><a href="/aws-velocity-local-development-environment/">Local development environment</a></li><li><a href="/aws-velocity-ci-cd-pipeline-as-code/">CI&#x2F;CD Pipeline as Code</a></li><li><a href="/aws-velocity-running-your-application/">Running your application</a></li><li>EC2 based app<br>  a. <a href="/aws-velocity-ec2-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-ec2-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li>Containerized ECS based app<br>  a. <a href="/aws-velocity-containerized-ecs-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-containerized-ecs-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li><strong>Serverless app</strong> (you are here)</li><li><a href="/aws-velocity-summary/">Summary</a></li></ol><p>You can find the source code on <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">GitHub</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Velocity Series: Containerized ECS based app CI/CD pipeline</title>
      <link>https://cloudonaut.io/aws-velocity-containerized-ecs-based-app-ci-cd-pipeline/</link>
      <description>
        <![CDATA[<p>In the <a href="/aws-velocity-containerized-ecs-based-app-infrastructure/">previous article</a>, you learned how to use CloudFormation to]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/continuous-delivery/">Continuous Delivery</category>
      <category domain="https://cloudonaut.io/tag/velocity/">velocity</category>
      <category domain="https://cloudonaut.io/tag/codebuild/">codebuild</category>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-velocity-containerized-ecs-based-app-ci-cd-pipeline/</guid>
      <pubDate>Mon, 03 Apr 2017 18:05:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>In the <a href="/aws-velocity-containerized-ecs-based-app-infrastructure/">previous article</a>, you learned how to use CloudFormation to describe a production-ready infrastructure for a containerized app running on ECS. In this article you will learn to:</p><ul><li>Automate the creation of a Docker image that contains the app</li><li>Deploy a CloudFormation stack based <code>infrastructure/ecs.yml</code> with AWS CodePipeline</li><li>Run acceptance tests on AWS CodeBuild against the infrastructure created in the previous step</li><li>Deploy another CloudFormation stack for the production environment</li></ul><blockquote><p>You can follow step by step or get the full source code here: <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">https://github.com/widdix/aws-velocity</a></p></blockquote><p>The pipeline is based on the <a href="/aws-velocity-ci-cd-pipeline-as-code/">CI&#x2F;CD Pipeline as Code</a> part of this series.</p><div class="well">  <div class="media">    <div class="media-left">      <a href="/aws-velocity-series/">        <img class="media-object raw" src="/images/2017/04/aws-velocity-cover-icon.png" alt="AWS Velocity Cover">      </a>    </div>    <div class="media-body">      <h4 class="media-heading">AWS Velocity Series</h4>      <p>Most of our clients use AWS to reduce time-to-market following an agile approach. But AWS is only one part of the solution. In this article series, I show you how we help our clients to improve velocity: the time from idea to production. <a href="/aws-velocity-series/">Discover all posts!</a></p>    </div>  </div></div><h2 id="Docker-image"><a href="#Docker-image" class="headerlink" title="Docker image"></a>Docker image</h2><p>The Docker image contains the runtime and your application. You will use Amazon Linux, Node.js, and the <a href="/aws-velocity-local-development-environment/">factorial application, developed in a previous post</a> to create your Docker image. The Docker image is later used by Docker containers running on the ECS cluster.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/04/docker-image-flow@730w.webp 730w, /images/2017/04/docker-image-flow@730w2x.webp 1460w, /images/2017/04/docker-image-flow@610w.webp 610w, /images/2017/04/docker-image-flow@610w2x.webp 1220w, /images/2017/04/docker-image-flow@450w.webp 450w, /images/2017/04/docker-image-flow@450w2x.webp 900w, /images/2017/04/docker-image-flow@330w.webp 330w, /images/2017/04/docker-image-flow@330w2x.webp 660w, /images/2017/04/docker-image-flow@545w.webp 545w, /images/2017/04/docker-image-flow@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/04/docker-image-flow@730w.png 730w, /images/2017/04/docker-image-flow@730w2x.png 1460w, /images/2017/04/docker-image-flow@610w.png 610w, /images/2017/04/docker-image-flow@610w2x.png 1220w, /images/2017/04/docker-image-flow@450w.png 450w, /images/2017/04/docker-image-flow@450w2x.png 900w, /images/2017/04/docker-image-flow@330w.png 330w, /images/2017/04/docker-image-flow@330w2x.png 660w, /images/2017/04/docker-image-flow@545w.png 545w, /images/2017/04/docker-image-flow@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/04/docker-image-flow.png" alt="Docker Image flow" title="Docker Image flow"></picture></p><p>A Docker image is build using the <code>docker build</code> command. A <code>Dockerfile</code> defines the steps that are necessary to produce the image. To create the image that contains the factorial application, the following steps are needed:</p><ol><li>Update the operating system based on <code>amazonlinux:2016.09</code> with <code>yum update</code></li><li>Install Node.js 6.x</li><li>Create a directory for the app and copy the local app folder into the image</li><li>Set the workdir and expose the application’s port 3000</li><li>Set the command to start the app to <code>node index.js</code></li></ol><p>Let’s so how this looks in a Dockerfile.</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;Dockerfile</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/Dockerfile">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="string">FROM</span> <span class="string">amazonlinux:2016.09</span></span><br><span class="line"></span><br><span class="line"><span class="string">RUN</span> <span class="string">yum</span> <span class="string">-y</span> <span class="string">update</span></span><br><span class="line"></span><br><span class="line"><span class="string">RUN</span> <span class="string">curl</span> <span class="string">--silent</span> <span class="string">--location</span> <span class="string">https://rpm.nodesource.com/setup_6.x</span> <span class="string">|</span> <span class="string">bash</span> <span class="bullet">-</span></span><br><span class="line"><span class="string">RUN</span> <span class="string">yum</span> <span class="string">-y</span> <span class="string">install</span> <span class="string">nodejs</span></span><br><span class="line"></span><br><span class="line"><span class="string">RUN</span> <span class="string">mkdir</span> <span class="string">-p</span> <span class="string">/usr/src/app</span></span><br><span class="line"><span class="string">ADD</span> <span class="string">app</span> <span class="string">/usr/src/app</span></span><br><span class="line"></span><br><span class="line"><span class="string">WORKDIR</span> <span class="string">/usr/src/app</span></span><br><span class="line"></span><br><span class="line"><span class="string">EXPOSE</span> <span class="number">3000</span></span><br><span class="line"></span><br><span class="line"><span class="string">CMD</span> [ <span class="string">&quot;node&quot;</span>, <span class="string">&quot;index.js&quot;</span> ]</span><br></pre></td></tr></table></figure><p>If you run <code>docker build -f infrastructure/Dockerfile .</code> inside the project directory, you will create a Docker image locally. But this article is about automation, so you need to embed this step into the pipeline. Let’s see how this works next.</p><p>Copy the <code>deploy/pipeline.yml</code> file to <code>deploy/pipeline_ecs.yml</code> to get the starting point right. If you don’t have the <code>deploy/pipeline.yml</code> file you can download it from <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">https://github.com/widdix/aws-velocity</a>.</p><p>To integrate the docker build step into the pipeline, add the following resources to the <code>Resources</code> section of <code>deploy/pipeline_ecs.yml</code> to create a CodeBuild project to run docker with the above configuration. Docker also needs a bunch of IAM permissions which are also added.</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline_ecs.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline_ecs.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># Private repository that stores Docker images</span></span><br><span class="line"><span class="attr">ArtifactsRepository:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">DeletionPolicy:</span> <span class="string">Retain</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::ECR::Repository&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span> &#123;&#125;</span><br><span class="line"><span class="comment"># Docker needs a set of access rights to push images to the repository</span></span><br><span class="line"><span class="attr">ImageCodeBuildRole:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">      <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">      <span class="attr">Statement:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">        <span class="attr">Principal:</span></span><br><span class="line">          <span class="attr">Service:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;codebuild.amazonaws.com&#x27;</span></span><br><span class="line">        <span class="attr">Action:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    <span class="attr">Policies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">ServiceRole</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="comment">#[...]</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">ECR</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">&#x27;Allow&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;ecr:GetAuthorizationToken&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">ECRRepo</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">&#x27;Allow&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ecr:BatchCheckLayerAvailability&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ecr:CompleteLayerUpload&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ecr:InitiateLayerUpload&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ecr:PutImage&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ecr:UploadLayerPart&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:ecr:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:repository/$&#123;ArtifactsRepository&#125;&#x27;</span></span><br><span class="line"><span class="comment"># build the Docker image and publish the tag of the image as an artifact</span></span><br><span class="line"><span class="attr">ImageProject:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodeBuild::Project&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Artifacts:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">    <span class="attr">Environment:</span></span><br><span class="line">      <span class="attr">ComputeType:</span> <span class="string">&#x27;BUILD_GENERAL1_SMALL&#x27;</span></span><br><span class="line">      <span class="attr">Image:</span> <span class="string">&#x27;aws/codebuild/docker:1.12.1&#x27;</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">&#x27;LINUX_CONTAINER&#x27;</span></span><br><span class="line">    <span class="attr">Name:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-image&#x27;</span></span><br><span class="line">    <span class="attr">ServiceRole:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;ImageCodeBuildRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">Source:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">      <span class="attr">BuildSpec:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">        version: 0.1</span></span><br><span class="line"><span class="string">        phases:</span></span><br><span class="line"><span class="string">          build:</span></span><br><span class="line"><span class="string">            commands:</span></span><br><span class="line"><span class="string">            - &#x27;docker build -f infrastructure/Dockerfile -t $&#123;AWS::AccountId&#125;.dkr.ecr.$&#123;AWS::Region&#125;.amazonaws.com/$&#123;ArtifactsRepository&#125;:$&#123;!CODEBUILD_BUILD_ID#*:&#125; .&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;eval $(aws ecr get-login --no-include-email) &amp;&amp; docker push $&#123;AWS::AccountId&#125;.dkr.ecr.$&#123;AWS::Region&#125;.amazonaws.com/$&#123;ArtifactsRepository&#125;:$&#123;!CODEBUILD_BUILD_ID#*:&#125;&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;echo &quot;$&#123;AWS::AccountId&#125;.dkr.ecr.$&#123;AWS::Region&#125;.amazonaws.com/$&#123;ArtifactsRepository&#125;:$&#123;!CODEBUILD_BUILD_ID#*:&#125;&quot; &gt; infrastructure/image.txt&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;echo &quot;&#123;\&quot;image\&quot;: \&quot;$&#123;AWS::AccountId&#125;.dkr.ecr.$&#123;AWS::Region&#125;.amazonaws.com/$&#123;ArtifactsRepository&#125;:$&#123;!CODEBUILD_BUILD_ID#*:&#125;\&quot;&#125;&quot; &gt; infrastructure/image.json&#x27;</span></span><br><span class="line"><span class="string">        artifacts:</span></span><br><span class="line"><span class="string">          files:</span></span><br><span class="line"><span class="string">          - &#x27;infrastructure/image.json&#x27;</span></span><br><span class="line"><span class="string">          - &#x27;infrastructure/image.txt&#x27;</span></span><br><span class="line"><span class="string"></span>    <span class="attr">TimeoutInMinutes:</span> <span class="number">10</span></span><br></pre></td></tr></table></figure><p>You also need to make a small modification to the exiting <code>BuildSpec</code> in the <code>AppProject</code> resource to include the image files into the <code>App</code> artifact. This is a hack because CodeBuild does not support multiple input artifacts at the moment. Change the <code>artifacts</code> section to:</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline_ecs.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline_ecs.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">artifacts:</span></span><br><span class="line">  <span class="attr">files:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&#x27;app/**/*&#x27;</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&#x27;infrastructure/image.json&#x27;</span> <span class="comment"># this is a hack because we can not pass multiple Artifacts as an input to CodeBuild at the moment</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&#x27;infrastructure/image.sh&#x27;</span> <span class="comment"># this is a hack because we can not pass multiple Artifacts as an input to CodeBuild at the moment</span></span><br></pre></td></tr></table></figure><p>Now the CodeBuild project needs to be called in the pipeline, therefore change the <code>Pipeline</code> resource in the file <code>deploy/pipeline_ecs.yml</code> and add a new build action:</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline_ecs.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline_ecs.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">Pipeline:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodePipeline::Pipeline&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">BuildAndTestAcceptance</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">AcceptanceProject</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="attr">OutputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Acceptance</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br><span class="line">      <span class="comment"># NEW STUFF!</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">BuildImage</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">ImageProject</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">App</span></span><br><span class="line">        <span class="attr">OutputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Image</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">2</span></span><br></pre></td></tr></table></figure><p>Now, a new Docker image is automatically build and pushed to the ECR repository whenever the pipeline runs. The image will include the app and the latest security patches.</p><p>It’s time to deploy the app to the acceptance stage and too see if the app works.</p><h3 id="Acceptance-stage"><a href="#Acceptance-stage" class="headerlink" title="Acceptance stage"></a>Acceptance stage</h3><p>The acceptance stage consists of a CloudFormation stack based on <code>infrastructure/ecs.yml</code> and the execution of the acceptance tests. To create the CloudFormation stack, you first have to provide a few parameters. Create a file <code>infrastructure/ecs.json</code> with the following content:</p><figure class="highlight json"><figcaption><span>infrastructure&#x2F;ecs.json</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ecs.json">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Parameters&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;ParentVPCStack&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vpc-2azs&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;ParentClusterStack&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ecs-cluster&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;Image&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="attr">&quot;Fn::GetParam&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;Image&quot;</span><span class="punctuation">,</span> <span class="string">&quot;infrastructure/image.json&quot;</span><span class="punctuation">,</span> <span class="string">&quot;image&quot;</span><span class="punctuation">]</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;DesiredCount&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;MaxCapacity&quot;</span><span class="punctuation">:</span> <span class="string">&quot;4&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;MinCapacity&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;AdminEmail&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your@email.com&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Make sure to change the <code>ParentVPCStack</code> and the <code>ParentClusterStack</code> parameter in the <code>infrastructure/ecs.yml</code> accordingly to your stack names. Also change the value of the <code>AdminEmail</code> parameter to your email address. The other values can be stay as they are. Look at the <code>Image</code> parameter value. This is the way of getting a value out of a JSON artifact file in CodePipeline. The artifact is crated in the <code>BuildImage</code> action in the CodePipeline.</p><p>To run the acceptance tests, you also need another CodeBuild project, add the following resources to the <code>Resources</code> section of <code>deploy/pipeline_ecs.yml</code>:</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline_ecs.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline_ecs.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">RunAcceptanceCodeBuildRole:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">      <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">      <span class="attr">Statement:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">        <span class="attr">Principal:</span></span><br><span class="line">          <span class="attr">Service:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;codebuild.amazonaws.com&#x27;</span></span><br><span class="line">        <span class="attr">Action:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    <span class="attr">Policies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">ServiceRole</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">CloudWatchLogsPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogGroup&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogStream&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">CodeCommitPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;codecommit:GitPull&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">S3GetObjectPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;s3:GetObject&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;s3:GetObjectVersion&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">S3PutObjectPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">&#x27;Allow&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;s3:PutObject&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">CloudFormation</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">CloudFormation</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;cloudformation:DescribeStacks&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:cloudformation:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:stack/$&#123;AWS::StackName&#125;-acceptance/*&#x27;</span></span><br><span class="line"><span class="attr">RunAcceptanceProject:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodeBuild::Project&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Artifacts:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">    <span class="attr">Environment:</span></span><br><span class="line">      <span class="attr">ComputeType:</span> <span class="string">&#x27;BUILD_GENERAL1_SMALL&#x27;</span></span><br><span class="line">      <span class="attr">Image:</span> <span class="string">&#x27;aws/codebuild/nodejs:6.3.1&#x27;</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">&#x27;LINUX_CONTAINER&#x27;</span></span><br><span class="line">    <span class="attr">Name:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-run-acceptance&#x27;</span></span><br><span class="line">    <span class="attr">ServiceRole:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;RunAcceptanceCodeBuildRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">Source:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">      <span class="attr">BuildSpec:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">        version: 0.1</span></span><br><span class="line"><span class="string">        phases:</span></span><br><span class="line"><span class="string">          build: # execute acceptance tests against the acceptance stack</span></span><br><span class="line"><span class="string">            commands:</span></span><br><span class="line"><span class="string">            - &#x27;cd acceptance/ &amp;&amp; ENDPOINT=`aws cloudformation describe-stacks --stack-name $&#123;AWS::StackName&#125;-acceptance --query &quot;Stacks[0].Outputs[?OutputKey==&#x27;&#x27;URL&#x27;&#x27;].OutputValue&quot; --output text` ./node_modules/jasmine-node/bin/jasmine-node .&#x27;</span></span><br><span class="line"><span class="string"></span>    <span class="attr">TimeoutInMinutes:</span> <span class="number">10</span></span><br></pre></td></tr></table></figure><p>Now the CodeBuild project needs to be called in the pipeline, therefore change <code>Pipeline</code> resource in the file <code>deploy/pipeline_ecs.yml</code> to:</p><ol><li>deploy the CloudFormation stack suffixed with <code>-acceptance</code></li><li>run the acceptance tests</li></ol><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline_ecs.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline_ecs.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">Pipeline:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodePipeline::Pipeline&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">BuildImage</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">ImageProject</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">App</span></span><br><span class="line">        <span class="attr">OutputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Image</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">2</span></span><br><span class="line">    <span class="comment"># NEW STUFF!</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Acceptance</span></span><br><span class="line">      <span class="attr">Actions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Deploy</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Deploy</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CloudFormation</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ActionMode:</span> <span class="string">CREATE_UPDATE</span></span><br><span class="line">          <span class="attr">Capabilities:</span> <span class="string">CAPABILITY_IAM</span></span><br><span class="line">          <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CloudFormationRole.Arn&#x27;</span></span><br><span class="line">          <span class="attr">StackName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-acceptance&#x27;</span></span><br><span class="line">          <span class="attr">TemplatePath:</span> <span class="string">&#x27;Source::infrastructure/ecs.yml&#x27;</span></span><br><span class="line">          <span class="attr">TemplateConfiguration:</span> <span class="string">&#x27;Source::infrastructure/ecs.json&#x27;</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Image</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Test</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">RunAcceptanceProject</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Acceptance</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">2</span></span><br></pre></td></tr></table></figure><p>The acceptance stage is now ready.</p><h3 id="Production-stage"><a href="#Production-stage" class="headerlink" title="Production stage"></a>Production stage</h3><p>The production stage is pretty simple, just one CloudFormation stack. Change the <code>Pipeline</code> resource in the file <code>deploy/pipeline_ecs.yml</code> to add a new stage that looks familiar to the acceptance stage:</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline_ecs.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline_ecs.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">Pipeline:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodePipeline::Pipeline&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Test</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">RunAcceptanceProject</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Acceptance</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">2</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Production</span></span><br><span class="line">      <span class="attr">Actions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Deploy</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Deploy</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CloudFormation</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ActionMode:</span> <span class="string">CREATE_UPDATE</span></span><br><span class="line">          <span class="attr">Capabilities:</span> <span class="string">CAPABILITY_IAM</span></span><br><span class="line">          <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CloudFormationRole.Arn&#x27;</span></span><br><span class="line">          <span class="attr">StackName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-production&#x27;</span></span><br><span class="line">          <span class="attr">TemplatePath:</span> <span class="string">&#x27;Source::infrastructure/ecs.yml&#x27;</span></span><br><span class="line">          <span class="attr">TemplateConfiguration:</span> <span class="string">&#x27;Source::infrastructure/ecs.json&#x27;</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Image</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br></pre></td></tr></table></figure><p>Now the Docker image containing the application is deployed to production with confidence and without disturbing the users. Try it and run the pipeline!</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Let’s use my production-ready definition to summarize how each point is implemented:</p><ul><li>Highly available: The load balancer (which is HA) sits in front of a fleet of Docker containers managed by the ECS service for maximum availability. In the case of an unhealthy container, the ECS service will replace that container.</li><li>Scalable: If the CPU utilization gets over 70%, a Cloud Watch Alarm triggers a Scaling Policy to add new containers automatically. If the ECS cluster runs out of capacity, it is scaled as well.</li><li>Frictionless deployment: To deploy a new version of the app, a new Docker image is created. This image is then rolled out to the acceptance environment by updating the CloudFormation stack with the new <code>Image</code> parameter. The ECS Service performs a rolling update to avoid the application being down during deployment.</li><li>Secure: During Docker image creation, the latest patches are applied. You must ensure that the pipeline runs often enough to keep up with new patches. Besides that, Security Groups control network traffic to the EC2 instances running the containers. When following the bastion host approach, you get maximum security. The EC2 instance is only allowed to send logs to CloudWatch Logs by following the least privileges approach. The container itself has no additional IAM permissions but has access to the instance permissions <a href="http://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html" target="_blank" rel="noopener">by default</a> as well.</li><li>Operations: All logs are stored in CloudWatch Logs, important metrics are monitored, and alarms are defined.</li></ul><p>If you now have the impression that deploying and running an app on ECS is a bit easier compared to the plain EC2 approach you are right. In the next article, you will learn about yet another option with even fewer responsibilities.</p><blockquote><p>One more thing: You need to automate the patching of the EC2 instances as well. To do so, you could follow the approach that I demonstrated in the <a href="/aws-velocity-ec2-based-app-ci-cd-pipeline/">EC2 based app</a> and use Packer to keep the AMI up-to-date in an automated way.</p></blockquote><h2 id="Series"><a href="#Series" class="headerlink" title="Series"></a>Series</h2><p align="center"><img src="/images/2017/01/aws-velocity-cover.png" alt="AWS Velocity Cover" style="max-width: 349px"></p><ol><li><a href="/aws-velocity-set-the-assembly-line-up/">Set the assembly line up</a></li><li><a href="/aws-velocity-local-development-environment/">Local development environment</a></li><li><a href="/aws-velocity-ci-cd-pipeline-as-code/">CI&#x2F;CD Pipeline as Code</a></li><li><a href="/aws-velocity-running-your-application/">Running your application</a></li><li>EC2 based app<br>  a. <a href="/aws-velocity-ec2-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-ec2-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li>Containerized ECS based app<br>  a. <a href="/aws-velocity-containerized-ecs-based-app-infrastructure/">Infrastructure</a><br>  b. <strong>CI&#x2F;CD Pipeline</strong> (you are here)</li><li><a href="/aws-velocity-serverless-app/">Serverless app</a></li><li><a href="/aws-velocity-summary/">Summary</a></li></ol><p>You can find the source code on <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">GitHub</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Velocity Series: Containerized ECS based app infrastructure</title>
      <link>https://cloudonaut.io/aws-velocity-containerized-ecs-based-app-infrastructure/</link>
      <description>
        <![CDATA[<p><a href="https://aws.amazon.com/ecs/" target="_blank" rel="noopener">EC2 Container Service (ECS)</a> is a highly scalable, fast, containe]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/docker/">Docker</category>
      <category domain="https://cloudonaut.io/tag/velocity/">velocity</category>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <category domain="https://cloudonaut.io/tag/ecs/">ecs</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-velocity-containerized-ecs-based-app-infrastructure/</guid>
      <pubDate>Mon, 27 Mar 2017 16:05:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><a href="https://aws.amazon.com/ecs/" target="_blank" rel="noopener">EC2 Container Service (ECS)</a> is a highly scalable, fast, container management service that makes it easy to run, stop, and manage Docker containers on a cluster of Amazon EC2 instances. To run an application on ECS, you need the following components:</p><ul><li>Docker image</li><li>ECS cluster: EC2 instances running Docker and the ECS agent</li><li>ECS service: Managed Docker containers on the ECS cluster</li></ul><p>ECS provides the ability to run a production-ready application on EC2 with reduced responsibilities and increased deployment speed compared to the EC2 based approach. By production-ready, I mean:</p><ul><li>Highly available: no single point of failure</li><li>Scalable: increase or decrease the number of instances&#x2F;containers based on load</li><li>Frictionless deployment: deliver new versions of your application automatically without downtime</li><li>Secure: patching operating systems and libraries frequently, follow the least privilege principle in all areas</li><li>Operations: provide tools like logging, monitoring and alerting to recognize and debug problems</li></ul><p>The overall architecture will consist of a load balancer, forwarding requests to containers running on multiple EC2 instances, distributed among different availability zones (data centers).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/03/ecs-based-app@730w.webp 730w, /images/2017/03/ecs-based-app@730w2x.webp 1460w, /images/2017/03/ecs-based-app@610w.webp 610w, /images/2017/03/ecs-based-app@610w2x.webp 1220w, /images/2017/03/ecs-based-app@450w.webp 450w, /images/2017/03/ecs-based-app@450w2x.webp 900w, /images/2017/03/ecs-based-app@330w.webp 330w, /images/2017/03/ecs-based-app@330w2x.webp 660w, /images/2017/03/ecs-based-app@545w.webp 545w, /images/2017/03/ecs-based-app@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/03/ecs-based-app@730w.png 730w, /images/2017/03/ecs-based-app@730w2x.png 1460w, /images/2017/03/ecs-based-app@610w.png 610w, /images/2017/03/ecs-based-app@610w2x.png 1220w, /images/2017/03/ecs-based-app@450w.png 450w, /images/2017/03/ecs-based-app@450w2x.png 900w, /images/2017/03/ecs-based-app@330w.png 330w, /images/2017/03/ecs-based-app@330w2x.png 660w, /images/2017/03/ecs-based-app@545w.png 545w, /images/2017/03/ecs-based-app@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/03/ecs-based-app.png" alt="ECS based app architecture" title="ECS based app architecture"></picture></p><p><em>The diagram was created with <a href="https://bit.ly/2PpMc2M" target="_blank" rel="noopener">Cloudcraft - Visualize your cloud architecture like a pro</a>.</em></p><div class="well">  <div class="media">    <div class="media-left">      <a href="/aws-velocity-series/">        <img class="media-object raw" src="/images/2017/04/aws-velocity-cover-icon.png" alt="AWS Velocity Cover">      </a>    </div>    <div class="media-body">      <h4 class="media-heading">AWS Velocity Series</h4>      <p>Most of our clients use AWS to reduce time-to-market following an agile approach. But AWS is only one part of the solution. In this article series, I show you how we help our clients to improve velocity: the time from idea to production. <a href="/aws-velocity-series/">Discover all posts!</a></p>    </div>  </div></div><p>Let’s start with the needed infrastructure. The ECS cluster and the service.</p><h2 id="ECS-cluster"><a href="#ECS-cluster" class="headerlink" title="ECS cluster"></a>ECS cluster</h2><p>The ECS cluster is a fleet of EC2 instances with the ECS agent and Docker installed. The ECS cluster is responsible for scheduling the work (containers) to the EC2 instances.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/03/ecs-cluster@730w.webp 730w, /images/2017/03/ecs-cluster@730w2x.webp 1460w, /images/2017/03/ecs-cluster@610w.webp 610w, /images/2017/03/ecs-cluster@610w2x.webp 1220w, /images/2017/03/ecs-cluster@450w.webp 450w, /images/2017/03/ecs-cluster@450w2x.webp 900w, /images/2017/03/ecs-cluster@330w.webp 330w, /images/2017/03/ecs-cluster@330w2x.webp 660w, /images/2017/03/ecs-cluster@545w.webp 545w, /images/2017/03/ecs-cluster@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/03/ecs-cluster@730w.png 730w, /images/2017/03/ecs-cluster@730w2x.png 1460w, /images/2017/03/ecs-cluster@610w.png 610w, /images/2017/03/ecs-cluster@610w2x.png 1220w, /images/2017/03/ecs-cluster@450w.png 450w, /images/2017/03/ecs-cluster@450w2x.png 900w, /images/2017/03/ecs-cluster@330w.png 330w, /images/2017/03/ecs-cluster@330w2x.png 660w, /images/2017/03/ecs-cluster@545w.png 545w, /images/2017/03/ecs-cluster@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/03/ecs-cluster.png" alt="ECS cluster architecture" title="ECS cluster architecture"></picture></p><p><em>The diagram was created with <a href="https://bit.ly/2PpMc2M" target="_blank" rel="noopener">Cloudcraft - Visualize your cloud architecture like a pro</a>.</em></p><p>Your Docker containers will run on those EC2 instances. You don’t need to care about the ECS cluster too much. We provide you a free and production-ready <a href="https://github.com/widdix/aws-cf-templates/blob/master/ecs/README.md#ecs-cluster" target="_blank" rel="noopener">CloudFormation template</a>. Please setup the ECS cluster now if you want to setup the scenario. <strong>AWS charges will likely occur!</strong> The ECS cluster need to run in a VPC, so if you don’t have a VPC stack based of our Free Templates for AWS CloudFormation (<a href="https://github.com/widdix/aws-cf-templates/tree/master/vpc" target="_blank" rel="noopener">https://github.com/widdix/aws-cf-templates/tree/master/vpc</a>) create a VPC stack first.</p><p>This first step was easy. Now you learn to use the ECS cluster with ECS services.</p><h2 id="ECS-service"><a href="#ECS-service" class="headerlink" title="ECS service"></a>ECS service</h2><p>The ECS service is responsible for launching Docker containers in the cluster. The service also makes sure that failed containers are replaced, and it also takes care about performing rolling updates for you. You also need a load balancer to route traffic to the containers. The following ECS service template is based on free and production-ready <a href="https://github.com/widdix/aws-cf-templates/blob/master/ecs/README.md#using-a-dedicated-load-balancer-for-the-service" target="_blank" rel="noopener">CloudFormation template</a>.</p><h3 id="Load-balancer"><a href="#Load-balancer" class="headerlink" title="Load balancer"></a>Load balancer</h3><blockquote><p>You can follow step by step or get the full source code here: <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">https://github.com/widdix/aws-velocity</a></p></blockquote><p>Create a file <code>infrastructure/ecs.yml</code>. The first part of the file contains the load balancer. To fully describe an Application Load Balancer, you need:</p><ul><li>A Security Group that allows traffic on port 80</li><li>The lApplication Load Balancer itself</li><li>A Target Group, which is a group of EC2 instances. Each of them runs containers that can receive traffic from the load balancer</li><li>A Listener, which wires the load balancer together with the target group and defines the listening port</li></ul><p>Watch out for comments with more detailed information in the code.</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ecs.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ecs.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&#x27;ECS: service that runs on an ECS cluster based on ecs/cluster.yaml and uses a dedicated ALB, a cloudonaut.io template&#x27;</span></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="comment"># You can reuse a VPC for multiple applications. In this case, we use one of our Free Templates for AWS CloudFormation (https://github.com/widdix/aws-cf-templates/tree/master/vpc).</span></span><br><span class="line">  <span class="attr">ParentVPCStack:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;Stack name of parent VPC stack based on vpc/vpc-*azs.yaml template.&#x27;</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">  <span class="attr">ParentClusterStack:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;Stack name of parent Cluster stack based on ecs/cluster.yaml template.&#x27;</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="comment"># Allow traffic from the load balancer to the EC2 instances in the cluster. This is only necessary because we reuse the cluster template in the way it is!</span></span><br><span class="line">  <span class="attr">SecurityGroupInALB:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::SecurityGroupIngress&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">GroupId:</span></span><br><span class="line">        <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentClusterStack&#125;-SecurityGroup&#x27;</span></span><br><span class="line">      <span class="attr">IpProtocol:</span> <span class="string">tcp</span></span><br><span class="line">      <span class="attr">FromPort:</span> <span class="number">0</span></span><br><span class="line">      <span class="attr">ToPort:</span> <span class="number">65535</span></span><br><span class="line">      <span class="attr">SourceSecurityGroupId:</span> <span class="type">!Ref</span> <span class="string">ALBSecurityGroup</span></span><br><span class="line">  <span class="comment"># The load balancer accepts HTTP traffic. Therefore the firewall must allow incoming traffic on port 80.</span></span><br><span class="line">  <span class="attr">ALBSecurityGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::SecurityGroup&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">GroupDescription:</span> <span class="string">&#x27;ecs-cluster-alb&#x27;</span></span><br><span class="line">      <span class="attr">VpcId:</span></span><br><span class="line">        <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentVPCStack&#125;-VPC&#x27;</span></span><br><span class="line">      <span class="attr">SecurityGroupIngress:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">CidrIp:</span> <span class="string">&#x27;0.0.0.0/0&#x27;</span></span><br><span class="line">        <span class="attr">FromPort:</span> <span class="number">80</span></span><br><span class="line">        <span class="attr">ToPort:</span> <span class="number">80</span></span><br><span class="line">        <span class="attr">IpProtocol:</span> <span class="string">tcp</span></span><br><span class="line">  <span class="comment"># The load balancer needs to run in public subnets because our users should be able to access the app from the Internet.</span></span><br><span class="line">  <span class="attr">LoadBalancer:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::ElasticLoadBalancingV2::LoadBalancer&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Scheme:</span> <span class="string">&#x27;internet-facing&#x27;</span></span><br><span class="line">      <span class="attr">SecurityGroups:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">ALBSecurityGroup</span></span><br><span class="line">      <span class="attr">Subnets:</span> </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentVPCStack&#125;-SubnetAPublic&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentVPCStack&#125;-SubnetBPublic&#x27;</span></span><br><span class="line">  <span class="comment"># A target group groups a bunch of backend instances that receive traffic from the load balancer. the health check ensures that only working backends are used.</span></span><br><span class="line">  <span class="attr">DefaultTargetGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::ElasticLoadBalancingV2::TargetGroup&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">HealthCheckIntervalSeconds:</span> <span class="number">15</span></span><br><span class="line">      <span class="attr">HealthCheckPath:</span> <span class="string">&#x27;/1&#x27;</span></span><br><span class="line">      <span class="attr">HealthCheckProtocol:</span> <span class="string">HTTP</span></span><br><span class="line">      <span class="attr">HealthCheckTimeoutSeconds:</span> <span class="number">10</span></span><br><span class="line">      <span class="attr">HealthyThresholdCount:</span> <span class="number">2</span></span><br><span class="line">      <span class="attr">Matcher:</span></span><br><span class="line">        <span class="attr">HttpCode:</span> <span class="string">&#x27;200&#x27;</span></span><br><span class="line">      <span class="attr">Port:</span> <span class="number">80</span></span><br><span class="line">      <span class="attr">Protocol:</span> <span class="string">HTTP</span></span><br><span class="line">      <span class="attr">UnhealthyThresholdCount:</span> <span class="number">4</span></span><br><span class="line">      <span class="attr">VpcId:</span></span><br><span class="line">        <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentVPCStack&#125;-VPC&#x27;</span></span><br><span class="line">  <span class="comment"># The load balancer should listen on port 80 for HTTP traffic</span></span><br><span class="line">  <span class="attr">HttpListener:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::ElasticLoadBalancingV2::Listener&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">DefaultActions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">TargetGroupArn:</span> <span class="type">!Ref</span> <span class="string">DefaultTargetGroup</span></span><br><span class="line">        <span class="attr">Type:</span> <span class="string">forward</span></span><br><span class="line">      <span class="attr">LoadBalancerArn:</span> <span class="type">!Ref</span> <span class="string">LoadBalancer</span></span><br><span class="line">      <span class="attr">Port:</span> <span class="number">80</span></span><br><span class="line">      <span class="attr">Protocol:</span> <span class="string">HTTP</span></span><br><span class="line"><span class="comment"># A CloudFormation stack can return information that is needed by other stacks or scripts.</span></span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">DNSName:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;The DNS name for the ECS cluster/service load balancer.&#x27;</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;LoadBalancer.DNSName&#x27;</span></span><br><span class="line">    <span class="attr">Export:</span></span><br><span class="line">      <span class="attr">Name:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-DNSName&#x27;</span></span><br><span class="line">  <span class="attr">URL:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;URL to the ECS service.&#x27;</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Sub</span> <span class="string">&#x27;http://$&#123;LoadBalancer.DNSName&#125;&#x27;</span></span><br><span class="line">    <span class="attr">Export:</span></span><br><span class="line">      <span class="attr">Name:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-URL&#x27;</span></span><br></pre></td></tr></table></figure><p>But how do you get notified if something goes wrong? Let’s add a parameter to the <code>Parameters</code> section to make the receiver configurable:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ecs.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ecs.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">AdminEmail:</span></span><br><span class="line">  <span class="attr">Description:</span> <span class="string">&#x27;The email address of the admin who receives alerts.&#x27;</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">String</span></span><br></pre></td></tr></table></figure><p>Alerts are triggered by a CloudWatch Alarm which can send an alert to an SNS topic. You can subscribe to this topic via an email address to receive the alerts. Let’s create an SNS topic and two alarms in the <code>Resources</code> section:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ecs.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ecs.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># A SNS topic is used to send alerts via Email to the value of the AdminEmail parameter </span></span><br><span class="line">  <span class="attr">Alerts:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::SNS::Topic&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Subscription:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Endpoint:</span> <span class="type">!Ref</span> <span class="string">AdminEmail</span></span><br><span class="line">        <span class="attr">Protocol:</span> <span class="string">email</span></span><br><span class="line"><span class="comment"># This alarm is triggered, if the load balancer responds with 5XX status codes</span></span><br><span class="line">  <span class="attr">LoadBalancer5XXAlarm:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">Statistic:</span> <span class="string">Sum</span></span><br><span class="line">      <span class="attr">Threshold:</span> <span class="number">0</span></span><br><span class="line">      <span class="attr">AlarmDescription:</span> <span class="string">&#x27;Load balancer responds with 5XX status codes.&#x27;</span></span><br><span class="line">      <span class="attr">Period:</span> <span class="number">60</span></span><br><span class="line">      <span class="attr">AlarmActions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Alerts</span></span><br><span class="line">      <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/ApplicationELB&#x27;</span></span><br><span class="line">      <span class="attr">Dimensions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">LoadBalancer</span></span><br><span class="line">        <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;LoadBalancer.LoadBalancerFullName&#x27;</span></span><br><span class="line">      <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br><span class="line">      <span class="attr">MetricName:</span> <span class="string">HTTPCode_ELB_5XX_Count</span></span><br><span class="line">  <span class="comment"># This alarm is triggered, if the backend responds with 5XX status codes</span></span><br><span class="line">  <span class="attr">LoadBalancerTargetGroup5XXAlarm:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">Statistic:</span> <span class="string">Sum</span></span><br><span class="line">      <span class="attr">Threshold:</span> <span class="number">0</span></span><br><span class="line">      <span class="attr">AlarmDescription:</span> <span class="string">&#x27;Load balancer target responds with 5XX status codes.&#x27;</span></span><br><span class="line">      <span class="attr">Period:</span> <span class="number">60</span></span><br><span class="line">      <span class="attr">AlarmActions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Alerts</span></span><br><span class="line">      <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/ApplicationELB&#x27;</span></span><br><span class="line">      <span class="attr">Dimensions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">LoadBalancer</span></span><br><span class="line">        <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;LoadBalancer.LoadBalancerFullName&#x27;</span></span><br><span class="line">      <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br><span class="line">      <span class="attr">MetricName:</span> <span class="string">HTTPCode_Target_5XX_Count</span></span><br></pre></td></tr></table></figure><p>Let’s recap what you implemented: A load balancer with a firewall rule that allows traffic on port 80. In the case of 5XX status codes, you will receive an Email. But the load balancer alone is not enough. Now it’s time to add the ECS service.</p><h3 id="ECS-service-1"><a href="#ECS-service-1" class="headerlink" title="ECS service"></a>ECS service</h3><p>I already talked about the ECS service. It will take care of your containers. To be more precise, it will take care of your tasks that run in the ECS cluster. One task can contain one or multiple Docker containers. Three resources are needed:</p><ul><li>A <code>Task Definition</code> that describes the Docker containers (similar to a Docker Compose file, or a Kubernetes Deployment)</li><li>An <code>IAM Role</code> for your container, so you don’t need to pass in static credentials when you want to interact with AWS from within your container. If you don’t want to make AWS API calls from your container the role is not needed.</li><li>The <code>ECS Service</code>  itself.</li></ul><p>To make things parameterizable, you also need to add a few parameters to the <code>Parameters</code> section:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ecs.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ecs.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># Where does this Docker image comes from? It will be created in the pipeline!</span></span><br><span class="line"><span class="attr">Image:</span></span><br><span class="line">  <span class="attr">Description:</span> <span class="string">&#x27;The image to use for a container, which is passed directly to the Docker daemon. You can use images in the Docker Hub registry or specify other repositories (repository-url/image:tag).&#x27;</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line"><span class="attr">DesiredCount:</span></span><br><span class="line">  <span class="attr">Description:</span> <span class="string">&#x27;The number of simultaneous tasks, which you specify by using the TaskDefinition property, that you want to run on the cluster.&#x27;</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">Number</span></span><br><span class="line">  <span class="attr">Default:</span> <span class="number">2</span></span><br><span class="line">  <span class="attr">ConstraintDescription:</span> <span class="string">&#x27;Must be &gt;= 1&#x27;</span></span><br><span class="line">  <span class="attr">MinValue:</span> <span class="number">1</span></span><br><span class="line"><span class="attr">MaxCapacity:</span></span><br><span class="line">  <span class="attr">Description:</span> <span class="string">&#x27;The maximum number of simultaneous tasks, that you want to run on the cluster.&#x27;</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">Number</span></span><br><span class="line">  <span class="attr">Default:</span> <span class="number">4</span></span><br><span class="line">  <span class="attr">ConstraintDescription:</span> <span class="string">&#x27;Must be &gt;= 1&#x27;</span></span><br><span class="line">  <span class="attr">MinValue:</span> <span class="number">1</span></span><br><span class="line"><span class="attr">MinCapacity:</span></span><br><span class="line">  <span class="attr">Description:</span> <span class="string">&#x27;The minimum number of simultaneous tasks, that you want to run on the cluster.&#x27;</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">Number</span></span><br><span class="line">  <span class="attr">Default:</span> <span class="number">2</span></span><br><span class="line">  <span class="attr">ConstraintDescription:</span> <span class="string">&#x27;Must be &gt;= 1&#x27;</span></span><br><span class="line">  <span class="attr">MinValue:</span> <span class="number">1</span></span><br></pre></td></tr></table></figure><p>Now you can describe the resources in the CloudFormation template.</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ecs.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ecs.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">TaskDefinition:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::ECS::TaskDefinition&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span> </span><br><span class="line">    <span class="attr">Family:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line">    <span class="attr">NetworkMode:</span> <span class="string">bridge</span></span><br><span class="line">    <span class="attr">ContainerDefinitions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">main</span></span><br><span class="line">      <span class="attr">Image:</span> <span class="type">!Ref</span> <span class="string">Image</span> <span class="comment"># This is where the Docker image is configured</span></span><br><span class="line">      <span class="attr">Memory:</span> <span class="number">128</span></span><br><span class="line">      <span class="attr">PortMappings:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">ContainerPort:</span> <span class="number">3000</span> <span class="comment"># The image exposes the app on port 3000</span></span><br><span class="line">        <span class="attr">Protocol:</span> <span class="string">tcp</span></span><br><span class="line">      <span class="attr">Essential:</span> <span class="literal">true</span></span><br><span class="line">      <span class="attr">LogConfiguration:</span></span><br><span class="line">        <span class="attr">LogDriver:</span> <span class="string">awslogs</span></span><br><span class="line">        <span class="attr">Options:</span></span><br><span class="line">          <span class="attr">&#x27;awslogs-region&#x27;:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::Region&#x27;</span></span><br><span class="line">          <span class="attr">&#x27;awslogs-group&#x27;:</span></span><br><span class="line">            <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentClusterStack&#125;-LogGroup&#x27;</span></span><br><span class="line">          <span class="attr">&#x27;awslogs-stream-prefix&#x27;:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line"><span class="comment"># The role is using the managed policy AmazonEC2ContainerServiceRole</span></span><br><span class="line"><span class="attr">ServiceRole:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">ManagedPolicyArns:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole&#x27;</span></span><br><span class="line">    <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">      <span class="attr">Version:</span> <span class="string">&#x27;2008-10-17&#x27;</span></span><br><span class="line">      <span class="attr">Statement:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">        <span class="attr">Principal:</span></span><br><span class="line">          <span class="attr">Service:</span> <span class="string">&#x27;ecs.amazonaws.com&#x27;</span></span><br><span class="line">        <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line"><span class="attr">Service:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::ECS::Service&#x27;</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">HttpListener</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Cluster:</span></span><br><span class="line">      <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentClusterStack&#125;-Cluster&#x27;</span></span><br><span class="line">    <span class="attr">DeploymentConfiguration:</span> <span class="comment"># This is the configuration for the rolling update</span></span><br><span class="line">      <span class="attr">MaximumPercent:</span> <span class="number">200</span></span><br><span class="line">      <span class="attr">MinimumHealthyPercent:</span> <span class="number">50</span></span><br><span class="line">    <span class="attr">DesiredCount:</span> <span class="type">!Ref</span> <span class="string">DesiredCount</span></span><br><span class="line">    <span class="attr">LoadBalancers:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">ContainerName:</span> <span class="string">main</span></span><br><span class="line">      <span class="attr">ContainerPort:</span> <span class="number">3000</span> <span class="comment"># The image exposes the app on port 3000</span></span><br><span class="line">      <span class="attr">TargetGroupArn:</span> <span class="type">!Ref</span> <span class="string">DefaultTargetGroup</span></span><br><span class="line">    <span class="attr">Role:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;ServiceRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">TaskDefinition:</span> <span class="type">!Ref</span> <span class="string">TaskDefinition</span></span><br></pre></td></tr></table></figure><p>Let’s recap what you implemented: A Task definition to define the containers that are managed by the service, an IAM role that is accessible inside the containers, and the ECS service that used the task definition to launch tasks (a bunch of containers) in the cluster. Logs from the containers are already shipped to CLoudWatch Logs by the <code>awslogs</code> log driver and are visible in the Log Group that is part of the ECS cluster template.</p><p>Two things are missing:</p><ol><li>Scalability of containers</li><li>Alerting if containers have issues</li></ol><p>Let’s tackle those issues step by step.</p><h3 id="Auto-scaling-of-containers"><a href="#Auto-scaling-of-containers" class="headerlink" title="Auto scaling of containers"></a>Auto scaling of containers</h3><blockquote><p>If you auto scale the number of containers, your ECS cluster must be able to auto scale as well. if you use our free template on GitHub, the cluster will auto scale as well</p></blockquote><p>Auto Scaling works similar compared to the EC2 based approach. To scale based on the load you need to add:</p><ul><li>Scaling Policies to define what should happen if the system should scale up&#x2F;down</li><li>CloudWatch Alarms to trigger a Scaling Policy based on a metric such as CPU utilization</li><li>Additionally, you need a so-called <code>Scalable Target</code>. The <code>Scalable Target</code> can be an ECS service, but it could also be a Spot Fleet or an EMR Instance Group.</li></ul><p>Again, you have to add those resources to the <code>Resources</code> section of your template:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ecs.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ecs.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">ScalableTargetRole:</span> <span class="comment"># based on http://docs.aws.amazon.com/AmazonECS/latest/developerguide/autoscale_IAM_role.html</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">      <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">      <span class="attr">Statement:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">        <span class="attr">Principal:</span></span><br><span class="line">          <span class="attr">Service:</span> <span class="string">&#x27;application-autoscaling.amazonaws.com&#x27;</span></span><br><span class="line">        <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    <span class="attr">Path:</span> <span class="string">&#x27;/&#x27;</span></span><br><span class="line">    <span class="attr">Policies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">ecs</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ecs:DescribeServices&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ecs:UpdateService&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">cloudwatch</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;cloudwatch:DescribeAlarms&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line"><span class="attr">ScalableTarget:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::ApplicationAutoScaling::ScalableTarget&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">MaxCapacity:</span> <span class="type">!Ref</span> <span class="string">MaxCapacity</span></span><br><span class="line">    <span class="attr">MinCapacity:</span> <span class="type">!Ref</span> <span class="string">MinCapacity</span></span><br><span class="line">    <span class="attr">ResourceId:</span> <span class="type">!Sub</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;service/$&#123;Cluster&#125;/$&#123;Service&#125;&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Cluster:</span></span><br><span class="line">        <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentClusterStack&#125;-Cluster&#x27;</span></span><br><span class="line">      <span class="attr">Service:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Service.Name&#x27;</span></span><br><span class="line">    <span class="attr">RoleARN:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;ScalableTargetRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">ScalableDimension:</span> <span class="string">&#x27;ecs:service:DesiredCount&#x27;</span></span><br><span class="line">    <span class="attr">ServiceNamespace:</span> <span class="string">ecs</span></span><br><span class="line"><span class="attr">ScaleUpPolicy:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::ApplicationAutoScaling::ScalingPolicy&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">PolicyName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-scale-up&#x27;</span></span><br><span class="line">    <span class="attr">PolicyType:</span> <span class="string">StepScaling</span></span><br><span class="line">    <span class="attr">ScalingTargetId:</span> <span class="type">!Ref</span> <span class="string">ScalableTarget</span></span><br><span class="line">    <span class="attr">StepScalingPolicyConfiguration:</span> </span><br><span class="line">      <span class="attr">AdjustmentType:</span> <span class="string">PercentChangeInCapacity</span></span><br><span class="line">      <span class="attr">Cooldown:</span> <span class="number">300</span></span><br><span class="line">      <span class="attr">MinAdjustmentMagnitude:</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">StepAdjustments:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">MetricIntervalLowerBound:</span> <span class="number">0</span></span><br><span class="line">        <span class="attr">ScalingAdjustment:</span> <span class="number">25</span></span><br><span class="line"><span class="attr">ScaleDownPolicy:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::ApplicationAutoScaling::ScalingPolicy&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">PolicyName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-scale-down&#x27;</span></span><br><span class="line">    <span class="attr">PolicyType:</span> <span class="string">StepScaling</span></span><br><span class="line">    <span class="attr">ScalingTargetId:</span> <span class="type">!Ref</span> <span class="string">ScalableTarget</span></span><br><span class="line">    <span class="attr">StepScalingPolicyConfiguration:</span> </span><br><span class="line">      <span class="attr">AdjustmentType:</span> <span class="string">PercentChangeInCapacity</span></span><br><span class="line">      <span class="attr">Cooldown:</span> <span class="number">300</span></span><br><span class="line">      <span class="attr">MinAdjustmentMagnitude:</span> <span class="number">1</span></span><br><span class="line">      <span class="attr">StepAdjustments:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">MetricIntervalLowerBound:</span> <span class="number">0</span></span><br><span class="line">        <span class="attr">ScalingAdjustment:</span> <span class="number">-25</span></span><br><span class="line"><span class="attr">CPUUtilizationHighAlarm:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AlarmDescription:</span> <span class="string">&#x27;Service is running out of CPU&#x27;</span></span><br><span class="line">    <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/ECS&#x27;</span></span><br><span class="line">    <span class="attr">Dimensions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">ClusterName</span></span><br><span class="line">      <span class="attr">Value:</span></span><br><span class="line">        <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentClusterStack&#125;-Cluster&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">ServiceName</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Service.Name&#x27;</span></span><br><span class="line">    <span class="attr">MetricName:</span> <span class="string">CPUUtilization</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br><span class="line">    <span class="attr">Statistic:</span> <span class="string">Average</span></span><br><span class="line">    <span class="attr">Period:</span> <span class="number">60</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">70</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">ScaleUpPolicy</span></span><br><span class="line"><span class="attr">CPUUtilizationLowAlarm:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AlarmDescription:</span> <span class="string">&#x27;Service is wasting CPU&#x27;</span></span><br><span class="line">    <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/ECS&#x27;</span></span><br><span class="line">    <span class="attr">Dimensions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">ClusterName</span></span><br><span class="line">      <span class="attr">Value:</span></span><br><span class="line">        <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentClusterStack&#125;-Cluster&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">ServiceName</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Service.Name&#x27;</span></span><br><span class="line">    <span class="attr">MetricName:</span> <span class="string">CPUUtilization</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">LessThanThreshold</span></span><br><span class="line">    <span class="attr">Statistic:</span> <span class="string">Average</span></span><br><span class="line">    <span class="attr">Period:</span> <span class="number">60</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">30</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">ScaleDownPolicy</span></span><br></pre></td></tr></table></figure><p>The number of tasks is now increased if the CPU utilization of the service goes above 70%, while the number of tasks is decreased if the CPU utilization falls below 30%.</p><h3 id="Monitoring"><a href="#Monitoring" class="headerlink" title="Monitoring"></a>Monitoring</h3><p>Last but not least, you have to add CloudWatch Alarms to get alerted if something is wrong with your service. In the <code>Resources</code> section:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ecs.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ecs.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># Sends an alert if the average CPU load of the past 5 minutes is higher than 85%</span></span><br><span class="line"><span class="attr">CPUTooHighAlarm:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AlarmDescription:</span> <span class="string">&#x27;Service is running out of CPU&#x27;</span></span><br><span class="line">    <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/ECS&#x27;</span></span><br><span class="line">    <span class="attr">Dimensions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">ClusterName</span></span><br><span class="line">      <span class="attr">Value:</span></span><br><span class="line">        <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentClusterStack&#125;-Cluster&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">ServiceName</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;Service.Name&#x27;</span></span><br><span class="line">    <span class="attr">MetricName:</span> <span class="string">CPUUtilization</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br><span class="line">    <span class="attr">Statistic:</span> <span class="string">Average</span></span><br><span class="line">    <span class="attr">Period:</span> <span class="number">300</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">85</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Alerts</span></span><br></pre></td></tr></table></figure><p>The infrastructure is ready now. Read the next part of the series to learn how to setup the <a href="/aws-velocity-containerized-ecs-based-app-ci-cd-pipeline/">CI&#x2F;CD pipeline to deploy the ECS based app</a>.</p><h2 id="Series"><a href="#Series" class="headerlink" title="Series"></a>Series</h2><p align="center"><img src="/images/2017/01/aws-velocity-cover.png" alt="AWS Velocity Cover" style="max-width: 349px"></p><ol><li><a href="/aws-velocity-set-the-assembly-line-up/">Set the assembly line up</a></li><li><a href="/aws-velocity-local-development-environment/">Local development environment</a></li><li><a href="/aws-velocity-ci-cd-pipeline-as-code/">CI&#x2F;CD Pipeline as Code</a></li><li><a href="/aws-velocity-running-your-application/">Running your application</a></li><li>EC2 based app<br>  a. <a href="/aws-velocity-ec2-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-ec2-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li>Containerized ECS based app<br>  a. <strong>Infrastructure</strong> (you are here)<br>  b. <a href="/aws-velocity-containerized-ecs-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li><a href="/aws-velocity-serverless-app/">Serverless app</a></li><li><a href="/aws-velocity-summary/">Summary</a></li></ol><p>You can find the source code on <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">GitHub</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Painlessly create or update a CloudFormation stack in an idempotent way</title>
      <link>https://cloudonaut.io/painlessly-create-or-update-cloudformation-stack-idempotent/</link>
      <description>
        <![CDATA[<p>When integrating CloudFormation into your CI&#x2F;CD pipeline you are faced with the challenge of creating a CloudFormation stack on the]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <guid isPermaLink="true">https://cloudonaut.io/painlessly-create-or-update-cloudformation-stack-idempotent/</guid>
      <pubDate>Thu, 16 Mar 2017 18:05:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>When integrating CloudFormation into your CI&#x2F;CD pipeline you are faced with the challenge of creating a CloudFormation stack on the first run of the pipeline, while you need to update the stack for all following pipeline runs. If you use the AWS CLI this is painful.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/03/pain@730w.webp 730w, /images/2017/03/pain@730w2x.webp 1460w, /images/2017/03/pain@610w.webp 610w, /images/2017/03/pain@610w2x.webp 1220w, /images/2017/03/pain@450w.webp 450w, /images/2017/03/pain@450w2x.webp 900w, /images/2017/03/pain@330w.webp 330w, /images/2017/03/pain@330w2x.webp 660w, /images/2017/03/pain@545w.webp 545w, /images/2017/03/pain@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/03/pain@730w.jpg 730w, /images/2017/03/pain@730w2x.jpg 1460w, /images/2017/03/pain@610w.jpg 610w, /images/2017/03/pain@610w2x.jpg 1220w, /images/2017/03/pain@450w.jpg 450w, /images/2017/03/pain@450w2x.jpg 900w, /images/2017/03/pain@330w.jpg 330w, /images/2017/03/pain@330w2x.jpg 660w, /images/2017/03/pain@545w.jpg 545w, /images/2017/03/pain@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/03/pain.jpg" alt="Pain" title="Pain"></picture></p><p>You may also have your code and template in the same repository. Therefore code changes without the template. But the AWS CLI threats an update without changes to a stack as an error which is not the behavior that you need in your pipeline.</p><p><a href="https://github.com/widdix/cfn-create-or-update" target="_blank" rel="noopener">cfn-create-or-update</a> can <a href="http://docs.aws.amazon.com/cli/latest/reference/cloudformation/create-stack.html" target="_blank" rel="noopener">create</a> or <a href="http://docs.aws.amazon.com/cli/latest/reference/cloudformation/update-stack.html" target="_blank" rel="noopener">update</a> a CloudFormation stack. If no updates are to be performed, no error is thrown. <a href="https://github.com/widdix/cfn-create-or-update" target="_blank" rel="noopener">cfn-create-or-update</a> behaves exactly as the AWS CLI regarding input values, output will be different.</p><h2 id="Installation"><a href="#Installation" class="headerlink" title="Installation"></a>Installation</h2><p>To install the CLI tool <code>cfn-create-or-update</code>, run:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm install -g cfn-create-or-update</span><br></pre></td></tr></table></figure><p>To create or update a stack, run:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">cfn-create-or-update --stack-name <span class="built_in">test</span> --template-body file://template.yml</span><br></pre></td></tr></table></figure><p>The first time you run this command, a stack will be created. The second time an update will be performed but only if the template has changes.</p><h2 id="CLI-parameters"><a href="#CLI-parameters" class="headerlink" title="CLI parameters"></a>CLI parameters</h2><p><a href="https://github.com/widdix/cfn-create-or-update" target="_blank" rel="noopener">cfn-create-or-update</a> behaves exactly as the AWS CLI regarding input values. Supported parameters (as documented in the AWS CLI <a href="http://docs.aws.amazon.com/cli/latest/reference/cloudformation/create-stack.html" target="_blank" rel="noopener">create-stack</a> or <a href="http://docs.aws.amazon.com/cli/latest/reference/cloudformation/update-stack.html" target="_blank" rel="noopener">update-stack</a>):</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">cfn-create-or-update</span><br><span class="line">--stack-name</span><br><span class="line">--template-body</span><br><span class="line">--template-url</span><br><span class="line">--parameters</span><br><span class="line">--capabilities </span><br><span class="line">--resource-types</span><br><span class="line">--role-arn</span><br><span class="line">--stack-policy-body</span><br><span class="line">--stack-policy-url</span><br><span class="line">--notification-arns</span><br><span class="line">--tags</span><br></pre></td></tr></table></figure><p>Global parameters (as documented in the <a href="http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#general-options" target="_blank" rel="noopener">AWS CLI</a></p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">--profile</span><br><span class="line">--region</span><br></pre></td></tr></table></figure><p>Only used during create, otherwise ignored (as documented in the AWS CLI <a href="http://docs.aws.amazon.com/cli/latest/reference/cloudformation/create-stack.html" target="_blank" rel="noopener">create-stack</a>):</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">--disable-rollback | --no-disable-rollback</span><br><span class="line">--timeout-in-minutes</span><br><span class="line">--on-failure</span><br></pre></td></tr></table></figure><p>Only used during update, otherwise ignored (as documented in the AWS CLI <a href="http://docs.aws.amazon.com/cli/latest/reference/cloudformation/update-stack.html" target="_blank" rel="noopener">update-stack</a>):</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">--use-previous-template | --no-use-previous-template]</span><br><span class="line">--stack-policy-during-update-body</span><br><span class="line">--stack-policy-during-update-url</span><br></pre></td></tr></table></figure><p>Additional parameter, to wait for create complete &#x2F; update complete.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">---<span class="built_in">wait</span></span><br></pre></td></tr></table></figure><p><a href="https://github.com/widdix/cfn-create-or-update" target="_blank" rel="noopener">cfn-create-or-update</a> is available on <a href="https://github.com/widdix/cfn-create-or-update" target="_blank" rel="noopener">GitHub</a> for free (MIT license).</p><h2 id="Other-solutions-to-this-problem"><a href="#Other-solutions-to-this-problem" class="headerlink" title="Other solutions to this problem:"></a>Other solutions to this problem:</h2><ul><li>Use <a href="/tag/codepipeline/">AWS CodePipeline</a></li><li><a href="https://ruempler.eu/2017/03/17/ansible-cloudformation-wrapper-one-liner/" target="_blank" rel="noopener">Idempotent CloudFormation stack creation&#x2F;update one-liner with Ansible</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Velocity Series: EC2 based app CI/CD pipeline</title>
      <link>https://cloudonaut.io/aws-velocity-ec2-based-app-ci-cd-pipeline/</link>
      <description>
        <![CDATA[<p>In the <a href="/aws-velocity-ec2-based-app-infrastructure/">previous article</a>, you learned how to use CloudFormation to describe a pr]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/continuous-delivery/">Continuous Delivery</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/velocity/">velocity</category>
      <category domain="https://cloudonaut.io/tag/codebuild/">codebuild</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-velocity-ec2-based-app-ci-cd-pipeline/</guid>
      <pubDate>Mon, 06 Mar 2017 15:05:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>In the <a href="/aws-velocity-ec2-based-app-infrastructure/">previous article</a>, you learned how to use CloudFormation to describe a production-ready infrastructure for an EC2 based app. In this article you will learn to:</p><ul><li>Automate the creation of an AMI that contains the app with Packer</li><li>Deploy a CloudFormation stack based <code>infrastructure/ec2.yml</code> with AWS CodePipeline</li><li>Run the Acceptance tests on AWS CodeBuild against the infrastructure created in the previous step</li><li>Deploy another CloudFormation stack for the production environment</li></ul><blockquote><p>You can follow step by step or get the full source code here: <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">https://github.com/widdix/aws-velocity</a></p></blockquote><p>The pipeline is based on the <a href="/aws-velocity-ci-cd-pipeline-as-code/">CI&#x2F;CD Pipeline as Code</a> part of this series.</p><div class="well">  <div class="media">    <div class="media-left">      <a href="/aws-velocity-series/">        <img class="media-object raw" src="/images/2017/04/aws-velocity-cover-icon.png" alt="AWS Velocity Cover">      </a>    </div>    <div class="media-body">      <h4 class="media-heading">AWS Velocity Series</h4>      <p>Most of our clients use AWS to reduce time-to-market following an agile approach. But AWS is only one part of the solution. In this article series, I show you how we help our clients to improve velocity: the time from idea to production. <a href="/aws-velocity-series/">Discover all posts!</a></p>    </div>  </div></div><p>Additionally, the EC2 based app pipeline contains:</p><ul><li>add a BuildAMI action to the Build stage. </li><li>add an Acceptance stage</li><li>add a Production stage</li></ul><p>Copy the <code>deploy/pipeline.yml</code> file to <code>deploy/pipeline_ec2.yml</code> to get the starting point right. If you don’t have the <code>deploy/pipeline.yml</code> file you can download it from <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">https://github.com/widdix/aws-velocity</a>. </p><h3 id="BuildAMI-Action"><a href="#BuildAMI-Action" class="headerlink" title="BuildAMI Action"></a>BuildAMI Action</h3><p>EC2 instances start from an image (AMI) that contains the operating system including all the files that are needed. You can create your own AMI as well. Usually, you take one of the available AMIs like the Amazon Linux, make your modifications, and then create a new image from that. This whole procedure can also be automated with a tool called <a href="https://www.packer.io/" target="_blank" rel="noopener">Packer</a>. You will now see how you can run Packer in CodeBuild to create a new AMI that contains the app.</p><p>Packer itself needs configuration files. Create a file <code>infrastructure/packer.json</code> with the following content that build a new AMI based on <code>ami-c51e3eb6</code> (Amazon Linux) and a Bash script that you will create later:</p><figure class="highlight autohotkey"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;variables&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;ami_name&quot;</span>: <span class="string">&quot;&#123;&#123;env `CODEBUILD_BUILD_ID`&#125;&#125;&quot;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="string">&quot;builders&quot;</span>: [&#123;</span><br><span class="line">    <span class="string">&quot;type&quot;</span>: <span class="string">&quot;amazon-ebs&quot;</span>,</span><br><span class="line">    <span class="string">&quot;region&quot;</span>: <span class="string">&quot;eu-west-1&quot;</span>,</span><br><span class="line">    <span class="string">&quot;source_ami&quot;</span>: <span class="string">&quot;ami-c51e3eb6&quot;</span>,</span><br><span class="line">    <span class="string">&quot;instance_type&quot;</span>: <span class="string">&quot;t2.micro&quot;</span>,</span><br><span class="line">    <span class="string">&quot;ssh_username&quot;</span>: <span class="string">&quot;ec2-user&quot;</span>,</span><br><span class="line">    <span class="string">&quot;ami_name&quot;</span>: <span class="string">&quot;&#123;&#123;user `ami_name` | clean_ami_name&#125;&#125;&quot;</span>,</span><br><span class="line">    <span class="string">&quot;ami_regions&quot;</span>: [<span class="string">&quot;REGION&quot;</span>]</span><br><span class="line">  &#125;],</span><br><span class="line">  <span class="string">&quot;provisioners&quot;</span>: [&#123;</span><br><span class="line">    <span class="string">&quot;type&quot;</span>: <span class="string">&quot;file&quot;</span>,</span><br><span class="line">    <span class="string">&quot;source&quot;</span>: <span class="string">&quot;app&quot;</span>,</span><br><span class="line">    <span class="string">&quot;destination&quot;</span>: <span class="string">&quot;/tmp&quot;</span></span><br><span class="line">  &#125;, &#123;</span><br><span class="line">    <span class="string">&quot;type&quot;</span>: <span class="string">&quot;shell&quot;</span>,</span><br><span class="line">    <span class="string">&quot;script&quot;</span>: <span class="string">&quot;infrastructure/packer.sh&quot;</span></span><br><span class="line">  &#125;]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Packer is configured to run a Bash script to provision the AMI and to upload the <code>app</code> folder. Create a file <code>infrastructure/packer.sh</code> with the following content to:</p><ul><li>install the latest patches</li><li>install Node.js 6.x</li><li>install the CloudWatch Logs agent</li><li>install <code>forever</code>, a tool to run Node.js script in the background</li></ul><figure class="highlight bash"><figcaption><span>infrastructure&#x2F;packer.sh</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/packer.sh">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash -ex</span></span><br><span class="line"><span class="comment"># this script runs as ec2-user</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> yum -y update</span><br><span class="line">curl --silent --location https://rpm.nodesource.com/setup_6.x | <span class="built_in">sudo</span> bash -</span><br><span class="line"><span class="built_in">sudo</span> yum -y install nodejs awslogs</span><br><span class="line"><span class="built_in">sudo</span> npm install -g forever@0.15.3</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">mv</span> /tmp/app /opt</span><br></pre></td></tr></table></figure><p>The script also moves the application files to the right place.</p><p>To integrate Packer into the pipeline, add the following resources to the <code>Resources</code> section of <code>deploy/pipeline_ec2.yml</code> to create a CodeBuild project to run Packer with the above configuration. Packer also needs a bunch of IAM permissions which are also added.</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline_ec2.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline_ec2.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># Packer needs a set of access rights as defined in https://www.packer.io/docs/builders/amazon.html</span></span><br><span class="line"><span class="attr">AMICodeBuildRole:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">      <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">      <span class="attr">Statement:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">        <span class="attr">Principal:</span></span><br><span class="line">          <span class="attr">Service:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;codebuild.amazonaws.com&#x27;</span></span><br><span class="line">        <span class="attr">Action:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    <span class="attr">Policies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">ServiceRole</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">CloudWatchLogsPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogGroup&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogStream&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">CodeCommitPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;codecommit:GitPull&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">S3GetObjectPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;s3:GetObject&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;s3:GetObjectVersion&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">S3PutObjectPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">&#x27;Allow&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;s3:PutObject&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">EC2</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">CloudFormation</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:AttachVolume&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:AuthorizeSecurityGroupIngress&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:CopyImage&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:CreateImage&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:CreateKeypair&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:CreateSecurityGroup&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:CreateSnapshot&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:CreateTags&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:CreateVolume&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DeleteKeypair&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DeleteSecurityGroup&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DeleteSnapshot&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DeleteVolume&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DeregisterImage&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeImageAttribute&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeImages&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeInstances&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeRegions&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeSecurityGroups&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeSnapshots&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeSubnets&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeTags&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeVolumes&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DetachVolume&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:GetPasswordData&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:ModifyImageAttribute&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:ModifyInstanceAttribute&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:ModifySnapshotAttribute&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:RegisterImage&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:RunInstances&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:StopInstances&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:TerminateInstances&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line"><span class="comment"># This IAM User is only temporarily necessary until https://github.com/mitchellh/packer/pull/4613 is fixed!</span></span><br><span class="line"><span class="attr">AMICodeBuildUser:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::User&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Policies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">Packer</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">EC2</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:AttachVolume&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:AuthorizeSecurityGroupIngress&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:CopyImage&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:CreateImage&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:CreateKeypair&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:CreateSecurityGroup&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:CreateSnapshot&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:CreateTags&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:CreateVolume&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DeleteKeypair&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DeleteSecurityGroup&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DeleteSnapshot&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DeleteVolume&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DeregisterImage&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeImageAttribute&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeImages&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeInstances&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeRegions&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeSecurityGroups&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeSnapshots&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeSubnets&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeTags&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DescribeVolumes&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:DetachVolume&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:GetPasswordData&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:ModifyImageAttribute&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:ModifyInstanceAttribute&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:ModifySnapshotAttribute&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:RegisterImage&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:RunInstances&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:StopInstances&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;ec2:TerminateInstances&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line"><span class="comment"># This IAM Access Key is only temporarily necessary until https://github.com/mitchellh/packer/pull/4613 is fixed!</span></span><br><span class="line"><span class="attr">AMICodeBuildUserAccessKey:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::AccessKey&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">UserName:</span> <span class="type">!Ref</span> <span class="string">AMICodeBuildUser</span></span><br><span class="line"><span class="attr">AMIProject:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodeBuild::Project&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Artifacts:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">    <span class="attr">Environment:</span></span><br><span class="line">      <span class="attr">ComputeType:</span> <span class="string">&#x27;BUILD_GENERAL1_SMALL&#x27;</span></span><br><span class="line">      <span class="attr">Image:</span> <span class="string">&#x27;amazonlinux:2016.09&#x27;</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">&#x27;LINUX_CONTAINER&#x27;</span></span><br><span class="line">      <span class="attr">EnvironmentVariables:</span> <span class="comment"># pass in the AWS credentials as environment variables is only temporarily necessary until https://github.com/mitchellh/packer/pull/4613 is fixed!</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">&#x27;AWS_ACCESS_KEY_ID&#x27;</span></span><br><span class="line">        <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">AMICodeBuildUserAccessKey</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">&#x27;AWS_SECRET_ACCESS_KEY&#x27;</span></span><br><span class="line">        <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;AMICodeBuildUserAccessKey.SecretAccessKey&#x27;</span></span><br><span class="line">    <span class="attr">Name:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-ami&#x27;</span></span><br><span class="line">    <span class="attr">ServiceRole:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;AMICodeBuildRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">Source:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">      <span class="attr">BuildSpec:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">        version: 0.1</span></span><br><span class="line"><span class="string">        phases:</span></span><br><span class="line"><span class="string">          install: # install Packer</span></span><br><span class="line"><span class="string">            commands:</span></span><br><span class="line"><span class="string">            - &#x27;yum -y install unzip&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;curl -s -m 60 -o /opt/packer.zip https://releases.hashicorp.com/packer/0.12.3/packer_0.12.3_linux_amd64.zip&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;unzip /opt/packer.zip -d /opt&#x27;</span></span><br><span class="line"><span class="string">          pre_build: # replace the REGION placeholder with the stack&#x27;s region</span></span><br><span class="line"><span class="string">            commands:</span></span><br><span class="line"><span class="string">            - &#x27;sed -i &quot;s:REGION:$&#123;AWS::Region&#125;:g&quot; infrastructure/packer.json&#x27;</span></span><br><span class="line"><span class="string">          build:</span></span><br><span class="line"><span class="string">            commands:</span></span><br><span class="line"><span class="string">            # run packer</span></span><br><span class="line"><span class="string">            - &#x27;/opt/packer -machine-readable build infrastructure/packer.json | tee infrastructure/packer.txt&#x27;</span></span><br><span class="line"><span class="string">            # extract the AMI id into a JSON file</span></span><br><span class="line"><span class="string">            - &#x27;echo &quot;&#123;&quot; &gt; infrastructure/ami.json&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;cat infrastructure/packer.txt | grep &#x27;&#x27;,amazon-ebs,artifact,0,id,&#x27;&#x27; | awk -F&#x27;&#x27;,&#x27;&#x27; &#x27;&#x27;&#123;print $6&#125;&#x27;&#x27; | sed &#x27;&#x27;s/%!(PACKER_COMMA)/\&#x27;&#x27;$&#x27;&#x27;\n/g&#x27;&#x27; | awk -F&#x27;&#x27;:&#x27;&#x27; &#x27;&#x27;&#123;print &quot;\&quot;image\&quot;: \&quot;&quot;$2&quot;\&quot;,&quot;&#125;&#x27;&#x27; | sed &#x27;&#x27;$ s/.$//&#x27;&#x27; &gt;&gt; infrastructure/ami.json&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;echo &quot;&#125;&quot; &gt;&gt; infrastructure/ami.json&#x27;</span></span><br><span class="line"><span class="string">        artifacts:</span></span><br><span class="line"><span class="string">          files:</span></span><br><span class="line"><span class="string">          - &#x27;infrastructure/packer.txt&#x27;</span></span><br><span class="line"><span class="string">          - &#x27;infrastructure/ami.json&#x27;</span></span><br><span class="line"><span class="string"></span>    <span class="attr">TimeoutInMinutes:</span> <span class="number">10</span></span><br></pre></td></tr></table></figure><p>You also need to make a small modification to the exiting <code>BuildSpec</code> in the <code>AppProject</code> resource to include the packer files into the <code>App</code> artifact. This is a hack because CodeBuild does not support multiple input artifacts at the moment. Change the <code>artifacts</code> section to:</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline_ec2.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline_ec2.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">artifacts:</span></span><br><span class="line">  <span class="attr">files:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&#x27;app/**/*&#x27;</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&#x27;infrastructure/packer.json&#x27;</span> <span class="comment"># this is a hack because we can not pass multiple Arifacts as an input to CodeBuild at the moment</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&#x27;infrastructure/packer.sh&#x27;</span> <span class="comment"># this is a hack because we can not pass multiple Arifacts as an input to CodeBuild at the moment</span></span><br></pre></td></tr></table></figure><p>Now the CodeBuild project needs to be called in the pipeline, therefore change the <code>Pipeline</code> resource in the file <code>deploy/pipeline_ec2.yml</code> and add a new build action:</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline_ec2.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline_ec2.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">Pipeline:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodePipeline::Pipeline&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">BuildAndTestAcceptance</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">AcceptanceProject</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="attr">OutputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Acceptance</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br><span class="line">      <span class="comment"># NEW STUFF!</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">BuildAMI</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">AMIProject</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">App</span></span><br><span class="line">        <span class="attr">OutputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">AMI</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">2</span></span><br></pre></td></tr></table></figure><p>Now, a new AMI is automatically created whenever the pipeline runs. The AMI will include the app and the latest patches.</p><p>It’s time to deploy the app to the acceptance stage and too see if the app works.</p><h3 id="Acceptance-stage"><a href="#Acceptance-stage" class="headerlink" title="Acceptance stage"></a>Acceptance stage</h3><p>The acceptance stage consists of a CloudFormation stack based on <code>infrastructure/ec2.yml</code> and the execution of the acceptance tests. To create the CloudFormation stack, you first have to provide a few parameters. Create a file <code>infrastructure/ec2.json</code> with the following content:</p><figure class="highlight json"><figcaption><span>infrastructure&#x2F;ec2.json</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ec2.json">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Parameters&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;ImageId&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="attr">&quot;Fn::GetParam&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;AMI&quot;</span><span class="punctuation">,</span> <span class="string">&quot;infrastructure/ami.json&quot;</span><span class="punctuation">,</span> <span class="string">&quot;image&quot;</span><span class="punctuation">]</span><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;ParentVPCStack&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vpc-2azs&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;ParentSSHBastionStack&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;KeyName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;AdminEmail&quot;</span><span class="punctuation">:</span> <span class="string">&quot;your@email.com&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>If you don’t have a VPC stack based of our Free Templates for AWS CloudFormation (<a href="https://github.com/widdix/aws-cf-templates/tree/master/vpc" target="_blank" rel="noopener">https://github.com/widdix/aws-cf-templates/tree/master/vpc</a>) create a VPC stack first. Make sure to change the <code>ParentVPCStack</code>  parameter in the <code>infrastructure/ec2.yml</code> accordingly. Also change the value of the <code>AdminEmail</code> parameter. The other values can be stay as they are. Look at the <code>ImageId</code> parameter value. This is the way of getting a value out of a JSON artifact file in CoePipeline.</p><p>To run the acceptance tests, you also need another CodeBuild project, add the following resources to the <code>Resources</code> section of <code>deploy/pipeline_ec2.yml</code>:</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline_ec2.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline_ec2.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">ExtendedCodeBuildRole:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">      <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">      <span class="attr">Statement:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">        <span class="attr">Principal:</span></span><br><span class="line">          <span class="attr">Service:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;codebuild.amazonaws.com&#x27;</span></span><br><span class="line">        <span class="attr">Action:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    <span class="attr">Policies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">ServiceRole</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">CloudWatchLogsPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogGroup&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogStream&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">CodeCommitPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;codecommit:GitPull&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">S3GetObjectPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;s3:GetObject&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;s3:GetObjectVersion&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">S3PutObjectPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">&#x27;Allow&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;s3:PutObject&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">CloudFormation</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">CloudFormation</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;cloudformation:DescribeStacks&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:cloudformation:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:stack/$&#123;AWS::StackName&#125;-acceptance/*&#x27;</span></span><br><span class="line"><span class="attr">RunAcceptanceProject:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodeBuild::Project&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Artifacts:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">    <span class="attr">Environment:</span></span><br><span class="line">      <span class="attr">ComputeType:</span> <span class="string">&#x27;BUILD_GENERAL1_SMALL&#x27;</span></span><br><span class="line">      <span class="attr">Image:</span> <span class="string">&#x27;aws/codebuild/nodejs:6.3.1&#x27;</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">&#x27;LINUX_CONTAINER&#x27;</span></span><br><span class="line">    <span class="attr">Name:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-run-acceptance&#x27;</span></span><br><span class="line">    <span class="attr">ServiceRole:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;ExtendedCodeBuildRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">Source:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">      <span class="attr">BuildSpec:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">        version: 0.1</span></span><br><span class="line"><span class="string">        phases:</span></span><br><span class="line"><span class="string">          build: # execute acceptance tests against the acceptance stack</span></span><br><span class="line"><span class="string">            commands:</span></span><br><span class="line"><span class="string">            - &#x27;cd acceptance/ &amp;&amp; ENDPOINT=`aws cloudformation describe-stacks --stack-name $&#123;AWS::StackName&#125;-acceptance --query &quot;Stacks[0].Outputs[?OutputKey==&#x27;&#x27;URL&#x27;&#x27;].OutputValue&quot; --output text` ./node_modules/jasmine-node/bin/jasmine-node .&#x27;</span></span><br><span class="line"><span class="string"></span>    <span class="attr">TimeoutInMinutes:</span> <span class="number">10</span></span><br></pre></td></tr></table></figure><p>Now the CodeBuild project needs to be called in the pipeline, therefore change <code>Pipeline</code> resource in the file <code>deploy/pipeline_ec2.yml</code> to:</p><ol><li>deploy the CloudFormation stack suffixed with <code>-acceptance</code></li><li>run the acceptance tests</li></ol><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline_ec2.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline_ec2.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">Pipeline:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodePipeline::Pipeline&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">BuildAMI</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">AMIProject</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">App</span></span><br><span class="line">        <span class="attr">OutputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">AMI</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">2</span></span><br><span class="line">    <span class="comment"># NEW STUFF!</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Acceptance</span></span><br><span class="line">      <span class="attr">Actions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Deploy</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Deploy</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CloudFormation</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ActionMode:</span> <span class="string">CREATE_UPDATE</span></span><br><span class="line">          <span class="attr">Capabilities:</span> <span class="string">CAPABILITY_IAM</span></span><br><span class="line">          <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CloudFormationRole.Arn&#x27;</span></span><br><span class="line">          <span class="attr">StackName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-acceptance&#x27;</span></span><br><span class="line">          <span class="attr">TemplatePath:</span> <span class="string">&#x27;Source::infrastructure/ec2.yml&#x27;</span></span><br><span class="line">          <span class="attr">TemplateConfiguration:</span> <span class="string">&#x27;Source::infrastructure/ec2.json&#x27;</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">AMI</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Test</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">RunAcceptanceProject</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Acceptance</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">2</span></span><br></pre></td></tr></table></figure><p>The acceptance stage is now ready.</p><h3 id="Production-stage"><a href="#Production-stage" class="headerlink" title="Production stage"></a>Production stage</h3><p>The production stage is pretty simple, just one CloudFormation stack. Change the <code>Pipeline</code> resource in the file <code>deploy/pipeline_ec2.yml</code> to add a new stage that looks familiar to the acceptance stage:</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline_ec2.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline_ec2.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">Pipeline:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodePipeline::Pipeline&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="comment"># [...]</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Test</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">RunAcceptanceProject</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Acceptance</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">2</span></span><br><span class="line">    <span class="comment"># NEW STUFF!  </span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Production</span></span><br><span class="line">      <span class="attr">Actions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Deploy</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Deploy</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CloudFormation</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ActionMode:</span> <span class="string">CREATE_UPDATE</span></span><br><span class="line">          <span class="attr">Capabilities:</span> <span class="string">CAPABILITY_IAM</span></span><br><span class="line">          <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CloudFormationRole.Arn&#x27;</span></span><br><span class="line">          <span class="attr">StackName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-production&#x27;</span></span><br><span class="line">          <span class="attr">TemplatePath:</span> <span class="string">&#x27;Source::infrastructure/ec2.yml&#x27;</span></span><br><span class="line">          <span class="attr">TemplateConfiguration:</span> <span class="string">&#x27;Source::infrastructure/ec2.json&#x27;</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">AMI</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br></pre></td></tr></table></figure><p>Now the AMI containing the application is deployed to production with confidence and without disturbing the users. Try it and run the pipeline!</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Let’s use my production-ready definition to summarize how each point is implemented:</p><ul><li>Highly available: The load balancer (which is HA) sits in front of a fleet of EC2 instances managed by the Auto Scaling Group for maximum availability. In case of an unhealthy instance, the Auto Scaling Group will replace that instance.</li><li>Scalable: If the CPU utilization gets over 70%, a Cloud Watch Alarm triggers a Scaling Policy to add new instances automatically.</li><li>Frictionless deployment: To deploy a new version of the app, a new AMI is created. This AMI is then rolled out to the acceptance environment by updating the CloudFormation stack with the new ImageId parameter. CloudFormation and the Auto Scaling Group perform a rolling update to avoid the application being down during deployment. If the application can not be started the Rolling Update fails and CloudFormation rolls back.</li><li>Secure: During AMI creation, the latest patches are applied. You must ensure that the pipeline runs often enough to keep up with new patches. Besides that, Security Groups control network traffic to the EC2 instances. When following the bastion host approach, you get maximum security. The EC2 instance is only allowed to send logs to CloudWatch Logs by following the least privileges approach.</li><li>Operations: All logs are stored in CloudWatch Logs, important metrics are monitored and alarms are defined.</li></ul><p>If you now have the impression that running an app on EC2 is a lot of work you are right. In the next two articles, you will learn about other options with fewer responsibilities.</p><h2 id="Series"><a href="#Series" class="headerlink" title="Series"></a>Series</h2><p align="center"><img src="/images/2017/01/aws-velocity-cover.png" alt="AWS Velocity Cover" style="max-width: 349px"></p><ol><li><a href="/aws-velocity-set-the-assembly-line-up/">Set the assembly line up</a></li><li><a href="/aws-velocity-local-development-environment/">Local development environment</a></li><li><a href="/aws-velocity-ci-cd-pipeline-as-code/">CI&#x2F;CD Pipeline as Code</a></li><li><a href="/aws-velocity-running-your-application/">Running your application</a></li><li>EC2 based app<br>  a. <a href="/aws-velocity-ec2-based-app-infrastructure/">Infrastructure</a><br>  b. <strong>CI&#x2F;CD Pipeline</strong> (you are here)</li><li>Containerized ECS based app<br>  a. <a href="/aws-velocity-containerized-ecs-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-containerized-ecs-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li><a href="/aws-velocity-serverless-app/">Serverless app</a></li><li><a href="/aws-velocity-summary/">Summary</a></li></ol><p>You can find the source code on <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">GitHub</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Velocity Series: EC2 based app infrastructure</title>
      <link>https://cloudonaut.io/aws-velocity-ec2-based-app-infrastructure/</link>
      <description>
        <![CDATA[<p>To run a production-ready application on EC2 gives you maximum freedom but also maximum responsibilities. By production-ready, I mean:</p]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/computing-with-ec2/">Computing with EC2</category>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/velocity/">velocity</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-velocity-ec2-based-app-infrastructure/</guid>
      <pubDate>Sun, 05 Mar 2017 18:05:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>To run a production-ready application on EC2 gives you maximum freedom but also maximum responsibilities. By production-ready, I mean:</p><ul><li>Highly available: no single point of failure</li><li>Scalable: increase or decrease the number of instances based on load</li><li>Frictionless deployment: deliver new versions of your application automatically without downtime</li><li>Secure: patching operating systems and libraries frequently, follow the least privilege principle in all areas</li><li>Operations: provide tools like logging, monitoring and alerting to recognize and debug problems</li></ul><p>The overall architecture will consist of a load balancer, forwarding requests to multiple EC2 instances, distributed among different availability zones (data centers).</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/03/ec2-based-app@730w.webp 730w, /images/2017/03/ec2-based-app@730w2x.webp 1460w, /images/2017/03/ec2-based-app@610w.webp 610w, /images/2017/03/ec2-based-app@610w2x.webp 1220w, /images/2017/03/ec2-based-app@450w.webp 450w, /images/2017/03/ec2-based-app@450w2x.webp 900w, /images/2017/03/ec2-based-app@330w.webp 330w, /images/2017/03/ec2-based-app@330w2x.webp 660w, /images/2017/03/ec2-based-app@545w.webp 545w, /images/2017/03/ec2-based-app@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/03/ec2-based-app@730w.png 730w, /images/2017/03/ec2-based-app@730w2x.png 1460w, /images/2017/03/ec2-based-app@610w.png 610w, /images/2017/03/ec2-based-app@610w2x.png 1220w, /images/2017/03/ec2-based-app@450w.png 450w, /images/2017/03/ec2-based-app@450w2x.png 900w, /images/2017/03/ec2-based-app@330w.png 330w, /images/2017/03/ec2-based-app@330w2x.png 660w, /images/2017/03/ec2-based-app@545w.png 545w, /images/2017/03/ec2-based-app@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/03/ec2-based-app.png" alt="EC2 based app architecture" title="EC2 based app architecture"></picture></p><p><em>The diagram was created with <a href="https://bit.ly/2PpMc2M" target="_blank" rel="noopener">Cloudcraft - Visualize your cloud architecture like a pro</a>.</em></p><div class="well">  <div class="media">    <div class="media-left">      <a href="/aws-velocity-series/">        <img class="media-object raw" src="/images/2017/04/aws-velocity-cover-icon.png" alt="AWS Velocity Cover">      </a>    </div>    <div class="media-body">      <h4 class="media-heading">AWS Velocity Series</h4>      <p>Most of our clients use AWS to reduce time-to-market following an agile approach. But AWS is only one part of the solution. In this article series, I show you how we help our clients to improve velocity: the time from idea to production. <a href="/aws-velocity-series/">Discover all posts!</a></p>    </div>  </div></div><p>Let’s start simple and tackle all the challenges along the way.</p><h2 id="A-single-EC2-instance-is-a-single-point-of-failure"><a href="#A-single-EC2-instance-is-a-single-point-of-failure" class="headerlink" title="A single EC2 instance is a single point of failure"></a>A single EC2 instance is a single point of failure</h2><p>A single EC2 instance is a single point of failure. When you want to run a production-ready app on EC2, you need more than one EC2 instance. Luckily, AWS provides a way to manage multiple EC2 instances: the <strong>Auto Scaling Group</strong>. But if you run multiple EC2 instances to serve your application, you also need a load balancer to distribute the requests to one of the EC2 instances.</p><p>In the <a href="/aws-velocity-local-development-environment/">Local development environment</a> part of this series, you created an <code>infrastructure</code> folder which is empty by now. It’s time to change this. You will now create a CloudFormation template that describes the infrastructure that is needed to run the app on EC2 instances.</p><h2 id="Load-balancer"><a href="#Load-balancer" class="headerlink" title="Load balancer"></a>Load balancer</h2><blockquote><p>You can follow step by step or get the full source code here: <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">https://github.com/widdix/aws-velocity</a></p></blockquote><p>Create a file <code>infrastructure/ec2.yml</code>. The first part of the file contains the load balancer. To fully describe an Application Load Balancer, you need:</p><ul><li>A Security Group that allows traffic on port 80</li><li>The lApplication Load Balancer itself</li><li>A Target Group, which is a fleet of EC2 instances that can receive traffic from the load balancer</li><li>A Listener, which wires the load balancer together with the target group and defines the listening port</li></ul><p>Watch out for comments with more detailed information in the code.</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ec2.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ec2.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&#x27;EC2&#x27;</span></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="comment"># You can reuse a VPC for multiple applications. In this case, we use one of our Free Templates for AWS CloudFormation (https://github.com/widdix/aws-cf-templates/tree/master/vpc).</span></span><br><span class="line">  <span class="attr">ParentVPCStack:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;Stack name of parent VPC stack based on vpc/vpc-*azs.yaml template.&#x27;</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line"><span class="attr">Conditions:</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="comment"># The load balancer accepts HTTP traffic. Therefore the firewall must allow incoming traffic on port 80.</span></span><br><span class="line">  <span class="attr">LoadBalancerSecurityGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::SecurityGroup&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">GroupDescription:</span> <span class="string">&#x27;load-balancer-sg&#x27;</span></span><br><span class="line">      <span class="attr">VpcId:</span></span><br><span class="line">        <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentVPCStack&#125;-VPC&#x27;</span></span><br><span class="line">      <span class="attr">SecurityGroupIngress:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">CidrIp:</span> <span class="string">&#x27;0.0.0.0/0&#x27;</span></span><br><span class="line">        <span class="attr">FromPort:</span> <span class="number">80</span></span><br><span class="line">        <span class="attr">ToPort:</span> <span class="number">80</span></span><br><span class="line">        <span class="attr">IpProtocol:</span> <span class="string">tcp</span></span><br><span class="line">  <span class="comment"># The load balancer needs to run in public subnets because our users should be able to access the app from the Internet.</span></span><br><span class="line">  <span class="attr">LoadBalancer:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::ElasticLoadBalancingV2::LoadBalancer&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Scheme:</span> <span class="string">&#x27;internet-facing&#x27;</span></span><br><span class="line">      <span class="attr">SecurityGroups:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">LoadBalancerSecurityGroup</span></span><br><span class="line">      <span class="attr">Subnets:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentVPCStack&#125;-SubnetAPublic&#x27;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentVPCStack&#125;-SubnetBPublic&#x27;</span></span><br><span class="line">      <span class="attr">Tags:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Key:</span> <span class="string">Name</span></span><br><span class="line">        <span class="attr">Value:</span> <span class="string">&#x27;load-balancer&#x27;</span></span><br><span class="line">  <span class="comment"># A target group groups a bunch of backend instances that receive traffic from the load balancer. the health check ensures that only working backends are used.</span></span><br><span class="line">  <span class="attr">TargetGroup:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::ElasticLoadBalancingV2::TargetGroup&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">HealthCheckIntervalSeconds:</span> <span class="number">15</span></span><br><span class="line">      <span class="attr">HealthCheckPath:</span> <span class="string">&#x27;/5&#x27;</span></span><br><span class="line">      <span class="attr">HealthCheckPort:</span> <span class="number">3000</span></span><br><span class="line">      <span class="attr">HealthCheckProtocol:</span> <span class="string">HTTP</span></span><br><span class="line">      <span class="attr">HealthCheckTimeoutSeconds:</span> <span class="number">10</span></span><br><span class="line">      <span class="attr">HealthyThresholdCount:</span> <span class="number">2</span></span><br><span class="line">      <span class="attr">UnhealthyThresholdCount:</span> <span class="number">8</span></span><br><span class="line">      <span class="attr">Matcher:</span></span><br><span class="line">        <span class="attr">HttpCode:</span> <span class="number">200</span></span><br><span class="line">      <span class="attr">Port:</span> <span class="number">3000</span></span><br><span class="line">      <span class="attr">Protocol:</span> <span class="string">HTTP</span></span><br><span class="line">      <span class="attr">Tags:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Key:</span> <span class="string">Name</span></span><br><span class="line">        <span class="attr">Value:</span> <span class="string">&#x27;target-group&#x27;</span></span><br><span class="line">      <span class="attr">VpcId:</span></span><br><span class="line">        <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentVPCStack&#125;-VPC&#x27;</span></span><br><span class="line">  <span class="comment"># The load balancer should listen on port 80 for HTTP traffic</span></span><br><span class="line">  <span class="attr">Listener:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::ElasticLoadBalancingV2::Listener&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">DefaultActions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">TargetGroupArn:</span> <span class="type">!Ref</span> <span class="string">TargetGroup</span></span><br><span class="line">        <span class="attr">Type:</span> <span class="string">forward</span></span><br><span class="line">      <span class="attr">LoadBalancerArn:</span> <span class="type">!Ref</span> <span class="string">LoadBalancer</span></span><br><span class="line">      <span class="attr">Port:</span> <span class="number">80</span></span><br><span class="line">      <span class="attr">Protocol:</span> <span class="string">HTTP</span></span><br><span class="line"><span class="comment"># A CloudFormation stack can return information that is needed by other stacks or scripts.</span></span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">DNSName:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;The DNS name for the load balancer.&#x27;</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;LoadBalancer.DNSName&#x27;</span></span><br><span class="line">    <span class="attr">Export:</span></span><br><span class="line">      <span class="attr">Name:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-DNSName&#x27;</span></span><br><span class="line">  <span class="comment"># The URL is needed to run the acceptance test against the correct endpoint</span></span><br><span class="line">  <span class="attr">URL:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;URL to the load balancer.&#x27;</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Sub</span> <span class="string">&#x27;http://$&#123;LoadBalancer.DNSName&#125;&#x27;</span></span><br><span class="line">    <span class="attr">Export:</span></span><br><span class="line">      <span class="attr">Name:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-URL&#x27;</span></span><br></pre></td></tr></table></figure><p>But how do you get notified if something goes wrong? Let’s add a parameter to the <code>Parameters</code> section to make the receiver configurable:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ec2.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ec2.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">AdminEmail:</span></span><br><span class="line">  <span class="attr">Description:</span> <span class="string">&#x27;The email address of the admin who receives alerts.&#x27;</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">String</span></span><br></pre></td></tr></table></figure><p>Alerts are triggered by a CloudWatch Alarm which can send an alert to an SNS topic. You can subscribe to this topic via an email address to receive the alerts. Let’s create a SNS topic and two alarms in the <code>Resources</code> section:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ec2.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ec2.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># A SNS topic is used to send alerts via Email to the value of the AdminEmail parameter </span></span><br><span class="line"><span class="attr">Alerts:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::SNS::Topic&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Subscription:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Endpoint:</span> <span class="type">!Ref</span> <span class="string">AdminEmail</span></span><br><span class="line">      <span class="attr">Protocol:</span> <span class="string">email</span></span><br><span class="line"><span class="comment"># This alarm is triggered, if the load balancer responds with 5XX status codes</span></span><br><span class="line"><span class="attr">LoadBalancer5XXAlarm:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Statistic:</span> <span class="string">Sum</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">0</span></span><br><span class="line">    <span class="attr">AlarmDescription:</span> <span class="string">&#x27;Load balancer responds with 5XX status codes.&#x27;</span></span><br><span class="line">    <span class="attr">Period:</span> <span class="number">60</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Alerts</span></span><br><span class="line">    <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/ApplicationELB&#x27;</span></span><br><span class="line">    <span class="attr">Dimensions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">LoadBalancer</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;LoadBalancer.LoadBalancerFullName&#x27;</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br><span class="line">    <span class="attr">MetricName:</span> <span class="string">HTTPCode_ELB_5XX_Count</span></span><br><span class="line"><span class="comment"># This alarm is triggered, if the backend responds with 5XX status codes</span></span><br><span class="line"><span class="attr">LoadBalancerTargetGroup5XXAlarm:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Statistic:</span> <span class="string">Sum</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">0</span></span><br><span class="line">    <span class="attr">AlarmDescription:</span> <span class="string">&#x27;Load balancer target responds with 5XX status codes.&#x27;</span></span><br><span class="line">    <span class="attr">Period:</span> <span class="number">60</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Alerts</span></span><br><span class="line">    <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/ApplicationELB&#x27;</span></span><br><span class="line">    <span class="attr">Dimensions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">LoadBalancer</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;LoadBalancer.LoadBalancerFullName&#x27;</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br><span class="line">    <span class="attr">MetricName:</span> <span class="string">HTTPCode_Target_5XX_Count</span></span><br></pre></td></tr></table></figure><p>Let’s recap what you implemented: A load balancer with a firewall rule that allows traffic on port 80. In the case of 5XX status codes you will receive an Email. But the load balancer alone is not enough. Now it’s time to add the EC2 instances.</p><h2 id="EC2-instances"><a href="#EC2-instances" class="headerlink" title="EC2 instances"></a>EC2 instances</h2><p>So far, there are no EC2 instances. Let’s change that by adding a few more parameters in the <code>Parameters</code> section to make EC2 instances configurable:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ec2.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ec2.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># A bastion host increases the security of your system. In this case, we use one of our Free Templates for AWS CloudFormation (https://github.com/widdix/aws-cf-templates/tree/master/vpc).</span></span><br><span class="line"><span class="attr">ParentSSHBastionStack:</span></span><br><span class="line">  <span class="attr">Description:</span> <span class="string">&#x27;Optional Stack name of parent SSH bastion host/instance stack based on vpc/vpc-ssh-bastion.yaml template.&#x27;</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">  <span class="attr">Default:</span> <span class="string">&#x27;&#x27;</span></span><br><span class="line"><span class="comment"># This is the simple way of getting SSH access to your EC2 instance. Not the most secure way. If you want to have personalized users follow https://cloudonaut.io/manage-aws-ec2-ssh-access-with-iam/</span></span><br><span class="line"><span class="attr">KeyName:</span></span><br><span class="line">  <span class="attr">Description:</span> <span class="string">&#x27;Optional key pair of the ec2-user to establish a SSH connection.&#x27;</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">  <span class="attr">Default:</span> <span class="string">&#x27;&#x27;</span></span><br><span class="line"><span class="attr">InstanceType:</span></span><br><span class="line">  <span class="attr">Description:</span> <span class="string">&#x27;The instance type of web servers (e.g. t2.micro).&#x27;</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">  <span class="attr">Default:</span> <span class="string">&#x27;t2.micro&#x27;</span></span><br><span class="line"><span class="comment"># Where does this AMI comes from? It will be created in the CI/CD pipeline!</span></span><br><span class="line"><span class="attr">ImageId:</span></span><br><span class="line">  <span class="attr">Description:</span> <span class="string">&#x27;Unique ID of the Amazon Machine Image (AMI) to boot from.&#x27;</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line"><span class="comment"># How long do you want to keep logs?</span></span><br><span class="line"><span class="attr">LogsRetentionInDays:</span></span><br><span class="line">  <span class="attr">Description:</span> <span class="string">&#x27;Specifies the number of days you want to retain log events in the specified log group.&#x27;</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">Number</span></span><br><span class="line">  <span class="attr">Default:</span> <span class="number">14</span></span><br><span class="line">  <span class="attr">AllowedValues:</span> [<span class="number">1</span>, <span class="number">3</span>, <span class="number">5</span>, <span class="number">7</span>, <span class="number">14</span>, <span class="number">30</span>, <span class="number">60</span>, <span class="number">90</span>, <span class="number">120</span>, <span class="number">150</span>, <span class="number">180</span>, <span class="number">365</span>, <span class="number">400</span>, <span class="number">545</span>, <span class="number">731</span>, <span class="number">1827</span>, <span class="number">3653</span>]</span><br></pre></td></tr></table></figure><p>To make the template react differently to different parameter inputs, you need to add a few <code>Conditions</code> that will be used later in the template:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ec2.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ec2.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">HasKeyName:</span> <span class="type">!Not</span> [<span class="type">!Equals</span> [<span class="type">!Ref</span> <span class="string">KeyName</span>, <span class="string">&#x27;&#x27;</span>]]</span><br><span class="line"><span class="attr">HasSSHBastionSecurityGroup:</span> <span class="type">!Not</span> [<span class="type">!Equals</span> [<span class="type">!Ref</span> <span class="string">ParentSSHBastionStack</span>, <span class="string">&#x27;&#x27;</span>]]</span><br><span class="line"><span class="attr">HasNotSSHBastionSecurityGroup:</span> <span class="type">!Equals</span> [<span class="type">!Ref</span> <span class="string">ParentSSHBastionStack</span>, <span class="string">&#x27;&#x27;</span>]</span><br></pre></td></tr></table></figure><p>Now everything is prepared to describe the EC2 instances. You need:</p><ul><li>A Security Group that allows <ul><li>traffic on port 3000 from the load balancer Security Group</li><li>traffic on port 22 from the bastion host Security Group if the condition <code>HasSSHBastionSecurityGroup</code> is met</li><li>traffic on port 22 from the world if the condition <code>HasNotSSHBastionSecurityGroup</code> is met</li></ul></li><li>An Auto Scaling Group that defined how many EC2 instances should run</li><li>A CloudWatch Logs Group to capture the logs</li><li>A Instance Profile to reference the IAM Role</li><li>An IAM Role that allows access to deliver logs to CloudWatch Logs</li><li>A Launch Configuration that defined what kind of EC2 instances should be created by the Auto Scaling Group</li></ul><p>And also create a fleet of EC2 instances in the <code>Resources</code> section:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ec2.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ec2.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># The app listens on port 3000, but only the load balancer is allowed to send traffic to that port!</span></span><br><span class="line"><span class="attr">SecurityGroup:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::SecurityGroup&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">GroupDescription:</span> <span class="string">&#x27;ec2-sg&#x27;</span></span><br><span class="line">    <span class="attr">VpcId:</span></span><br><span class="line">      <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentVPCStack&#125;-VPC&#x27;</span></span><br><span class="line">    <span class="attr">SecurityGroupIngress:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">SourceSecurityGroupId:</span> <span class="type">!Ref</span> <span class="string">LoadBalancerSecurityGroup</span></span><br><span class="line">      <span class="attr">FromPort:</span> <span class="number">3000</span></span><br><span class="line">      <span class="attr">ToPort:</span> <span class="number">3000</span></span><br><span class="line">      <span class="attr">IpProtocol:</span> <span class="string">tcp</span></span><br><span class="line"><span class="comment"># If the bastion host approach is enabled, traffic on port 22 is only allowed from the bastion host</span></span><br><span class="line"><span class="attr">SecurityGroupInSSHBastion:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::SecurityGroupIngress&#x27;</span></span><br><span class="line">  <span class="attr">Condition:</span> <span class="string">HasSSHBastionSecurityGroup</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">GroupId:</span> <span class="type">!Ref</span> <span class="string">SecurityGroup</span></span><br><span class="line">    <span class="attr">IpProtocol:</span> <span class="string">tcp</span></span><br><span class="line">    <span class="attr">FromPort:</span> <span class="number">22</span></span><br><span class="line">    <span class="attr">ToPort:</span> <span class="number">22</span></span><br><span class="line">    <span class="attr">SourceSecurityGroupId:</span></span><br><span class="line">      <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentSSHBastionStack&#125;-SecurityGroup&#x27;</span></span><br><span class="line"><span class="comment"># Otherwise SSH is allowed from anywhere</span></span><br><span class="line"><span class="attr">SecurityGroupInSSHWorld:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::SecurityGroupIngress&#x27;</span></span><br><span class="line">  <span class="attr">Condition:</span> <span class="string">HasNotSSHBastionSecurityGroup</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">GroupId:</span> <span class="type">!Ref</span> <span class="string">SecurityGroup</span></span><br><span class="line">    <span class="attr">IpProtocol:</span> <span class="string">tcp</span></span><br><span class="line">    <span class="attr">FromPort:</span> <span class="number">22</span></span><br><span class="line">    <span class="attr">ToPort:</span> <span class="number">22</span></span><br><span class="line">    <span class="attr">CidrIp:</span> <span class="string">&#x27;0.0.0.0/0&#x27;</span></span><br><span class="line"><span class="attr">AutoScalingGroup:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::AutoScaling::AutoScalingGroup&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">LaunchConfigurationName:</span> <span class="type">!Ref</span> <span class="string">LaunchConfiguration</span> <span class="comment"># be patient, you will create a launch configuration soon</span></span><br><span class="line">    <span class="attr">MinSize:</span> <span class="number">2</span> <span class="comment"># at least two instances should always be running</span></span><br><span class="line">    <span class="attr">MaxSize:</span> <span class="number">4</span> <span class="comment"># at most 4 instances are allowed to run</span></span><br><span class="line">    <span class="attr">DesiredCapacity:</span> <span class="number">2</span> <span class="comment"># you want to start with 2 instances</span></span><br><span class="line">    <span class="attr">HealthCheckGracePeriod:</span> <span class="number">300</span></span><br><span class="line">    <span class="attr">HealthCheckType:</span> <span class="string">ELB</span> <span class="comment"># make use of the health check of the load balancer which checks the application health instead of only checking the instance health</span></span><br><span class="line">    <span class="attr">VPCZoneIdentifier:</span> </span><br><span class="line">    <span class="bullet">-</span> <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentVPCStack&#125;-SubnetAPublic&#x27;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">&#x27;Fn::ImportValue&#x27;:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;ParentVPCStack&#125;-SubnetBPublic&#x27;</span></span><br><span class="line">    <span class="attr">TargetGroupARNs:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">TargetGroup</span> <span class="comment"># automatically (de)register instances with the target group of the load balancer</span></span><br><span class="line">    <span class="attr">Tags:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Key:</span> <span class="string">Name</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="string">&#x27;ec2&#x27;</span></span><br><span class="line">      <span class="attr">PropagateAtLaunch:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">CreationPolicy:</span> <span class="comment"># wait up to 15 minutes to receive a success signal during instance startup</span></span><br><span class="line">    <span class="attr">ResourceSignal:</span></span><br><span class="line">      <span class="attr">Timeout:</span> <span class="string">PT15M</span></span><br><span class="line">  <span class="attr">UpdatePolicy:</span> <span class="comment"># this allows rolling updates if a change requires new EC2 instances</span></span><br><span class="line">    <span class="attr">AutoScalingRollingUpdate:</span></span><br><span class="line">      <span class="attr">PauseTime:</span> <span class="string">PT15M</span></span><br><span class="line">      <span class="attr">WaitOnResourceSignals:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>Let’s recap what you implemented: A firewall rule that allows traffic on port 3000 (the application’s port). Depending on if you use the bastion host approach or not, an appropriate firewall rule will be created to allow SSH access. You also added an Auto Scaling Group that can scale between 2 and 4 instances. So far you have not defined what kind of EC2 instances you want to start, let’s do this in the <code>Resources</code> section:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ec2.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ec2.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># Log files that reside on EC2 instances must be avoided because instances come and go depending on load. CloudWatch Logs provides a centralized way to store and search logs.</span></span><br><span class="line"><span class="attr">Logs:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::Logs::LogGroup&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">RetentionInDays:</span> <span class="type">!Ref</span> <span class="string">LogsRetentionInDays</span></span><br><span class="line"><span class="attr">InstanceProfile:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::InstanceProfile&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Path:</span> <span class="string">&#x27;/&#x27;</span></span><br><span class="line">    <span class="attr">Roles:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Role</span></span><br><span class="line"><span class="comment"># The EC2 instance needs permissions to make requests to the CloudWatch Logs service to deliver logs.</span></span><br><span class="line"><span class="attr">Role:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">      <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">      <span class="attr">Statement:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">        <span class="attr">Principal:</span></span><br><span class="line">          <span class="attr">Service:</span> <span class="string">&#x27;ec2.amazonaws.com&#x27;</span></span><br><span class="line">        <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    <span class="attr">Path:</span> <span class="string">&#x27;/&#x27;</span></span><br><span class="line">    <span class="attr">Policies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">logs</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogGroup&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogStream&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:DescribeLogStreams&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;arn:aws:logs:*:*:*&#x27;</span></span><br><span class="line"><span class="comment"># The Launch Configuration determines what kind of EC2 instances are launched by the Auto Scaling Group</span></span><br><span class="line"><span class="attr">LaunchConfiguration:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::AutoScaling::LaunchConfiguration&#x27;</span></span><br><span class="line">  <span class="attr">Metadata:</span></span><br><span class="line">    <span class="attr">&#x27;AWS::CloudFormation::Init&#x27;:</span> <span class="comment"># Configuration for the cfn-ini helper script that runs on startup. This is only needed for the dynamic configuration. The rest is backed into the AMI in the CI/CD pipeline.</span></span><br><span class="line">      <span class="attr">config:</span></span><br><span class="line">        <span class="attr">files:</span></span><br><span class="line">          <span class="string">&#x27;/etc/awslogs/awscli.conf&#x27;</span><span class="string">:</span> <span class="comment"># configuration file for the CloudWatch Logs agent that ships logs to the service</span></span><br><span class="line">            <span class="attr">content:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">              [default]</span></span><br><span class="line"><span class="string">              region = $&#123;AWS::Region&#125;</span></span><br><span class="line"><span class="string">              [plugins]</span></span><br><span class="line"><span class="string">              cwlogs = cwlogs</span></span><br><span class="line"><span class="string"></span>            <span class="attr">mode:</span> <span class="string">&#x27;000644&#x27;</span></span><br><span class="line">            <span class="attr">owner:</span> <span class="string">root</span></span><br><span class="line">            <span class="attr">group:</span> <span class="string">root</span></span><br><span class="line">          <span class="string">&#x27;/etc/awslogs/awslogs.conf&#x27;</span><span class="string">:</span> <span class="comment"># configuration file for the CloudWatch Logs agent that ships logs to the service</span></span><br><span class="line">            <span class="attr">content:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">              [general]</span></span><br><span class="line"><span class="string">              state_file = /var/lib/awslogs/agent-state</span></span><br><span class="line"><span class="string">              [/var/log/messages]</span></span><br><span class="line"><span class="string">              datetime_format = %b %d %H:%M:%S</span></span><br><span class="line"><span class="string">              file = /var/log/messages</span></span><br><span class="line"><span class="string">              log_stream_name = &#123;instance_id&#125;/var/log/messages</span></span><br><span class="line"><span class="string">              log_group_name = $&#123;Logs&#125;</span></span><br><span class="line"><span class="string">              [/var/log/secure]</span></span><br><span class="line"><span class="string">              datetime_format = %b %d %H:%M:%S</span></span><br><span class="line"><span class="string">              file = /var/log/secure</span></span><br><span class="line"><span class="string">              log_stream_name = &#123;instance_id&#125;/var/log/secure</span></span><br><span class="line"><span class="string">              log_group_name = $&#123;Logs&#125;</span></span><br><span class="line"><span class="string">              [/var/log/cron]</span></span><br><span class="line"><span class="string">              datetime_format = %b %d %H:%M:%S</span></span><br><span class="line"><span class="string">              file = /var/log/cron</span></span><br><span class="line"><span class="string">              log_stream_name = &#123;instance_id&#125;/var/log/cron</span></span><br><span class="line"><span class="string">              log_group_name = $&#123;Logs&#125;</span></span><br><span class="line"><span class="string">              [/var/log/cloud-init.log]</span></span><br><span class="line"><span class="string">              datetime_format = %b %d %H:%M:%S</span></span><br><span class="line"><span class="string">              file = /var/log/cloud-init.log</span></span><br><span class="line"><span class="string">              log_stream_name = &#123;instance_id&#125;/var/log/cloud-init.log</span></span><br><span class="line"><span class="string">              log_group_name = $&#123;Logs&#125;</span></span><br><span class="line"><span class="string">              [/var/log/cfn-init.log]</span></span><br><span class="line"><span class="string">              datetime_format = %Y-%m-%d %H:%M:%S</span></span><br><span class="line"><span class="string">              file = /var/log/cfn-init.log</span></span><br><span class="line"><span class="string">              log_stream_name = &#123;instance_id&#125;/var/log/cfn-init.log</span></span><br><span class="line"><span class="string">              log_group_name = $&#123;Logs&#125;</span></span><br><span class="line"><span class="string">              [/var/log/cfn-hup.log]</span></span><br><span class="line"><span class="string">              datetime_format = %Y-%m-%d %H:%M:%S</span></span><br><span class="line"><span class="string">              file = /var/log/cfn-hup.log</span></span><br><span class="line"><span class="string">              log_stream_name = &#123;instance_id&#125;/var/log/cfn-hup.log</span></span><br><span class="line"><span class="string">              log_group_name = $&#123;Logs&#125;</span></span><br><span class="line"><span class="string">              [/var/log/cfn-init-cmd.log]</span></span><br><span class="line"><span class="string">              datetime_format = %Y-%m-%d %H:%M:%S</span></span><br><span class="line"><span class="string">              file = /var/log/cfn-init-cmd.log</span></span><br><span class="line"><span class="string">              log_stream_name = &#123;instance_id&#125;/var/log/cfn-init-cmd.log</span></span><br><span class="line"><span class="string">              log_group_name = $&#123;Logs&#125;</span></span><br><span class="line"><span class="string">              [/var/log/cloud-init-output.log]</span></span><br><span class="line"><span class="string">              file = /var/log/cloud-init-output.log</span></span><br><span class="line"><span class="string">              log_stream_name = &#123;instance_id&#125;/var/log/cloud-init-output.log</span></span><br><span class="line"><span class="string">              log_group_name = $&#123;Logs&#125;</span></span><br><span class="line"><span class="string">              [/var/log/dmesg]</span></span><br><span class="line"><span class="string">              file = /var/log/dmesg</span></span><br><span class="line"><span class="string">              log_stream_name = &#123;instance_id&#125;/var/log/dmesg</span></span><br><span class="line"><span class="string">              log_group_name = $&#123;Logs&#125;</span></span><br><span class="line"><span class="string">              [/var/log/forever.log]</span></span><br><span class="line"><span class="string">              file = /var/log/forever.log</span></span><br><span class="line"><span class="string">              log_stream_name = &#123;instance_id&#125;/var/log/forever.log</span></span><br><span class="line"><span class="string">              log_group_name = $&#123;Logs&#125;</span></span><br><span class="line"><span class="string">              [/var/log/app.out]</span></span><br><span class="line"><span class="string">              file = /var/log/app.out</span></span><br><span class="line"><span class="string">              log_stream_name = &#123;instance_id&#125;/var/log/app.out</span></span><br><span class="line"><span class="string">              log_group_name = $&#123;Logs&#125;</span></span><br><span class="line"><span class="string">              [/var/log/app.err]</span></span><br><span class="line"><span class="string">              file = /var/log/app.err</span></span><br><span class="line"><span class="string">              log_stream_name = &#123;instance_id&#125;/var/log/app.err</span></span><br><span class="line"><span class="string">              log_group_name = $&#123;Logs&#125;</span></span><br><span class="line"><span class="string"></span>            <span class="attr">mode:</span> <span class="string">&#x27;000644&#x27;</span></span><br><span class="line">            <span class="attr">owner:</span> <span class="string">root</span></span><br><span class="line">            <span class="attr">group:</span> <span class="string">root</span></span><br><span class="line">        <span class="attr">commands:</span></span><br><span class="line">          <span class="attr">&#x27;forever&#x27;:</span></span><br><span class="line">            <span class="attr">command:</span> <span class="string">&#x27;forever start -l /var/log/forever.log -o /var/log/app.out -e /var/log/app.err index.js&#x27;</span> <span class="comment"># forever keeps the app (a Node.js script) up and running in the background</span></span><br><span class="line">            <span class="attr">cwd:</span> <span class="string">&#x27;/opt/app&#x27;</span></span><br><span class="line">        <span class="attr">services:</span></span><br><span class="line">          <span class="attr">sysvinit:</span></span><br><span class="line">            <span class="attr">awslogs:</span> <span class="comment"># start the CloudWatch Logs agent</span></span><br><span class="line">              <span class="attr">enabled:</span> <span class="literal">true</span></span><br><span class="line">              <span class="attr">ensureRunning:</span> <span class="literal">true</span></span><br><span class="line">              <span class="attr">files:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="string">&#x27;/etc/awslogs/awslogs.conf&#x27;</span></span><br><span class="line">              <span class="bullet">-</span> <span class="string">&#x27;/etc/awslogs/awscli.conf&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">ImageId:</span> <span class="type">!Ref</span> <span class="string">ImageId</span> <span class="comment"># the image that is created during the build in the CI/CD pipeline passed in as a parameter</span></span><br><span class="line">    <span class="attr">IamInstanceProfile:</span> <span class="type">!Ref</span> <span class="string">InstanceProfile</span></span><br><span class="line">    <span class="attr">InstanceType:</span> <span class="type">!Ref</span> <span class="string">InstanceType</span></span><br><span class="line">    <span class="attr">SecurityGroups:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">SecurityGroup</span></span><br><span class="line">    <span class="attr">KeyName:</span> <span class="type">!If</span> [<span class="string">HasKeyName</span>, <span class="type">!Ref</span> <span class="string">KeyName</span>, <span class="type">!Ref</span> <span class="string">&#x27;AWS::NoValue&#x27;</span>]</span><br><span class="line">    <span class="attr">UserData:</span> <span class="comment"># execute cfn-init helper script and signal success or failure back to CloudFormation</span></span><br><span class="line">      <span class="attr">&#x27;Fn::Base64&#x27;:</span> <span class="type">!Sub</span> <span class="string">|</span></span><br><span class="line"><span class="string">        #!/bin/bash -x</span></span><br><span class="line"><span class="string">        /opt/aws/bin/cfn-init -v --stack $&#123;AWS::StackName&#125; --resource LaunchConfiguration --region $&#123;AWS::Region&#125;</span></span><br><span class="line"><span class="string">        /opt/aws/bin/cfn-signal -e $? --stack $&#123;AWS::StackName&#125; --resource AutoScalingGroup --region $&#123;AWS::Region&#125;</span></span><br></pre></td></tr></table></figure><p>Let’s recap what you implemented: The Launch Configuration defines what kind of EC2 instances the Auto Scaling Group creates. The <code>cfn-init</code> script reads <code>Metadata</code> from CloudFormation to configure an running EC2 instance dynamically. The <code>cfn-signal</code> script reports to CloudFormation if the EC2 instance was started successfully or not. CloudWatch Logs stored the log files that are delivered by an agent that runs on the EC2 instance.</p><h2 id="Auto-Scaling"><a href="#Auto-Scaling" class="headerlink" title="Auto Scaling"></a>Auto Scaling</h2><p>So far, the number of EC2 instances is static. To scale based on the load you need to add</p><ul><li>Scaling Policies to define what should happen if the system should scale up&#x2F;down</li><li>CloudWatch Alarms to trigger a Scaling Policy based on a metric such as CPU utilization</li></ul><p>to the <code>Resources</code> section:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ec2.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ec2.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># Increase the number of instances by 25% but at least by one not more often than every 10 minutes.</span></span><br><span class="line"><span class="attr">ScalingUpPolicy:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::AutoScaling::ScalingPolicy&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AdjustmentType:</span> <span class="string">PercentChangeInCapacity</span></span><br><span class="line">    <span class="attr">MinAdjustmentStep:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">AutoScalingGroupName:</span> <span class="type">!Ref</span> <span class="string">AutoScalingGroup</span></span><br><span class="line">    <span class="attr">Cooldown:</span> <span class="number">600</span></span><br><span class="line">    <span class="attr">ScalingAdjustment:</span> <span class="number">25</span></span><br><span class="line"><span class="comment"># Decrease the number of instances by 25% but at least by one one not more often than every 15 minutes.</span></span><br><span class="line"><span class="attr">ScalingDownPolicy:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::AutoScaling::ScalingPolicy&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AdjustmentType:</span> <span class="string">PercentChangeInCapacity</span></span><br><span class="line">    <span class="attr">MinAdjustmentStep:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">AutoScalingGroupName:</span> <span class="type">!Ref</span> <span class="string">AutoScalingGroup</span></span><br><span class="line">    <span class="attr">Cooldown:</span> <span class="number">900</span></span><br><span class="line">    <span class="attr">ScalingAdjustment:</span> <span class="number">-25</span></span><br><span class="line"><span class="comment"># Trigger the ScalingUpPolicy if the average CPU load of the past 5 minutes is higher than 70%</span></span><br><span class="line"><span class="attr">CPUHighAlarm:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Statistic:</span> <span class="string">Average</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">70</span></span><br><span class="line">    <span class="attr">AlarmDescription:</span> <span class="string">&#x27;CPU load is high.&#x27;</span></span><br><span class="line">    <span class="attr">Period:</span> <span class="number">300</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">ScalingUpPolicy</span></span><br><span class="line">    <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/EC2&#x27;</span></span><br><span class="line">    <span class="attr">Dimensions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">AutoScalingGroupName</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">AutoScalingGroup</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br><span class="line">    <span class="attr">MetricName:</span> <span class="string">CPUUtilization</span></span><br><span class="line"><span class="comment"># Trigger the ScalingDownPolicy if the average CPU load of the past 5 minutes is lower than 30% for 3 consecutive times</span></span><br><span class="line"><span class="attr">CPULowAlarm:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">3</span></span><br><span class="line">    <span class="attr">Statistic:</span> <span class="string">Average</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">30</span></span><br><span class="line">    <span class="attr">AlarmDescription:</span> <span class="string">&#x27;CPU load is low.&#x27;</span></span><br><span class="line">    <span class="attr">Period:</span> <span class="number">300</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">ScalingDownPolicy</span></span><br><span class="line">    <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/EC2&#x27;</span></span><br><span class="line">    <span class="attr">Dimensions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">AutoScalingGroupName</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">AutoScalingGroup</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">LessThanThreshold</span></span><br><span class="line">    <span class="attr">MetricName:</span> <span class="string">CPUUtilization</span></span><br></pre></td></tr></table></figure><p>Let’s recap what you implemented: The Scaling Policy defines what happens when you want to scale while a CloudWatch Alarm triggers the Scaling Policy based on live metrics like CPUUtilization. The Auto Scaling Group will now keep a dynamic number of EC2 instances but always ensures that not less that two instances are running and not more than 4.</p><p>One thing is missing: Monitoring of your EC2 instances. Add</p><ul><li>A CloudWatch Alarm to monitor the CPU utilization</li><li>A Log Filter that searches for the word <code>Error</code> in the logs and puts the result count into a CloudWatch Metric</li><li>A CloudWatch Alarm that monitors the Log Filter output</li></ul><p>to your <code>Resources</code> section:</p><figure class="highlight yaml"><figcaption><span>infrastructure&#x2F;ec2.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/infrastructure/ec2.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># Sends an alert if the average CPU load of the past 5 minutes is higher than 85%</span></span><br><span class="line"><span class="attr">CPUTooHighAlarm:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Statistic:</span> <span class="string">Average</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">85</span></span><br><span class="line">    <span class="attr">AlarmDescription:</span> <span class="string">&#x27;CPU load is too high.&#x27;</span></span><br><span class="line">    <span class="attr">Period:</span> <span class="number">300</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Alerts</span></span><br><span class="line">    <span class="attr">Namespace:</span> <span class="string">&#x27;AWS/EC2&#x27;</span></span><br><span class="line">    <span class="attr">Dimensions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">AutoScalingGroupName</span></span><br><span class="line">      <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">AutoScalingGroup</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br><span class="line">    <span class="attr">MetricName:</span> <span class="string">CPUUtilization</span></span><br><span class="line"><span class="comment"># Filters all logs for the word Error</span></span><br><span class="line"><span class="attr">AppErrorsLogsFilter:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::Logs::MetricFilter&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">FilterPattern:</span> <span class="string">Error</span></span><br><span class="line">    <span class="attr">LogGroupName:</span> <span class="type">!Ref</span> <span class="string">Logs</span></span><br><span class="line">    <span class="attr">MetricTransformations:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">MetricName:</span> <span class="string">AppErrors</span></span><br><span class="line">      <span class="attr">MetricNamespace:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line">      <span class="attr">MetricValue:</span> <span class="number">1</span></span><br><span class="line"><span class="comment"># Sends an alert if the word Error was found in the logs</span></span><br><span class="line"><span class="attr">AppErrorsAlarm:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CloudWatch::Alarm&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AlarmDescription:</span> <span class="string">&#x27;application errors in logs&#x27;</span></span><br><span class="line">    <span class="attr">Namespace:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line">    <span class="attr">MetricName:</span> <span class="string">AppErrors</span></span><br><span class="line">    <span class="attr">Statistic:</span> <span class="string">Sum</span></span><br><span class="line">    <span class="attr">Period:</span> <span class="number">60</span></span><br><span class="line">    <span class="attr">EvaluationPeriods:</span> <span class="number">1</span></span><br><span class="line">    <span class="attr">Threshold:</span> <span class="number">0</span></span><br><span class="line">    <span class="attr">ComparisonOperator:</span> <span class="string">GreaterThanThreshold</span></span><br><span class="line">    <span class="attr">AlarmActions:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">Alerts</span></span><br></pre></td></tr></table></figure><p>The infrastructure is ready now. Read the next part of the series to learn how to setup the <a href="/aws-velocity-ec2-based-app-ci-cd-pipeline/">CI&#x2F;CD pipeline to deploy the EC2 based app</a>.</p><h2 id="Series"><a href="#Series" class="headerlink" title="Series"></a>Series</h2><p align="center"><img src="/images/2017/01/aws-velocity-cover.png" alt="AWS Velocity Cover" style="max-width: 349px"></p><ol><li><a href="/aws-velocity-set-the-assembly-line-up/">Set the assembly line up</a></li><li><a href="/aws-velocity-local-development-environment/">Local development environment</a></li><li><a href="/aws-velocity-ci-cd-pipeline-as-code/">CI&#x2F;CD Pipeline as Code</a></li><li><a href="/aws-velocity-running-your-application/">Running your application</a></li><li>EC2 based app<br>  a. <strong>Infrastructure</strong> (you are here)<br>  b. <a href="/aws-velocity-ec2-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li>Containerized ECS based app<br>  a. <a href="/aws-velocity-containerized-ecs-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-containerized-ecs-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li><a href="/aws-velocity-serverless-app/">Serverless app</a></li><li><a href="/aws-velocity-summary/">Summary</a></li></ol><p>You can find the source code on <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">GitHub</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>DZone Cloud Guide including Serverless Architectures on AWS</title>
      <link>https://cloudonaut.io/dzone-cloud-guide-serverless-architectures-on-aws/</link>
      <description>
        <![CDATA[<p>DZone released a new guide: <a href="https://dzone.com/guides/the-cloud-native-development-and-deployment" target="_blank" rel="noopener"]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/dzone-cloud-guide-serverless-architectures-on-aws/</guid>
      <pubDate>Tue, 28 Feb 2017 21:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>DZone released a new guide: <a href="https://dzone.com/guides/the-cloud-native-development-and-deployment" target="_blank" rel="noopener">Cloud Native Development &amp; Deployment</a>. I’m proud that my article <strong>Serverless Architectures on AWS</strong> is part of the free ebook as well.</p><p><a href="https://dzone.com/guides/the-cloud-native-development-and-deployment" target="_blank" rel="noopener"><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/02/cloud-guide-2017@730w.webp 730w, /images/2017/02/cloud-guide-2017@730w2x.webp 1460w, /images/2017/02/cloud-guide-2017@610w.webp 610w, /images/2017/02/cloud-guide-2017@610w2x.webp 1220w, /images/2017/02/cloud-guide-2017@450w.webp 450w, /images/2017/02/cloud-guide-2017@450w2x.webp 900w, /images/2017/02/cloud-guide-2017@330w.webp 330w, /images/2017/02/cloud-guide-2017@330w2x.webp 660w, /images/2017/02/cloud-guide-2017@545w.webp 545w, /images/2017/02/cloud-guide-2017@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/02/cloud-guide-2017@730w.png 730w, /images/2017/02/cloud-guide-2017@730w2x.png 1460w, /images/2017/02/cloud-guide-2017@610w.png 610w, /images/2017/02/cloud-guide-2017@610w2x.png 1220w, /images/2017/02/cloud-guide-2017@450w.png 450w, /images/2017/02/cloud-guide-2017@450w2x.png 900w, /images/2017/02/cloud-guide-2017@330w.png 330w, /images/2017/02/cloud-guide-2017@330w2x.png 660w, /images/2017/02/cloud-guide-2017@545w.png 545w, /images/2017/02/cloud-guide-2017@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/02/cloud-guide-2017.png" alt="Cloud Native Development &amp;amp; Deployment" title="Cloud Native Development &amp;amp; Deployment"></picture></a></p><h2 id="Key-takeaways-from-my-article"><a href="#Key-takeaways-from-my-article" class="headerlink" title="Key takeaways from my article"></a>Key takeaways from my article</h2><p>A serverless platform allows you to run your application including computing, storing, and networking without the need of spinning up and managing a single (virtual) machine. Being able to focus on software development instead of operating a fleet of servers is the primary driver behind serverless. Dividing your application into small functions is necessary when following a serverless approach. Making use of microservice architectures helps you to achieve that goal. Serverless is made for event-driven architectures.</p><h2 id="More-articles-from-the-guide"><a href="#More-articles-from-the-guide" class="headerlink" title="More articles from the guide"></a>More articles from the guide</h2><ul><li>Cloud Native Applications and the CAP Theorem</li><li>Diving Deeper Into Cloud</li><li>Adapting Serverless Architecture</li><li>Cloud-native Middleware Microservices</li></ul><p><a href="https://dzone.com/guides/the-cloud-native-development-and-deployment" target="_blank" rel="noopener">Get a free copy now!</a></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>
        <![CDATA[New CloudFormation Templates - ECS Cluster & Service, legacy VPC wrapper, automated tests]]>
      </title>
      <link>https://cloudonaut.io/new-cloudformation-templates-ecs-cluster-service-legacy-vpc-wrapper-automated-tests/</link>
      <description>
        <![CDATA[<p>We released <strong>v3</strong> of our free and open source CloudFormation templates. This release brings:</p>
<ul>
<li><a href="https://]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/new-cloudformation-templates-ecs-cluster-service-legacy-vpc-wrapper-automated-tests/</guid>
      <pubDate>Tue, 28 Feb 2017 17:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>We released <strong>v3</strong> of our free and open source CloudFormation templates. This release brings:</p><ul><li><a href="https://github.com/widdix/aws-cf-templates/tree/master/ecs" target="_blank" rel="noopener">new templates for containerized workload with ECS</a></li><li><a href="https://github.com/widdix/aws-cf-templates/tree/master/vpc" target="_blank" rel="noopener">new templates to wrap an existing VPC into the format that is needed by all our templates</a></li><li><a href="https://github.com/widdix/aws-cf-templates/tree/master/test" target="_blank" rel="noopener">test suite that we run on every commit to ensure that the templates are still working</a></li></ul><p>you can read the full <a href="https://github.com/widdix/aws-cf-templates/releases/tag/v3.0.0" target="_blank" rel="noopener">Changelog on GitHub</a>.</p><h2 id="ECS"><a href="#ECS" class="headerlink" title="ECS"></a>ECS</h2><p><a href="https://aws.amazon.com/ecs/" target="_blank" rel="noopener">EC2 Container Service (ECS)</a> is a highly scalable, fast, container management service that makes it easy to run, stop, and manage Docker containers on a cluster of Amazon EC2 instances. To run an application on ECS you need the following components:</p><ul><li>Docker image published to <a href="https://hub.docker.com/" target="_blank" rel="noopener">Docker Hub</a> or <a href="https://aws.amazon.com/ecr/" target="_blank" rel="noopener">EC2 Container Registry (ECR)</a></li><li>ECS cluster</li><li>ECS service</li></ul><p>We provide you templates for the ECS cluster and the service. You need to publish the Docker image.</p><h2 id="ECS-cluster"><a href="#ECS-cluster" class="headerlink" title="ECS cluster"></a>ECS cluster</h2><p>This template describes a fault tolerant and scalable ECS cluster on AWS. The cluster scales the underlying EC2 instances based on memory and CPU reservation. In case of a scale down, the instance drains all containers before it is terminated.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/02/ecs-cluster@730w.webp 730w, /images/2017/02/ecs-cluster@730w2x.webp 1460w, /images/2017/02/ecs-cluster@610w.webp 610w, /images/2017/02/ecs-cluster@610w2x.webp 1220w, /images/2017/02/ecs-cluster@450w.webp 450w, /images/2017/02/ecs-cluster@450w2x.webp 900w, /images/2017/02/ecs-cluster@330w.webp 330w, /images/2017/02/ecs-cluster@330w2x.webp 660w, /images/2017/02/ecs-cluster@545w.webp 545w, /images/2017/02/ecs-cluster@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/02/ecs-cluster@730w.png 730w, /images/2017/02/ecs-cluster@730w2x.png 1460w, /images/2017/02/ecs-cluster@610w.png 610w, /images/2017/02/ecs-cluster@610w2x.png 1220w, /images/2017/02/ecs-cluster@450w.png 450w, /images/2017/02/ecs-cluster@450w2x.png 900w, /images/2017/02/ecs-cluster@330w.png 330w, /images/2017/02/ecs-cluster@330w2x.png 660w, /images/2017/02/ecs-cluster@545w.png 545w, /images/2017/02/ecs-cluster@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/02/ecs-cluster.png" alt="Architecture" title="Architecture"></picture></p><p><em>The diagram was created with <a href="https://bit.ly/2PpMc2M" target="_blank" rel="noopener">Cloudcraft - Visualize your cloud architecture like a pro</a>.</em></p><p><a href="https://github.com/widdix/aws-cf-templates/tree/master/ecs#ecs-cluster" target="_blank" rel="noopener">Install free template</a></p><h2 id="ECS-service"><a href="#ECS-service" class="headerlink" title="ECS service"></a>ECS service</h2><p>This template describes a fault tolerant and scalable ECS service on AWS. The service scales based on CPU utilization.</p><blockquote><p>The image needs to expose port 80 or the <code>AWS::ECS::TaskDefinition</code> needs to be adjusted!</p></blockquote><p>We provide two service templates:</p><ul><li><code>service-cluster-alb.yaml</code> uses the cluster’s load balancer and path based routing. If you want to run multiple services on the same cluster they all will use the same domain name but start with different paths (e.g. <code>https://yourdomain.com/service1/</code> and <code>https://yourdomain.com/service2/</code>).</li><li><code>service-dedicated-alb.yaml</code> includes a dedicated load balancer (ALB). You can then use a separate domain name for each service.</li></ul><h3 id="Using-the-cluster’s-load-balancer-and-path-based-routing"><a href="#Using-the-cluster’s-load-balancer-and-path-based-routing" class="headerlink" title="Using the cluster’s load balancer and path based routing"></a>Using the cluster’s load balancer and path based routing</h3><p>This template describes a fault tolerant and scalable ECS service that uses the cluster’s load balancer and path based routing.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/02/ecs-service-cluster-alb@730w.webp 730w, /images/2017/02/ecs-service-cluster-alb@730w2x.webp 1460w, /images/2017/02/ecs-service-cluster-alb@610w.webp 610w, /images/2017/02/ecs-service-cluster-alb@610w2x.webp 1220w, /images/2017/02/ecs-service-cluster-alb@450w.webp 450w, /images/2017/02/ecs-service-cluster-alb@450w2x.webp 900w, /images/2017/02/ecs-service-cluster-alb@330w.webp 330w, /images/2017/02/ecs-service-cluster-alb@330w2x.webp 660w, /images/2017/02/ecs-service-cluster-alb@545w.webp 545w, /images/2017/02/ecs-service-cluster-alb@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/02/ecs-service-cluster-alb@730w.png 730w, /images/2017/02/ecs-service-cluster-alb@730w2x.png 1460w, /images/2017/02/ecs-service-cluster-alb@610w.png 610w, /images/2017/02/ecs-service-cluster-alb@610w2x.png 1220w, /images/2017/02/ecs-service-cluster-alb@450w.png 450w, /images/2017/02/ecs-service-cluster-alb@450w2x.png 900w, /images/2017/02/ecs-service-cluster-alb@330w.png 330w, /images/2017/02/ecs-service-cluster-alb@330w2x.png 660w, /images/2017/02/ecs-service-cluster-alb@545w.png 545w, /images/2017/02/ecs-service-cluster-alb@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/02/ecs-service-cluster-alb.png" alt="Architecture" title="Architecture"></picture></p><p><em>The diagram was created with <a href="https://bit.ly/2PpMc2M" target="_blank" rel="noopener">Cloudcraft - Visualize your cloud architecture like a pro</a>.</em></p><p><a href="https://github.com/widdix/aws-cf-templates/tree/master/ecs#using-the-clusters-load-balancer-and-path-based-routing" target="_blank" rel="noopener">Install free template</a></p><h3 id="Using-a-dedicated-load-balancer-for-the-service"><a href="#Using-a-dedicated-load-balancer-for-the-service" class="headerlink" title="Using a dedicated load balancer for the service"></a>Using a dedicated load balancer for the service</h3><p>This template describes a fault tolerant and scalable ECS service that uses a dedicated load balancer for the service.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/02/ecs-service-dedicated-alb@730w.webp 730w, /images/2017/02/ecs-service-dedicated-alb@730w2x.webp 1460w, /images/2017/02/ecs-service-dedicated-alb@610w.webp 610w, /images/2017/02/ecs-service-dedicated-alb@610w2x.webp 1220w, /images/2017/02/ecs-service-dedicated-alb@450w.webp 450w, /images/2017/02/ecs-service-dedicated-alb@450w2x.webp 900w, /images/2017/02/ecs-service-dedicated-alb@330w.webp 330w, /images/2017/02/ecs-service-dedicated-alb@330w2x.webp 660w, /images/2017/02/ecs-service-dedicated-alb@545w.webp 545w, /images/2017/02/ecs-service-dedicated-alb@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/02/ecs-service-dedicated-alb@730w.png 730w, /images/2017/02/ecs-service-dedicated-alb@730w2x.png 1460w, /images/2017/02/ecs-service-dedicated-alb@610w.png 610w, /images/2017/02/ecs-service-dedicated-alb@610w2x.png 1220w, /images/2017/02/ecs-service-dedicated-alb@450w.png 450w, /images/2017/02/ecs-service-dedicated-alb@450w2x.png 900w, /images/2017/02/ecs-service-dedicated-alb@330w.png 330w, /images/2017/02/ecs-service-dedicated-alb@330w2x.png 660w, /images/2017/02/ecs-service-dedicated-alb@545w.png 545w, /images/2017/02/ecs-service-dedicated-alb@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/02/ecs-service-dedicated-alb.png" alt="Architecture" title="Architecture"></picture></p><p><em>The diagram was created with <a href="https://bit.ly/2PpMc2M" target="_blank" rel="noopener">Cloudcraft - Visualize your cloud architecture like a pro</a>.</em></p><p><a href="https://github.com/widdix/aws-cf-templates/tree/master/ecs#using-a-dedicated-load-balancer-for-the-service" target="_blank" rel="noopener">Install free template</a></p><h2 id="VPC-legacy-wrapper"><a href="#VPC-legacy-wrapper" class="headerlink" title="VPC legacy wrapper"></a>VPC legacy wrapper</h2><p>If you have an existing VPC you can wrap it into our required form using a legacy VPC wrapper.</p><p><a href="https://github.com/widdix/aws-cf-templates/tree/master/vpc" target="_blank" rel="noopener">Install free template</a></p><h2 id="Automated-tests"><a href="#Automated-tests" class="headerlink" title="Automated tests"></a>Automated tests</h2><p>The goal of the tests is to ensure that our templates are always working. The test are implemented in Java 8 and run in JUnit 4.</p><p>If you run this tests, many AWS CloudFormation tests are created and <strong>charges will apply</strong>!</p><p><a href="https://widdix.net/" target="_blank" rel="noopener">widdix GmbH</a> sponsors the test runs on every push and once per week to ensure that everything is working as expected.</p><p><a href="https://github.com/widdix/aws-cf-templates/tree/master/test" target="_blank" rel="noopener">Discover the tests</a></p><h2 id="Support"><a href="#Support" class="headerlink" title="Support"></a>Support</h2><p>We offer support for our CloudFormation templates: setting up environments based on our templates, adopting templates to specific use cases, resolving issues in production environments. <a href="https://widdix.net" class="btn btn-primary">Hire us!</a></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Velocity Series: Running your application</title>
      <link>https://cloudonaut.io/aws-velocity-running-your-application/</link>
      <description>
        <![CDATA[<p>There are many options when it comes to running an application on AWS. EC2 based, containerized, or serverless. Choosing the best option]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/velocity/">velocity</category>
      <category domain="https://cloudonaut.io/tag/container/">container</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-velocity-running-your-application/</guid>
      <pubDate>Tue, 21 Feb 2017 09:05:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>There are many options when it comes to running an application on AWS. EC2 based, containerized, or serverless. Choosing the best option for your specific use case is important.</p><p>All options that I present are what I call production-ready:</p><ul><li>Highly available: no single point of failure</li><li>Scalable: increase or decrease the number of instances based on load</li><li>Frictionless deployment: deliver new versions of your application automatically without downtime</li><li>Secure: patching operating systems and libraries frequently, follow the least privilege principle in all areas</li><li>Operations: provide tools like logging, monitoring and alerting to recognize and debug problems</li></ul><div class="well">  <div class="media">    <div class="media-left">      <a href="/aws-velocity-series/">        <img class="media-object raw" src="/images/2017/04/aws-velocity-cover-icon.png" alt="AWS Velocity Cover">      </a>    </div>    <div class="media-body">      <h4 class="media-heading">AWS Velocity Series</h4>      <p>Most of our clients use AWS to reduce time-to-market following an agile approach. But AWS is only one part of the solution. In this article series, I show you how we help our clients to improve velocity: the time from idea to production. <a href="/aws-velocity-series/">Discover all posts!</a></p>    </div>  </div></div><p>Let’s start by introducing the available options to you. After that, I present a comparison table to you.</p><h2 id="EC2-based-app"><a href="#EC2-based-app" class="headerlink" title="EC2 based app"></a>EC2 based app</h2><p>An EC2 based app runs directly on a virtual machine: the EC2 instance. You can choose a flavor of Windows or Linux as your operating system. You get root access to the virtual machine so there are no limitations in what you can install and configure. But keep in mind that you are also responsible for the operating system and all installed software. Patching is your job.</p><p>By default, a single EC2 instance can not guarantee high availability. That’s why you need more than one EC2 instance. An Auto Scaling Group can manage such a fleet of EC2 instances for you. And as the name implies, the Auto Scaling Group is also a building block when it comes to scalability. To provide a stable endpoint for your users, you also need a load balancer in front of your dynamic fleet of EC2 instances.</p><p>The way you deploy software is not defined by AWS. You can download your software during the start of the virtual machine, create your own AMIs with your software backed in, or use configuration management tools to install what you need. Again, many choices but also many responsibilities.</p><blockquote><p>You may miss Elastic Beanstalk or OpsWorks here. The last 4 years of using AWS in many client projectshave shown that those services come with too many limitations for running existing apps. This is different if the app is build from scratch. But if an app is build from scratch I would suggest a serverless approach!</p></blockquote><h2 id="Containerized-app"><a href="#Containerized-app" class="headerlink" title="Containerized app"></a>Containerized app</h2><p>A containerized app (Docker) can run on ECS: the container cluster service from AWS. ECS runs on top of <strong>EC2 instances that you have to manage</strong>. ECS makes it very easy to schedule containers in an intelligent way (e.g. zone aware) and also restarts failed containers. ECS comes with a nice integration with the load balancer. You can run all your applications on a single ECS cluster which lead to a better utilization of the underlying hardware compared to running directly on EC2.</p><p>To deploy software you need to create a Docker image, push that image to a Docker repository, and than ECS will take care of the rest. You are responsible for publishing a Docker image while AWS takes care of the rest.</p><h2 id="Serverless-app"><a href="#Serverless-app" class="headerlink" title="Serverless app"></a>Serverless app</h2><p>A serverless app (e.g. API Gateway &amp; Lambda) is operated completely by AWS. You upload your source code, and AWS will run it for you. The underlying compute infrastructure is abstracted away and you don’t have access to it any longer (e.g. no SSH possible). AWS provides you access to the logs your application generates and some metrics that you can use to debug problems of your application.</p><h2 id="Comparison"><a href="#Comparison" class="headerlink" title="Comparison"></a>Comparison</h2><p>When comparing the available options, you can take different perspectives. In this article series, you look at AWS from the velocity perspective. When your goal is to deliver fast, it is important to minimize the work that is not related to your goal: the application running in production. The table shows your responsibilities.</p><p>|  | EC2 based | Containerized | Serverless || --- | --- | --- || <strong>Operating system</strong> | you | you | AWS || <strong>Runtime</strong><br>e.g. Node.js, JVM | you | you | AWS || <strong>Web server</strong><br>(e.g. express, Apache, Nginx) | you | you | AWS || <strong>Deployment</strong> | you | AWS | AWS || <strong>Monitoring</strong> | AWS | AWS | AWS || <strong>Logging</strong> | you &amp; AWS | you &amp; AWS | AWS || <strong>High availability</strong> | you &amp; AWS | you &amp; AWS | AWS || <strong>Scalability</strong> | you &amp; AWS | you &amp; AWS | AWS || <strong>Alerting</strong> | you &amp; AWS | you &amp; AWS | you &amp; AWS || <strong>App source code</strong> | you | you | you |</p><p>It’s also important to look at the limitations:</p><table class="table table-striped table-responsive"><thead><tr><th></th><th>Limitations</th></tr></thead><tbody><tr><td><strong>EC2 bases app</strong></td><td>Linux or Windows</td></tr><tr><td><strong>Containerized app</strong></td><td>Docker</td></tr><tr><td><strong>Serverless app</strong></td><td>Node.js, Java/JVM, Python, C#<br>max 5 minute execution<br>Linux</td></tr></tbody></table><h2 id="Decision-time"><a href="#Decision-time" class="headerlink" title="Decision time"></a>Decision time</h2><p>Given the limitations I mentioned, I recommend that you pick the solution that minimizes your responsibilities and still fits your requirements. This should be an excellent starting point to achieve AWS Velocity.</p><p>The series continues with a deep dive into the three available options to deploy your application to AWS.</p><h2 id="Series"><a href="#Series" class="headerlink" title="Series"></a>Series</h2><p align="center"><img src="/images/2017/01/aws-velocity-cover.png" alt="AWS Velocity Cover" style="max-width: 349px"></p><ol><li><a href="/aws-velocity-set-the-assembly-line-up/">Set the assembly line up</a></li><li><a href="/aws-velocity-local-development-environment/">Local development environment</a></li><li><a href="/aws-velocity-ci-cd-pipeline-as-code/">CI&#x2F;CD Pipeline as Code</a></li><li><strong>Running your application</strong> (you are here)</li><li>EC2 based app<br>  a. <a href="/aws-velocity-ec2-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-ec2-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li>Containerized ECS based app<br>  a. <a href="/aws-velocity-containerized-ecs-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-containerized-ecs-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li><a href="/aws-velocity-serverless-app/">Serverless app</a></li><li><a href="/aws-velocity-summary/">Summary</a></li></ol><p>You can find the source code on <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">GitHub</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Velocity Series: CI/CD Pipeline as Code</title>
      <link>https://cloudonaut.io/aws-velocity-ci-cd-pipeline-as-code/</link>
      <description>
        <![CDATA[<p>The Continuous Integration &#x2F; Continuous Deployment pipeline is a major section of your software assembly line. It starts with the co]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/continuous-delivery/">Continuous Delivery</category>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <category domain="https://cloudonaut.io/tag/codepipeline/">codepipeline</category>
      <category domain="https://cloudonaut.io/tag/codecommit/">codecommit</category>
      <category domain="https://cloudonaut.io/tag/velocity/">velocity</category>
      <category domain="https://cloudonaut.io/tag/codebuild/">codebuild</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-velocity-ci-cd-pipeline-as-code/</guid>
      <pubDate>Tue, 14 Feb 2017 09:05:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The Continuous Integration &#x2F; Continuous Deployment pipeline is a major section of your software assembly line. It starts with the code repository and ends with the deployment into your production environment. CI&#x2F;CD includes many steps that all depend on each other. That’s why this is a valuable area for automation.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/02/aws-velocity-ci-cd@730w.webp 730w, /images/2017/02/aws-velocity-ci-cd@730w2x.webp 1460w, /images/2017/02/aws-velocity-ci-cd@610w.webp 610w, /images/2017/02/aws-velocity-ci-cd@610w2x.webp 1220w, /images/2017/02/aws-velocity-ci-cd@450w.webp 450w, /images/2017/02/aws-velocity-ci-cd@450w2x.webp 900w, /images/2017/02/aws-velocity-ci-cd@330w.webp 330w, /images/2017/02/aws-velocity-ci-cd@330w2x.webp 660w, /images/2017/02/aws-velocity-ci-cd@545w.webp 545w, /images/2017/02/aws-velocity-ci-cd@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/02/aws-velocity-ci-cd@730w.png 730w, /images/2017/02/aws-velocity-ci-cd@730w2x.png 1460w, /images/2017/02/aws-velocity-ci-cd@610w.png 610w, /images/2017/02/aws-velocity-ci-cd@610w2x.png 1220w, /images/2017/02/aws-velocity-ci-cd@450w.png 450w, /images/2017/02/aws-velocity-ci-cd@450w2x.png 900w, /images/2017/02/aws-velocity-ci-cd@330w.png 330w, /images/2017/02/aws-velocity-ci-cd@330w2x.png 660w, /images/2017/02/aws-velocity-ci-cd@545w.png 545w, /images/2017/02/aws-velocity-ci-cd@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/02/aws-velocity-ci-cd.png" alt="AWS Velocity: CI/CD in the software assembly line" title="AWS Velocity: CI/CD in the software assembly line"></picture></p><p>The CI&#x2F;CD pipeline does the following:</p><ol><li>runs on every commit into your repository</li><li>updates itself if needed</li><li>builds the source code</li><li>executes unit tests</li><li>packages the application as an artifact</li><li>creates or updates the acceptance environment if needed</li><li>deploys the artifact into the acceptance environment</li><li>executes acceptance tests against the acceptance environment</li><li>creates or updates the production environment if needed</li><li>deploys the artifact into the production environment</li></ol><p>That’s a lot of work. Luckily AWS can help us out.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@730w.webp 730w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@730w2x.webp 1460w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@610w.webp 610w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@610w2x.webp 1220w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@450w.webp 450w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@450w2x.webp 900w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@330w.webp 330w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@330w2x.webp 660w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@545w.webp 545w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@730w.png 730w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@730w2x.png 1460w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@610w.png 610w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@610w2x.png 1220w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@450w.png 450w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@450w2x.png 900w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@330w.png 330w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@330w2x.png 660w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@545w.png 545w, /images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/02/aws-velocity-ci-cd-mapped-to-aws-services.png" alt="AWS Velocity: CI/CD in the software assembly line" title="AWS Velocity: CI/CD in the software assembly line"></picture></p><ul><li><a href="https://aws.amazon.com/codecommit/" target="_blank" rel="noopener">AWS CodeCommit</a> is a managed source code repository service.</li><li><a href="https://aws.amazon.com/codebuild/" target="_blank" rel="noopener">AWS CodeBuild</a> is a fully managed build server that can run anything you need to build and deploy your application within a container.</li><li><a href="https://aws.amazon.com/cloudformation/" target="_blank" rel="noopener">AWS CloudFormation</a> is the Infrastructure as Code service from AWS that can convert YAML or JSON templates into running infrastructure stacks.</li><li><a href="https://aws.amazon.com/codepipeline/" target="_blank" rel="noopener">AWS CodePipeline</a> is a managed service that glues all the above services together in a pipeline.</li></ul><p>So what is your job? </p><ol><li>track your source code in a repository</li><li>describe your pipeline as code</li><li>write a script to build, test, and package application</li><li>write a script to build, test, and package acceptance test</li><li>describe your infrastructure as code</li></ol><p>In this article, you will learn how to perform steps 1 to 4. The rest of this series focuses on step 5. In the end, you will be able to push an application to production without any manual work. That’s enough motivation to continue? </p><div class="well">  <div class="media">    <div class="media-left">      <a href="/aws-velocity-series/">        <img class="media-object raw" src="/images/2017/04/aws-velocity-cover-icon.png" alt="AWS Velocity Cover">      </a>    </div>    <div class="media-body">      <h4 class="media-heading">AWS Velocity Series</h4>      <p>Most of our clients use AWS to reduce time-to-market following an agile approach. But AWS is only one part of the solution. In this article series, I show you how we help our clients to improve velocity: the time from idea to production. <a href="/aws-velocity-series/">Discover all posts!</a></p>    </div>  </div></div><p>Let’s get started.</p><h2 id="Setting-up-the-git-repository"><a href="#Setting-up-the-git-repository" class="headerlink" title="Setting up the git repository"></a>Setting up the git repository</h2><p>To create an AWS CodeCommit git repository, make sure you have the <a href="https://aws.amazon.com/cli/" target="_blank" rel="noopener">AWS CLI</a> installed. Execute the following command in your terminal:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> AWS_DEFAULT_REGION=eu-west-1</span><br><span class="line">aws codecommit create-repository --repository-name aws-velocity</span><br><span class="line"><span class="comment">#&#123;</span></span><br><span class="line"><span class="comment">#    &quot;repositoryMetadata&quot;: &#123;</span></span><br><span class="line"><span class="comment">#        &quot;repositoryName&quot;: &quot;aws-velocity&quot;, </span></span><br><span class="line"><span class="comment">#        &quot;cloneUrlSsh&quot;: &quot;ssh://git-codecommit.eu-west-1.amazonaws.com/v1/repos/aws-velocity&quot;, </span></span><br><span class="line"><span class="comment">#        &quot;lastModifiedDate&quot;: 1486450175.193, </span></span><br><span class="line"><span class="comment">#        &quot;repositoryId&quot;: &quot;11c6b1ec-95bb-4925-84ac-da9695ac6031&quot;, </span></span><br><span class="line"><span class="comment">#        &quot;cloneUrlHttp&quot;: &quot;https://git-codecommit.eu-west-1.amazonaws.com/v1/repos/aws-velocity&quot;, </span></span><br><span class="line"><span class="comment">#        &quot;creationDate&quot;: 1486450175.193, </span></span><br><span class="line"><span class="comment">#        &quot;Arn&quot;: &quot;arn:aws:codecommit:eu-west-1:163732473262:aws-velocity&quot;, </span></span><br><span class="line"><span class="comment">#        &quot;accountId&quot;: &quot;163732473262&quot;</span></span><br><span class="line"><span class="comment">#    &#125;</span></span><br><span class="line"><span class="comment">#&#125;</span></span><br></pre></td></tr></table></figure><p>If you use AWS CodeCommit the first time, you need to upload your public SSH key to your IAM user and make sure that the IAM user has access right to CodeCommit. You can grant access to CodeCommit using the managed policy <code>AWSCodeCommitPowerUser</code>.</p><ol><li>Open the <a href="https://console.aws.amazon.com/iam/home#/users" target="_blank" rel="noopener">IAM Dashboard</a></li><li>Click on your user</li><li>Select the <strong>Security Credentials</strong> tab</li><li>Click the gray <strong>Upload SSH public key</strong> button</li><li>Insert your public key (mine is located at <code>~/.ssh/id_rsa.pub</code>)</li><li>Click the blue <strong>Upload SSH public key</strong> button</li><li>Copy the <strong>SSH key ID</strong> of your uploaded public key (e.g. <code>ASFKAAPNGA66RIIWSYMQ</code>).</li><li>Select the <strong>Permissions</strong> tab</li><li>Click the blue <strong>Add permissions</strong> button</li><li>Select <strong>Attach existing policies directly</strong></li><li>Search for <code>AWSCodeCommitPowerUser</code> in the table</li><li>Select <strong>AWSCodeCommitPowerUser</strong></li><li>Click the blue <strong>Next: preview</strong> button</li><li>Confirm by clicking the blue <strong>Add permissions</strong> button</li></ol><p>Now you initialize the git repository locally and push your changes to CodeCommit.</p><ol><li>Make sure that you are in the project folder <code>aws-velocity</code> that you created in the <a href="/aws-velocity-local-development-environment/">previous part of the series</a>.</li><li>Run <code>git init</code></li><li>Run <code>echo &quot;node_modules/&quot; &gt; .gitignore</code></li><li>Replace <code>$YourSshKeyId</code> with your SSH key ID and run</li></ol><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line">git remote <span class="keyword">add</span><span class="language-bash"> origin ssh://<span class="variable">$YourSshKeyId</span>@git-codecommit.eu-west-1.amazonaws.com/v1/repos/aws-velocity</span></span><br></pre></td></tr></table></figure><ol><li>Run <code>git add -A</code></li><li>Run <code>git commit -m &#39;initial commit&#39;</code></li><li>Run <code>git push -u origin master</code></li></ol><p>The source code is now available in AWS CodeCommit.</p><h2 id="Describing-the-pipeline-as-code"><a href="#Describing-the-pipeline-as-code" class="headerlink" title="Describing the pipeline as code"></a>Describing the pipeline as code</h2><p>AWS CloudFormation is the infrastructure as code service on AWS. You can also use CloudFormation to describe a pipeline. CloudFormation is based on templates in YAML or JSON. You will use YAML in the following example. The template has ~200 lines. I will present the template in pieces that you need to copy together to get the running version. Or you can download the file on <a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline.yml" target="_blank" rel="noopener">GitHub</a>.</p><p>The first part of the template describes some parameters that make the template reusable. It also contains the S3 bucket that is used to store the compressed artifacts. In the <code>deploy</code> folder, create a file <code>pipeline.yml</code> with the following content:</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&#x27;Pipeline&#x27;</span></span><br><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">RepositoryName:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">    <span class="attr">Default:</span> <span class="string">&#x27;aws-velocity&#x27;</span></span><br><span class="line">  <span class="attr">BranchName:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">    <span class="attr">Default:</span> <span class="string">&#x27;master&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">ArtifactsBucket:</span></span><br><span class="line">    <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">    <span class="attr">DeletionPolicy:</span> <span class="string">Retain</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::S3::Bucket&#x27;</span></span><br></pre></td></tr></table></figure><p>You also need some IAM roles to allow CodePipeline, CloudFormation, and CodeBuild to access your account. Add the following content to <code>deploy/pipeline.yml</code>:</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">PipelineRole:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">      <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">      <span class="attr">Statement:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">        <span class="attr">Principal:</span></span><br><span class="line">          <span class="attr">Service:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;codepipeline.amazonaws.com&#x27;</span></span><br><span class="line">        <span class="attr">Action:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    <span class="attr">ManagedPolicyArns:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;arn:aws:iam::aws:policy/AdministratorAccess&#x27;</span></span><br><span class="line"><span class="attr">CloudFormationRole:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">      <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">      <span class="attr">Statement:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">        <span class="attr">Principal:</span></span><br><span class="line">          <span class="attr">Service:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;cloudformation.amazonaws.com&#x27;</span></span><br><span class="line">        <span class="attr">Action:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    <span class="attr">ManagedPolicyArns:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">&#x27;arn:aws:iam::aws:policy/AdministratorAccess&#x27;</span></span><br><span class="line"><span class="attr">CodeBuildRole:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">      <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">      <span class="attr">Statement:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">        <span class="attr">Principal:</span></span><br><span class="line">          <span class="attr">Service:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;codebuild.amazonaws.com&#x27;</span></span><br><span class="line">        <span class="attr">Action:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">    <span class="attr">Policies:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">ServiceRole</span></span><br><span class="line">      <span class="attr">PolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">CloudWatchLogsPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogGroup&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:CreateLogStream&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;logs:PutLogEvents&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">CodeCommitPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;codecommit:GitPull&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">S3GetObjectPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;s3:GetObject&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;s3:GetObjectVersion&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sid:</span> <span class="string">S3PutObjectPolicy</span></span><br><span class="line">          <span class="attr">Effect:</span> <span class="string">&#x27;Allow&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;s3:PutObject&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br></pre></td></tr></table></figure><p>You will use CodeBuild to build, test and package the app and the acceptance test. This part also includes the scripts that are run when building, testing, and packaging your app. Add the following content to <code>deploy/pipeline.yml</code>:</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">AppProject:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodeBuild::Project&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Artifacts:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">    <span class="attr">Environment:</span></span><br><span class="line">      <span class="attr">ComputeType:</span> <span class="string">&#x27;BUILD_GENERAL1_SMALL&#x27;</span></span><br><span class="line">      <span class="attr">Image:</span> <span class="string">&#x27;aws/codebuild/nodejs:6.3.1&#x27;</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">&#x27;LINUX_CONTAINER&#x27;</span></span><br><span class="line">    <span class="attr">Name:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-app&#x27;</span></span><br><span class="line">    <span class="attr">ServiceRole:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CodeBuildRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">Source:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">      <span class="attr">BuildSpec:</span> <span class="string">|</span></span><br><span class="line"><span class="string">        version: 0.1</span></span><br><span class="line"><span class="string">        phases:</span></span><br><span class="line"><span class="string">          build:</span></span><br><span class="line"><span class="string">            commands:</span></span><br><span class="line"><span class="string">            - &#x27;cd app/ &amp;&amp; npm install&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;cd app/ &amp;&amp; npm test&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;rm -rf app/node_modules/&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;rm -rf app/test/&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;cd app/ &amp;&amp; npm install --production&#x27;</span></span><br><span class="line"><span class="string">        artifacts:</span></span><br><span class="line"><span class="string">          files:</span></span><br><span class="line"><span class="string">          - &#x27;app/**/*&#x27;</span></span><br><span class="line"><span class="string"></span>    <span class="attr">TimeoutInMinutes:</span> <span class="number">10</span></span><br><span class="line"><span class="attr">AcceptanceProject:</span></span><br><span class="line">  <span class="attr">DependsOn:</span> <span class="string">CloudFormationRole</span> <span class="comment"># make sure that CloudFormationRole is deleted last</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodeBuild::Project&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Artifacts:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">    <span class="attr">Environment:</span></span><br><span class="line">      <span class="attr">ComputeType:</span> <span class="string">&#x27;BUILD_GENERAL1_SMALL&#x27;</span></span><br><span class="line">      <span class="attr">Image:</span> <span class="string">&#x27;aws/codebuild/nodejs:6.3.1&#x27;</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">&#x27;LINUX_CONTAINER&#x27;</span></span><br><span class="line">    <span class="attr">Name:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::StackName&#125;-acceptance&#x27;</span></span><br><span class="line">    <span class="attr">ServiceRole:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CodeBuildRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">Source:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">CODEPIPELINE</span></span><br><span class="line">      <span class="attr">BuildSpec:</span> <span class="string">|</span></span><br><span class="line"><span class="string">        version: 0.1</span></span><br><span class="line"><span class="string">        phases:</span></span><br><span class="line"><span class="string">          build:</span></span><br><span class="line"><span class="string">            commands:</span></span><br><span class="line"><span class="string">            - &#x27;cd acceptance/ &amp;&amp; npm install&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;cd acceptance/ &amp;&amp; npm test&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;rm -rf acceptance/node_modules/&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;rm -rf acceptance/test/&#x27;</span></span><br><span class="line"><span class="string">            - &#x27;cd acceptance/ &amp;&amp; npm install --production&#x27;</span></span><br><span class="line"><span class="string">        artifacts:</span></span><br><span class="line"><span class="string">          files:</span></span><br><span class="line"><span class="string">          - &#x27;acceptance/**/*&#x27;</span></span><br><span class="line"><span class="string"></span>    <span class="attr">TimeoutInMinutes:</span> <span class="number">10</span></span><br></pre></td></tr></table></figure><p>And finally the CodePipeline pipeline is described. The pipeline connects the dots. From repository, to pipeline update, to triggering the CodeBuild projects. Add the following content to <code>deploy/pipeline.yml</code>:</p><figure class="highlight yaml"><figcaption><span>deploy&#x2F;pipeline.yml</span><a href="https://github.com/widdix/aws-velocity/blob/master/deploy/pipeline.yml">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">Pipeline:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&#x27;AWS::CodePipeline::Pipeline&#x27;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">ArtifactStore:</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">S3</span></span><br><span class="line">      <span class="attr">Location:</span> <span class="type">!Ref</span> <span class="string">ArtifactsBucket</span></span><br><span class="line">    <span class="attr">Name:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line">    <span class="attr">RestartExecutionOnUpdate:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;PipelineRole.Arn&#x27;</span></span><br><span class="line">    <span class="attr">Stages:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">      <span class="attr">Actions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">FetchSource</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Source</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeCommit</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">RepositoryName:</span> <span class="type">!Ref</span> <span class="string">RepositoryName</span></span><br><span class="line">          <span class="attr">BranchName:</span> <span class="type">!Ref</span> <span class="string">BranchName</span></span><br><span class="line">        <span class="attr">OutputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Pipeline</span></span><br><span class="line">      <span class="attr">Actions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">DeployPipeline</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Deploy</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CloudFormation</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ActionMode:</span> <span class="string">CREATE_UPDATE</span></span><br><span class="line">          <span class="attr">Capabilities:</span> <span class="string">CAPABILITY_IAM</span></span><br><span class="line">          <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;CloudFormationRole.Arn&#x27;</span></span><br><span class="line">          <span class="attr">StackName:</span> <span class="type">!Ref</span> <span class="string">&#x27;AWS::StackName&#x27;</span></span><br><span class="line">          <span class="attr">TemplatePath:</span> <span class="string">&#x27;Source::deploy/pipeline.yml&#x27;</span></span><br><span class="line">          <span class="attr">ParameterOverrides:</span> <span class="type">!Sub</span> <span class="string">&#x27;&#123;&quot;RepositoryName&quot;: &quot;$&#123;RepositoryName&#125;&quot;, &quot;BranchName&quot;: &quot;$&#123;BranchName&#125;&quot;&#125;&#x27;</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Build</span></span><br><span class="line">      <span class="attr">Actions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">BuildAndTestApp</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">AppProject</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="attr">OutputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">App</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">BuildAndTestAcceptance</span></span><br><span class="line">        <span class="attr">ActionTypeId:</span></span><br><span class="line">          <span class="attr">Category:</span> <span class="string">Build</span></span><br><span class="line">          <span class="attr">Owner:</span> <span class="string">AWS</span></span><br><span class="line">          <span class="attr">Provider:</span> <span class="string">CodeBuild</span></span><br><span class="line">          <span class="attr">Version:</span> <span class="number">1</span></span><br><span class="line">        <span class="attr">Configuration:</span></span><br><span class="line">          <span class="attr">ProjectName:</span> <span class="type">!Ref</span> <span class="string">AcceptanceProject</span></span><br><span class="line">        <span class="attr">InputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="attr">OutputArtifacts:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Acceptance</span></span><br><span class="line">        <span class="attr">RunOrder:</span> <span class="number">1</span></span><br></pre></td></tr></table></figure><p>The Continuous Integration part of the pipeline is now done.</p><p>Make sure to commit the changes:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add -A</span><br><span class="line">git commit -m <span class="string">&#x27;added pipeline template&#x27;</span></span><br><span class="line">git push</span><br></pre></td></tr></table></figure><p>Now it’s time to create the pipeline. You only need to execute this command once; the pipeline will update itself in the future.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws cloudformation create-stack --stack-name aws-velocity-pipeline --template-body file://deploy/pipeline.yml --capabilities CAPABILITY_IAM</span><br></pre></td></tr></table></figure><p>Creating the CloudFormation stack that contains the pipeline will take some time. Use the following to command to wait until the stack was created::</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">aws cloudformation <span class="built_in">wait</span> stack-create-complete --stack-name aws-velocity-pipeline</span><br></pre></td></tr></table></figure><p>Open the <a href="https://eu-west-1.console.aws.amazon.com/codepipeline/" target="_blank" rel="noopener">AWS CodePipeline Dashboard</a>, select the <code>aws-velocity-pipeline</code> pipeline and wait until all boxes are green.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/02/aws-velocity-pipeline@730w.webp 730w, /images/2017/02/aws-velocity-pipeline@730w2x.webp 1460w, /images/2017/02/aws-velocity-pipeline@610w.webp 610w, /images/2017/02/aws-velocity-pipeline@610w2x.webp 1220w, /images/2017/02/aws-velocity-pipeline@450w.webp 450w, /images/2017/02/aws-velocity-pipeline@450w2x.webp 900w, /images/2017/02/aws-velocity-pipeline@330w.webp 330w, /images/2017/02/aws-velocity-pipeline@330w2x.webp 660w, /images/2017/02/aws-velocity-pipeline@545w.webp 545w, /images/2017/02/aws-velocity-pipeline@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/02/aws-velocity-pipeline@730w.png 730w, /images/2017/02/aws-velocity-pipeline@730w2x.png 1460w, /images/2017/02/aws-velocity-pipeline@610w.png 610w, /images/2017/02/aws-velocity-pipeline@610w2x.png 1220w, /images/2017/02/aws-velocity-pipeline@450w.png 450w, /images/2017/02/aws-velocity-pipeline@450w2x.png 900w, /images/2017/02/aws-velocity-pipeline@330w.png 330w, /images/2017/02/aws-velocity-pipeline@330w2x.png 660w, /images/2017/02/aws-velocity-pipeline@545w.png 545w, /images/2017/02/aws-velocity-pipeline@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/02/aws-velocity-pipeline.png" alt="Continuous Integration pipeline" title="Continuous Integration pipeline"></picture></p><p>Now, the Continuous Integration part of the pipeline is done. You can find a copy of the running app in the S3 bucket that starts with <code>aws-velocity-pipeline-artifactsbucket-*</code> under <code>aws-velocity-pipelin/App</code>. Download the compressed file, unzip it, and you will find a copy of your app.</p><h2 id="Describing-the-infrastructure-as-code"><a href="#Describing-the-infrastructure-as-code" class="headerlink" title="Describing the infrastructure as code"></a>Describing the infrastructure as code</h2><p>In the rest of this series, you will learn how to describe the infrastructure as code. Depending on your target platform things vary. You can choose from:</p><ul><li>EC2 based app that runs in an Auto Scaling Group</li><li>Containerized app that runs on ECS</li><li>Serverless app</li></ul><blockquote><p>All resources that you created are part of the non-expiring AWS Free Tier. If you have no other pipelines running and do not use more than 100 minutes of build time, you will not be charged by AWS.<br>You can cleanup all the resources at the very end of this series (coming soon)</p></blockquote><h2 id="Series"><a href="#Series" class="headerlink" title="Series"></a>Series</h2><p align="center"><img src="/images/2017/01/aws-velocity-cover.png" alt="AWS Velocity Cover" style="max-width: 349px"></p><ol><li><a href="/aws-velocity-set-the-assembly-line-up/">Set the assembly line up</a></li><li><a href="/aws-velocity-local-development-environment/">Local development environment</a></li><li><strong>CI&#x2F;CD Pipeline as Code</strong> (you are here)</li><li><a href="/aws-velocity-running-your-application/">Running your application</a></li><li>EC2 based app<br>  a. <a href="/aws-velocity-ec2-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-ec2-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li>Containerized ECS based app<br>  a. <a href="/aws-velocity-containerized-ecs-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-containerized-ecs-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li><a href="/aws-velocity-serverless-app/">Serverless app</a></li><li><a href="/aws-velocity-summary/">Summary</a></li></ol><p>You can find the source code on <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">GitHub</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Free ebook: Exploring Cloud Computing</title>
      <link>https://cloudonaut.io/free-ebook-exploring-cloud-computing/</link>
      <description>
        <![CDATA[<p><em>Exploring Cloud Computing</em> is a collection of hand-picked chapters presenting five topics that will give you insights into the wo]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/announcement/">announcement</category>
      <guid isPermaLink="true">https://cloudonaut.io/free-ebook-exploring-cloud-computing/</guid>
      <pubDate>Sat, 11 Feb 2017 13:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p><em>Exploring Cloud Computing</em> is a collection of hand-picked chapters presenting five topics that will give you insights into the world of cloud computing. Michael and I, authors of <a href="http://bit.ly/amazon-web-services-in-action" target="_blank" rel="noopener">Amazon Web Services in Action</a>, selected these particular topics allowing you to explore <em>Cloud Computing</em>.</p><p style="margin-top: 2em; margin-bottom: 2em; text-align: center;"><a href="https://www.manning.com/books/exploring-cloud-computing"><img src="/images/2017/02/exploring-cloud-computing.png" alt="Exploring Cloud Computing" style="max-width: 400px"></a></p><p><a href="https://www.manning.com/books/exploring-cloud-computing" target="_blank" rel="noopener"><strong>Get a free copy now!</strong></a></p><p>The ebook contains the following chapters:</p><ul><li>What is Cloud Computing? from <a href="http://bit.ly/the-cloud-at-your-service" target="_blank" rel="noopener">The Cloud at Your Service</a></li><li>What is Amazon Web Services? from <a href="http://bit.ly/amazon-web-services-in-action" target="_blank" rel="noopener">Amazon Web Services in Action</a></li><li>Trying it out: Deploying Wordpress on <a href="http://bit.ly/google-cloud-platform-in-action" target="_blank" rel="noopener">Google Cloud from Google Cloud Platform in Action</a></li><li>Going Serverless from <a href="http://bit.ly/serverless-architectures-on-aws" target="_blank" rel="noopener">Serverless Architectures on AWS</a></li><li>Running functions in the Cloud from <a href="http://bit.ly/aws-lambda-in-action" target="_blank" rel="noopener">AWS Lambda in Action</a></li></ul><p><a href="https://www.manning.com/books/exploring-cloud-computing" target="_blank" rel="noopener"><strong>Get a free copy now.</strong></a> Happy reading!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Velocity Series: Local development environment</title>
      <link>https://cloudonaut.io/aws-velocity-local-development-environment/</link>
      <description>
        <![CDATA[<p>The local development environment is where you make changes to the source code, edit configuration files, add images, and so on. You also]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/velocity/">velocity</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-velocity-local-development-environment/</guid>
      <pubDate>Wed, 08 Feb 2017 09:05:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The local development environment is where you make changes to the source code, edit configuration files, add images, and so on. You also want to run the app locally, execute the tests, and be able to debug your source code.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/02/aws-velocity-local-dev-env@730w.webp 730w, /images/2017/02/aws-velocity-local-dev-env@730w2x.webp 1460w, /images/2017/02/aws-velocity-local-dev-env@610w.webp 610w, /images/2017/02/aws-velocity-local-dev-env@610w2x.webp 1220w, /images/2017/02/aws-velocity-local-dev-env@450w.webp 450w, /images/2017/02/aws-velocity-local-dev-env@450w2x.webp 900w, /images/2017/02/aws-velocity-local-dev-env@330w.webp 330w, /images/2017/02/aws-velocity-local-dev-env@330w2x.webp 660w, /images/2017/02/aws-velocity-local-dev-env@545w.webp 545w, /images/2017/02/aws-velocity-local-dev-env@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/02/aws-velocity-local-dev-env@730w.png 730w, /images/2017/02/aws-velocity-local-dev-env@730w2x.png 1460w, /images/2017/02/aws-velocity-local-dev-env@610w.png 610w, /images/2017/02/aws-velocity-local-dev-env@610w2x.png 1220w, /images/2017/02/aws-velocity-local-dev-env@450w.png 450w, /images/2017/02/aws-velocity-local-dev-env@450w2x.png 900w, /images/2017/02/aws-velocity-local-dev-env@330w.png 330w, /images/2017/02/aws-velocity-local-dev-env@330w2x.png 660w, /images/2017/02/aws-velocity-local-dev-env@545w.png 545w, /images/2017/02/aws-velocity-local-dev-env@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/02/aws-velocity-local-dev-env.png" alt="AWS Velocity: Local development environment" title="AWS Velocity: Local development environment"></picture></p><p>In this article you will learn how to setup a Node.js project from scratch with:</p><ul><li>Unit tests to cover internal functionality</li><li>Code checks to get rid of typos and code smells early</li><li>Acceptance tests to verify the end-user behavior</li></ul><p>The Node.js app will offer an HTTP API to compute factorials. The concepts can be applied to any other programming language.</p><div class="well">  <div class="media">    <div class="media-left">      <a href="/aws-velocity-series/">        <img class="media-object raw" src="/images/2017/04/aws-velocity-cover-icon.png" alt="AWS Velocity Cover">      </a>    </div>    <div class="media-body">      <h4 class="media-heading">AWS Velocity Series</h4>      <p>Most of our clients use AWS to reduce time-to-market following an agile approach. But AWS is only one part of the solution. In this article series, I show you how we help our clients to improve velocity: the time from idea to production. <a href="/aws-velocity-series/">Discover all posts!</a></p>    </div>  </div></div><p>Let’s get started!</p><h2 id="Setup-project"><a href="#Setup-project" class="headerlink" title="Setup project"></a>Setup project</h2><blockquote><p>You can follow step by step or get the full source code here: <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">https://github.com/widdix/aws-velocity</a></p></blockquote><p>First, create a folder for the new project. I assume that you use macOS or Linux running a terminal:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> aws-velocity</span><br><span class="line"><span class="built_in">cd</span> aws-velocity/</span><br></pre></td></tr></table></figure><p>Within the new project folder, create folders for your app, infrastructure, deploy scripts, and your acceptance tests.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> app</span><br><span class="line"><span class="built_in">mkdir</span> infrastructure</span><br><span class="line"><span class="built_in">mkdir</span> deploy</span><br><span class="line"><span class="built_in">mkdir</span> acceptance</span><br></pre></td></tr></table></figure><p>The project structure should look like this now:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">tree ../aws-velocity/</span><br><span class="line"><span class="comment">#../aws-velocity/</span></span><br><span class="line"><span class="comment">#├── acceptance</span></span><br><span class="line"><span class="comment">#├── app</span></span><br><span class="line"><span class="comment">#├── deploy</span></span><br><span class="line"><span class="comment">#└── infrastructure</span></span><br><span class="line"><span class="comment">#4 directories, 0 files</span></span><br></pre></td></tr></table></figure><p>Let’s develop the app next.</p><h3 id="A-simple-app"><a href="#A-simple-app" class="headerlink" title="A simple app"></a>A simple app</h3><p>Now you need a simple app that you can use as an example. The app will be based on Node.js but the concepts will apply to all other programming languages. If you don’t have Node.js installed, visit <a href="https://nodejs.org/en" target="_blank" rel="noopener">nodejs.org</a>.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> app/</span><br><span class="line"><span class="built_in">mkdir</span> <span class="built_in">test</span></span><br><span class="line"><span class="built_in">mkdir</span> lib</span><br></pre></td></tr></table></figure><p>Now, create the app structure with the <code>npm init</code> tool. The tool will ask you a bunch of questions, make sure to answer them as I did:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm init</span><br><span class="line"><span class="comment"># name: (app) factorial</span></span><br><span class="line"><span class="comment"># version: (1.0.0) 1.0.0</span></span><br><span class="line"><span class="comment"># description: Factorial as a Service</span></span><br><span class="line"><span class="comment"># entry point: (index.js) index.js</span></span><br><span class="line"><span class="comment"># test command: ./node_modules/.bin/jshint . &amp;&amp; ./node_modules/.bin/mocha test/*.js</span></span><br><span class="line"><span class="comment"># git repository: </span></span><br><span class="line"><span class="comment"># keywords: </span></span><br><span class="line"><span class="comment"># author: </span></span><br><span class="line"><span class="comment"># license: (ISC) </span></span><br><span class="line"><span class="comment"># About to write to /Users/michael/Desktop/aws-velocity/app/package.json:</span></span><br><span class="line"><span class="comment"># &#123;</span></span><br><span class="line"><span class="comment">#   &quot;name&quot;: &quot;factorial&quot;,</span></span><br><span class="line"><span class="comment">#   &quot;version&quot;: &quot;1.0.0&quot;,</span></span><br><span class="line"><span class="comment">#   &quot;description&quot;: &quot;Factorial as a Service&quot;,</span></span><br><span class="line"><span class="comment">#   &quot;main&quot;: &quot;index.js&quot;,</span></span><br><span class="line"><span class="comment">#   &quot;directories&quot;: &#123;</span></span><br><span class="line"><span class="comment">#     &quot;test&quot;: &quot;test&quot;</span></span><br><span class="line"><span class="comment">#   &#125;,</span></span><br><span class="line"><span class="comment">#   &quot;scripts&quot;: &#123;</span></span><br><span class="line"><span class="comment">#     &quot;test&quot;: &quot;jshint . &amp;&amp; ./node_modules/.bin/mocha test/*.js&quot;</span></span><br><span class="line"><span class="comment">#   &#125;,</span></span><br><span class="line"><span class="comment">#   &quot;author&quot;: &quot;&quot;,</span></span><br><span class="line"><span class="comment">#   &quot;license&quot;: &quot;ISC&quot;</span></span><br><span class="line"><span class="comment"># &#125;</span></span><br><span class="line"><span class="comment"># Is this ok? (yes) yes</span></span><br></pre></td></tr></table></figure><p>And you need to install a few dependencies. <code>express</code> is a popular web framework, <code>jshint</code> is a code quality tool, and <code>mocha</code> is a test framework:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm install express@4.14.0 --save</span><br><span class="line">npm install jshint@2.9.4 --save-dev</span><br><span class="line">npm install mocha@3.2.0 --save-dev</span><br></pre></td></tr></table></figure><p><code>jshint</code> needs a little bit of configuration. Create a file <code>.jshintrc</code> with the following content:</p><figure class="highlight json"><figcaption><span>app&#x2F;.jshintrc</span><a href="https://github.com/widdix/aws-velocity/blob/master/app/.jshintrc">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;esversion&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;node&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Create a file <code>test/.jshintrc</code> with the following content:</p><figure class="highlight json"><figcaption><span>app&#x2F;test&#x2F;.jshintrc</span><a href="https://github.com/widdix/aws-velocity/blob/master/app/test/.jshintrc">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;extends&quot;</span><span class="punctuation">:</span> <span class="string">&quot;../.jshintrc&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;mocha&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Create a file <code>.jshintignore</code> with the following content:</p><figure class="highlight markdown"><figcaption><span>app&#x2F;.jshintignore</span><a href="https://github.com/widdix/aws-velocity/blob/master/app/.jshintignore">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line">node<span class="emphasis">_modules/<span class="strong">**</span></span></span><br></pre></td></tr></table></figure><p>Now the app structure is done. Let’s see if the test command works:</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">npm test</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">&gt; factorial@1.0.0 <span class="built_in">test</span> /Users/michael/Desktop/aws-velocity/app</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">&gt; jshint . &amp;&amp; ./node_modules/.bin/mocha <span class="built_in">test</span>/*.js</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"></span></span><br><span class="line"><span class="language-bash"><span class="comment"># Warning: Could not find any test files matching pattern: test/*.js</span></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">No <span class="built_in">test</span> files found</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">npm ERR! Test failed.  See above <span class="keyword">for</span> more details.</span></span><br></pre></td></tr></table></figure><p>The tests fail because there are no tests. So let’s add some unit tests for the factorial implementation.</p><p>Create a file <code>test/factorial.js</code> with the following content. The structure of the file is determined by the test framework <code>mocha</code>. The important lines start with <code>assert.equal</code>. Those lines contain actual test conditions that the implementation must satisfy:</p><figure class="highlight javascript"><figcaption><span>app&#x2F;test&#x2F;factorial.js</span><a href="https://github.com/widdix/aws-velocity/blob/master/app/test/factorial.js">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> factorial = <span class="built_in">require</span>(<span class="string">&#x27;../lib/factorial.js&#x27;</span>);</span><br><span class="line"><span class="keyword">var</span> assert = <span class="built_in">require</span>(<span class="string">&#x27;assert&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;factorial&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;should fail for &lt; 0&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    assert.<span class="title function_">throws</span>(<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="title function_">factorial</span>(-<span class="number">1</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;should return 1 for 0&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    assert.<span class="title function_">equal</span>(<span class="title function_">factorial</span>(<span class="number">0</span>), <span class="number">1</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;should return 1 for 1&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    assert.<span class="title function_">equal</span>(<span class="title function_">factorial</span>(<span class="number">1</span>), <span class="number">1</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;should return 2 for 2&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    assert.<span class="title function_">equal</span>(<span class="title function_">factorial</span>(<span class="number">2</span>), <span class="number">2</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;should return 6 for 3&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    assert.<span class="title function_">equal</span>(<span class="title function_">factorial</span>(<span class="number">3</span>), <span class="number">6</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;should return 24 for 4&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    assert.<span class="title function_">equal</span>(<span class="title function_">factorial</span>(<span class="number">4</span>), <span class="number">24</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;should return 120 for 5&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    assert.<span class="title function_">equal</span>(<span class="title function_">factorial</span>(<span class="number">5</span>), <span class="number">120</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;should return 87178291200 for 14&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    assert.<span class="title function_">equal</span>(<span class="title function_">factorial</span>(<span class="number">14</span>), <span class="number">87178291200</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;should fail for &gt; 14&#x27;</span>, <span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">    assert.<span class="title function_">throws</span>(<span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">      <span class="title function_">factorial</span>(<span class="number">15</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>If you run <code>npm test</code> again, the test still fail because there is no implementation yet. Let’s change that.</p><p>Create a file <code>lib/factorial.js</code> with the following content. The <code>factorial</code> implementation is recursive:</p><figure class="highlight javascript"><figcaption><span>app&#x2F;lib&#x2F;factorial.js</span><a href="https://github.com/widdix/aws-velocity/blob/master/app/lib/factorial.js">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">factorial</span>(<span class="params">n</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (n === <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> n * <span class="title function_">factorial</span>(n - <span class="number">1</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = factorial;</span><br></pre></td></tr></table></figure><p>Let’s see if the tests pass now by running <code>npm test</code>.</p><p>Oh no, the implementation is missing some checks for edge cases. Change the file <code>lib/factorial.js</code> accordingly:</p><figure class="highlight javascript"><figcaption><span>app&#x2F;lib&#x2F;factorial.js</span><a href="https://github.com/widdix/aws-velocity/blob/master/app/lib/factorial.js">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">factorial</span>(<span class="params">n</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (n &lt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;not defined for negative numbers&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (n &gt; <span class="number">14</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;not implemented for large numbers&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (n === <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> n * <span class="title function_">factorial</span>(n - <span class="number">1</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = factorial;</span><br></pre></td></tr></table></figure><p>Now, wire express up to offer a HTTP endpoint for factorial computation. Create a file <code>index.js</code> with the following content:</p><figure class="highlight javascript"><figcaption><span>app&#x2F;index.js</span><a href="https://github.com/widdix/aws-velocity/blob/master/app/index.js">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> factorial = <span class="built_in">require</span>(<span class="string">&#x27;./lib/factorial.js&#x27;</span>);</span><br><span class="line"><span class="keyword">var</span> express = <span class="built_in">require</span>(<span class="string">&#x27;express&#x27;</span>);</span><br><span class="line"><span class="keyword">var</span> app = <span class="title function_">express</span>();</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">&#x27;/:n&#x27;</span>, <span class="keyword">function</span> (<span class="params">req, res</span>) &#123;</span><br><span class="line">  <span class="keyword">var</span> n = <span class="built_in">parseInt</span>(req.<span class="property">params</span>.<span class="property">n</span>, <span class="number">10</span>);</span><br><span class="line">  <span class="keyword">if</span> (n &lt; <span class="number">0</span> || n &gt; <span class="number">14</span>) &#123;</span><br><span class="line">    res.<span class="title function_">sendStatus</span>(<span class="number">400</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    res.<span class="title function_">send</span>(<span class="title function_">factorial</span>(n).<span class="title function_">toString</span>());</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> port = process.<span class="property">env</span>.<span class="property">PORT</span> || <span class="number">3000</span>;</span><br><span class="line">app.<span class="title function_">listen</span>(port, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;app listening on port &#x27;</span> + port);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>You can now start the app locally:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">node index.js</span><br><span class="line"><span class="comment"># app listening on port 3000</span></span><br></pre></td></tr></table></figure><p>In another terminal, execute <code>curl</code> to make a HTTP request against your app:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl http://localhost:3000/5</span><br><span class="line"><span class="comment"># 120</span></span><br></pre></td></tr></table></figure><p>So far so good. It’s time to add an acceptance test.</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> ..</span><br><span class="line"><span class="built_in">cd</span> acceptance/</span><br></pre></td></tr></table></figure><p>Now, create the acceptance test structure. The acceptance test is an independent application also using Node.js. The <code>npm init</code> command will setup the project. Make sure to configure it like I did:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm init</span><br><span class="line"><span class="comment"># name: (acceptance) factorial-acceptance</span></span><br><span class="line"><span class="comment"># version: (1.0.0) 1.0.0</span></span><br><span class="line"><span class="comment"># description: Acceptance test for Factorial as a Service</span></span><br><span class="line"><span class="comment"># entry point: (spec.js) </span></span><br><span class="line"><span class="comment"># test command: ./node_modules/.bin/jshint .</span></span><br><span class="line"><span class="comment"># git repository: </span></span><br><span class="line"><span class="comment"># keywords: </span></span><br><span class="line"><span class="comment"># author: </span></span><br><span class="line"><span class="comment"># license: (ISC) </span></span><br><span class="line"><span class="comment"># About to write to /Users/michael/Desktop/aws-velocity/acceptance/package.json:</span></span><br><span class="line"><span class="comment"># &#123;</span></span><br><span class="line"><span class="comment">#   &quot;name&quot;: &quot;acceptance&quot;,</span></span><br><span class="line"><span class="comment">#   &quot;version&quot;: &quot;1.0.0&quot;,</span></span><br><span class="line"><span class="comment">#   &quot;description&quot;: &quot;&quot;,</span></span><br><span class="line"><span class="comment">#   &quot;main&quot;: &quot;spec.js&quot;,</span></span><br><span class="line"><span class="comment">#   &quot;scripts&quot;: &#123;</span></span><br><span class="line"><span class="comment">#     &quot;test&quot;: &quot;jshint .&quot;</span></span><br><span class="line"><span class="comment">#   &#125;,</span></span><br><span class="line"><span class="comment">#   &quot;author&quot;: &quot;&quot;,</span></span><br><span class="line"><span class="comment">#   &quot;license&quot;: &quot;ISC&quot;</span></span><br><span class="line"><span class="comment"># &#125;</span></span><br><span class="line"><span class="comment">#Is this ok? (yes) yes</span></span><br></pre></td></tr></table></figure><p>And again, you need to install a few dependencies. <code>frisby</code> helps us to test REST APIs, <code>jasmine</code> is yet another test framework, and <code>jshint</code> will ensure code quality:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm install frisby@0.8.5 --save</span><br><span class="line">npm install jasmine-node@1.14.5 --save</span><br><span class="line">npm install jshint@2.9.4 --save-dev</span><br></pre></td></tr></table></figure><p><code>jshint</code> needs some configuration. Create a file <code>.jshintrc</code> with the following content:</p><figure class="highlight json"><figcaption><span>acceptance&#x2F;.jshintrc</span><a href="https://github.com/widdix/aws-velocity/blob/master/acceptance/.jshintrc">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;esversion&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;node&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;jasmine&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Create a file <code>.jshintignore</code> with the following content:</p><figure class="highlight markdown"><figcaption><span>acceptance&#x2F;.jshintignore</span><a href="https://github.com/widdix/aws-velocity/blob/master/acceptance/.jshintignore">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line">node<span class="emphasis">_modules/<span class="strong">**</span></span></span><br></pre></td></tr></table></figure><p>Now the app structure is created. Let’s see if the test command works:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm <span class="built_in">test</span></span><br></pre></td></tr></table></figure><p>Everything is okay. Now you need to implement the acceptance test.</p><p>Create a file <code>factorial_spec.js</code> (the file must end with <code>_spec.js</code>!) with the following content. The structure is determined by <code>frisby</code>, lines with a condition start with <code>.expect</code>:</p><figure class="highlight javascript"><figcaption><span>acceptance&#x2F;factorial_spec.js</span><a href="https://github.com/widdix/aws-velocity/blob/master/acceptance/factorial_spec.js">GitHub</a></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> frisby = <span class="built_in">require</span>(<span class="string">&#x27;frisby&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (process.<span class="property">env</span>.<span class="property">ENDPOINT</span> === <span class="literal">undefined</span>) &#123;</span><br><span class="line">  <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;ENDPOINT environment variable missing&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">frisby.<span class="title function_">create</span>(<span class="string">&#x27;/-1&#x27;</span>)</span><br><span class="line">  .<span class="title function_">get</span>(process.<span class="property">env</span>.<span class="property">ENDPOINT</span> + <span class="string">&#x27;/-1&#x27;</span>)</span><br><span class="line">  .<span class="title function_">expectStatus</span>(<span class="number">400</span>)</span><br><span class="line">.<span class="title function_">toss</span>();</span><br><span class="line"></span><br><span class="line">frisby.<span class="title function_">create</span>(<span class="string">&#x27;/0&#x27;</span>)</span><br><span class="line">  .<span class="title function_">get</span>(process.<span class="property">env</span>.<span class="property">ENDPOINT</span> + <span class="string">&#x27;/0&#x27;</span>)</span><br><span class="line">  .<span class="title function_">expectStatus</span>(<span class="number">200</span>)</span><br><span class="line">  .<span class="title function_">expectBodyContains</span>(<span class="string">&#x27;1&#x27;</span>)</span><br><span class="line">.<span class="title function_">toss</span>();</span><br><span class="line"></span><br><span class="line">frisby.<span class="title function_">create</span>(<span class="string">&#x27;/14&#x27;</span>)</span><br><span class="line">  .<span class="title function_">get</span>(process.<span class="property">env</span>.<span class="property">ENDPOINT</span> + <span class="string">&#x27;/14&#x27;</span>)</span><br><span class="line">  .<span class="title function_">expectStatus</span>(<span class="number">200</span>)</span><br><span class="line">  .<span class="title function_">expectBodyContains</span>(<span class="string">&#x27;87178291200&#x27;</span>)</span><br><span class="line">.<span class="title function_">toss</span>();</span><br><span class="line"></span><br><span class="line">frisby.<span class="title function_">create</span>(<span class="string">&#x27;/15&#x27;</span>)</span><br><span class="line">  .<span class="title function_">get</span>(process.<span class="property">env</span>.<span class="property">ENDPOINT</span> + <span class="string">&#x27;/15&#x27;</span>)</span><br><span class="line">  .<span class="title function_">expectStatus</span>(<span class="number">400</span>)</span><br><span class="line">.<span class="title function_">toss</span>();</span><br></pre></td></tr></table></figure><p>To execute the acceptance tests against your locally running app (if you stopped the app, run <code>node index.js</code> in a separate terminal), run:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ENDPOINT=<span class="string">&quot;http://localhost:3000&quot;</span> ./node_modules/.bin/jasmine-node .</span><br><span class="line"><span class="comment">#Finished in 0.083 seconds</span></span><br><span class="line"><span class="comment">#4 tests, 6 assertions, 0 failures, 0 skipped</span></span><br></pre></td></tr></table></figure><p>The application is done. You have unit tests, and you also have acceptance tests. The Important differences between the two are:</p><ul><li>Unit tests can run without spawning a web server</li><li>Unit tests ensure that the factorial function returns the correct values</li><li>Acceptance tests ensure that the REST API works as expected (e.g. as documented, or as it behaved before)</li></ul><p>In the next article, you will learn how to setup the CI&#x2F;CD pipeline for your new app.</p><h2 id="Series"><a href="#Series" class="headerlink" title="Series"></a>Series</h2><p align="center"><img src="/images/2017/01/aws-velocity-cover.png" alt="AWS Velocity Cover" style="max-width: 349px"></p><ol><li><a href="/aws-velocity-set-the-assembly-line-up/">Set the assembly line up</a></li><li><strong>Local development environment</strong> (you are here)</li><li><a href="/aws-velocity-ci-cd-pipeline-as-code/">CI&#x2F;CD Pipeline as Code</a></li><li><a href="/aws-velocity-running-your-application/">Running your application</a></li><li>EC2 based app<br>  a. <a href="/aws-velocity-ec2-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-ec2-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li>Containerized ECS based app<br>  a. <a href="/aws-velocity-containerized-ecs-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-containerized-ecs-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li><a href="/aws-velocity-serverless-app/">Serverless app</a></li><li><a href="/aws-velocity-summary/">Summary</a></li></ol><p>You can find the source code on <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">GitHub</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Velocity Series: Set the assembly line up</title>
      <link>https://cloudonaut.io/aws-velocity-set-the-assembly-line-up/</link>
      <description>
        <![CDATA[<p>What is the first thing you do when starting a new project? For me, I design and set my assembly line up first. </p>
<p><picture class="i]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/continuous-delivery/">Continuous Delivery</category>
      <category domain="https://cloudonaut.io/tag/velocity/">velocity</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-velocity-set-the-assembly-line-up/</guid>
      <pubDate>Tue, 31 Jan 2017 09:05:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>What is the first thing you do when starting a new project? For me, I design and set my assembly line up first. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/01/aws-velocity@730w.webp 730w, /images/2017/01/aws-velocity@730w2x.webp 1460w, /images/2017/01/aws-velocity@610w.webp 610w, /images/2017/01/aws-velocity@610w2x.webp 1220w, /images/2017/01/aws-velocity@450w.webp 450w, /images/2017/01/aws-velocity@450w2x.webp 900w, /images/2017/01/aws-velocity@330w.webp 330w, /images/2017/01/aws-velocity@330w2x.webp 660w, /images/2017/01/aws-velocity@545w.webp 545w, /images/2017/01/aws-velocity@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/01/aws-velocity@730w.png 730w, /images/2017/01/aws-velocity@730w2x.png 1460w, /images/2017/01/aws-velocity@610w.png 610w, /images/2017/01/aws-velocity@610w2x.png 1220w, /images/2017/01/aws-velocity@450w.png 450w, /images/2017/01/aws-velocity@450w2x.png 900w, /images/2017/01/aws-velocity@330w.png 330w, /images/2017/01/aws-velocity@330w2x.png 660w, /images/2017/01/aws-velocity@545w.png 545w, /images/2017/01/aws-velocity@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/01/aws-velocity.png" alt="AWS Velocity: Software assembly line" title="AWS Velocity: Software assembly line"></picture></p><p>The assembly line describes the work that is necessary from idea to production.</p><ol><li>In a software project, you decide how to spend your resources. You can not work on everything at the same time.</li><li>You write code and tests, adjust configuration, add images, …</li><li>Every change is versioned in a repository.</li><li>The source code is built and packaged. Tests are executed, and static source code checkers analyze the source code.</li><li>The packaged software is deployed to AWS into a production-like environment.</li><li>Acceptance tests run against the production-like environment to ensure that your system still behaves as expected.</li><li>Now, you are ready to deploy to production.</li><li>All steps need to be monitored to know if something goes wrong and to decide based on data.</li></ol><p>As you can see, many steps are involved from idea to production. Many steps also mean that many things can go wrong. The goal of the assembly line is to ensure that work is only handed over from one step to the other if it has no defects. Therefore you aim for automation whenever possible. You also need to improve the assembly line whenever a defect gets through to make sure that this never happens again.</p><div class="well">  <div class="media">    <div class="media-left">      <a href="/aws-velocity-series/">        <img class="media-object raw" src="/images/2017/04/aws-velocity-cover-icon.png" alt="AWS Velocity Cover">      </a>    </div>    <div class="media-body">      <h4 class="media-heading">AWS Velocity Series</h4>      <p>Most of our clients use AWS to reduce time-to-market following an agile approach. But AWS is only one part of the solution. In this article series, I show you how we help our clients to improve velocity: the time from idea to production. <a href="/aws-velocity-series/">Discover all posts!</a></p>    </div>  </div></div><p>That’s your way to <strong>AWS Velocity</strong>.</p><h2 id="Software-assembly-line"><a href="#Software-assembly-line" class="headerlink" title="Software assembly line"></a>Software assembly line</h2><p>A typical assembly line in my projects looks like this:</p><ul><li>Kanban board to plan work and visualize work in progress (e.g. <a href="https://trello.com/" target="_blank" rel="noopener">Trello</a>)</li><li>Developer environment<ul><li>Editor</li><li>Local build</li><li>Local test</li></ul></li><li>The CI&#x2F;CD pipeline<ul><li>Code Repository (<a href="https://aws.amazon.com/codecommit/" target="_blank" rel="noopener">AWS CodeCommit</a>)</li><li>Artifact Repository (<a href="https://aws.amazon.com/s3/" target="_blank" rel="noopener">Amazon S3</a>, <a href="https://aws.amazon.com/ecr/" target="_blank" rel="noopener">Amazon EC2 Container Registry</a>)</li></ul></li><li>Pipeline (<a href="https://aws.amazon.com/codepipeline/" target="_blank" rel="noopener">AWS CodePipeline</a>)<ul><li>Build &amp; test (<a href="https://aws.amazon.com/codebuild/" target="_blank" rel="noopener">AWS CodeBuild</a>)</li><li>Deploy acceptance environment (<a href="https://aws.amazon.com/cloudformation/" target="_blank" rel="noopener">AWS CloudFormation</a>)</li><li>Run acceptance tests (<a href="https://aws.amazon.com/codebuild/" target="_blank" rel="noopener">AWS CodeBuild</a>)</li><li>Deploy production environment (<a href="https://aws.amazon.com/cloudformation/" target="_blank" rel="noopener">AWS CloudFormation</a>)</li></ul></li><li>Monitoring (<a href="https://aws.amazon.com/cloudwatch/" target="_blank" rel="noopener">AWS CloudWatch</a>)<ul><li>Logging</li><li>Metrics</li><li>Alerting</li></ul></li></ul><p>In the future articles of this series, you will learn how to glue all those AWS services together to implement your assembly line.</p><h2 id="Velocity-loves-managed-services"><a href="#Velocity-loves-managed-services" class="headerlink" title="Velocity loves managed services"></a>Velocity loves managed services</h2><p>Most of the assembly line can be implemented with managed AWS services. Managed services help you to focus on the actual work while AWS cares about things like running your build server. Using managed services will speed you up as they offer standardized and highly integrated building blocks for your system. In the following articles, you will learn how you can implement AWS Velocity with EC2 based apps, Containerized apps, and Serverless apps.</p><h2 id="Series"><a href="#Series" class="headerlink" title="Series"></a>Series</h2><p align="center"><img src="/images/2017/01/aws-velocity-cover.png" alt="AWS Velocity Cover" style="max-width: 349px"></p><ol><li><strong>Set the assembly line up</strong> (you are here)</li><li><a href="/aws-velocity-local-development-environment/">Local development environment</a></li><li><a href="/aws-velocity-ci-cd-pipeline-as-code/">CI&#x2F;CD Pipeline as Code</a></li><li><a href="/aws-velocity-running-your-application/">Running your application</a></li><li>EC2 based app<br>  a. <a href="/aws-velocity-ec2-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-ec2-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li>Containerized ECS based app<br>  a. <a href="/aws-velocity-containerized-ecs-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-containerized-ecs-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li><a href="/aws-velocity-serverless-app/">Serverless app</a></li><li><a href="/aws-velocity-summary/">Summary</a></li></ol><p>You can find the source code on <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">GitHub</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Velocity Series</title>
      <link>https://cloudonaut.io/aws-velocity-series/</link>
      <description>
        <![CDATA[<p>Most of our clients use AWS to reduce time-to-market following an agile approach. But AWS is only one part of the solution. In this artic]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/velocity/">velocity</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-velocity-series/</guid>
      <pubDate>Tue, 31 Jan 2017 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Most of our clients use AWS to reduce time-to-market following an agile approach. But AWS is only one part of the solution. In this article series, I show you how we help our clients to improve velocity: the time from idea to production.</p><p align="center"><img src="/images/2017/01/aws-velocity-cover.png" alt="AWS Velocity Cover" style="max-width: 349px"></p><h2 id="Table-of-Contents"><a href="#Table-of-Contents" class="headerlink" title="Table of Contents"></a>Table of Contents</h2><ol><li><a href="/aws-velocity-set-the-assembly-line-up/">Set the assembly line up</a></li><li><a href="/aws-velocity-local-development-environment/">Local development environment</a></li><li><a href="/aws-velocity-ci-cd-pipeline-as-code/">CI&#x2F;CD Pipeline as Code</a></li><li><a href="/aws-velocity-running-your-application/">Running your application</a></li><li>EC2 based app<br>  a. <a href="/aws-velocity-ec2-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-ec2-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li>Containerized ECS based app<br>  a. <a href="/aws-velocity-containerized-ecs-based-app-infrastructure/">Infrastructure</a><br>  b. <a href="/aws-velocity-containerized-ecs-based-app-ci-cd-pipeline/">CI&#x2F;CD Pipeline</a></li><li><a href="/aws-velocity-serverless-app/">Serverless app</a></li><li><a href="/aws-velocity-summary/">Summary</a></li></ol><p>You can find the source code on <a href="https://github.com/widdix/aws-velocity" target="_blank" rel="noopener">GitHub</a>.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Book review: The DevOps Handbook</title>
      <link>https://cloudonaut.io/book-review-the-devops-handbook/</link>
      <description>
        <![CDATA[<p>How to create world-class agility, reliability, and security in technology organizations? <em>The DevOps Handbook</em> (IT Revolution Pre]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/career/">career</category>
      <guid isPermaLink="true">https://cloudonaut.io/book-review-the-devops-handbook/</guid>
      <pubDate>Fri, 13 Jan 2017 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>How to create world-class agility, reliability, and security in technology organizations? <em>The DevOps Handbook</em> (IT Revolution Press) is answering this question. Read on for a review of <em><a href="http://itrevolution.com/devops-handbook" target="_blank" rel="noopener">The DevOps Handbook</a></em>.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/01/devops-handbook@730w.webp 730w, /images/2017/01/devops-handbook@730w2x.webp 1460w, /images/2017/01/devops-handbook@610w.webp 610w, /images/2017/01/devops-handbook@610w2x.webp 1220w, /images/2017/01/devops-handbook@450w.webp 450w, /images/2017/01/devops-handbook@450w2x.webp 900w, /images/2017/01/devops-handbook@330w.webp 330w, /images/2017/01/devops-handbook@330w2x.webp 660w, /images/2017/01/devops-handbook@545w.webp 545w, /images/2017/01/devops-handbook@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/01/devops-handbook@730w.png 730w, /images/2017/01/devops-handbook@730w2x.png 1460w, /images/2017/01/devops-handbook@610w.png 610w, /images/2017/01/devops-handbook@610w2x.png 1220w, /images/2017/01/devops-handbook@450w.png 450w, /images/2017/01/devops-handbook@450w2x.png 900w, /images/2017/01/devops-handbook@330w.png 330w, /images/2017/01/devops-handbook@330w2x.png 660w, /images/2017/01/devops-handbook@545w.png 545w, /images/2017/01/devops-handbook@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/01/devops-handbook.png" alt="The DevOps Handbook" title="The DevOps Handbook"></picture></p><p><em>The DevOps Handbook</em> starts with the author’s “aha” moments with DevOps. I’d like to share my personal “aha” moment with you, as well. Back in 2012 I have been working on an online banking platform. Our team of software engineers had the goal of releasing at least one feature or major improvement per week. But the process of shipping software did not allow us to do so: manual instead of automated tests, poor communication and feedback between software development and operations, time-consuming provisioning of new infrastructure and environments. To be able to get feedback from our customers as soon as possible, we were looking for a way to be able to deploy new features and improvements much faster and more often. Our team started to build and operate its own infrastructure on top of Amazon Web Services. We were also investing into automated tests and deployments very heavily. As a result, we were able to deploy to production with less risk and much faster.</p><p>Back to topic! I have been reading <em>The DevOps Handbook</em> recently. I can highly recommend this book written by Gene Kim, Jez Humble, Patrick Debois, and John Willis. <em>The DevOps Handbook</em> is summarizing and structuring the concepts behind DevOps clearly.</p><p>The book starts with illustrating the chronic conflict between Development and IT Operations.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/01/conflict-dev-ops@730w.webp 730w, /images/2017/01/conflict-dev-ops@730w2x.webp 1460w, /images/2017/01/conflict-dev-ops@610w.webp 610w, /images/2017/01/conflict-dev-ops@610w2x.webp 1220w, /images/2017/01/conflict-dev-ops@450w.webp 450w, /images/2017/01/conflict-dev-ops@450w2x.webp 900w, /images/2017/01/conflict-dev-ops@330w.webp 330w, /images/2017/01/conflict-dev-ops@330w2x.webp 660w, /images/2017/01/conflict-dev-ops@545w.webp 545w, /images/2017/01/conflict-dev-ops@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/01/conflict-dev-ops@730w.png 730w, /images/2017/01/conflict-dev-ops@730w2x.png 1460w, /images/2017/01/conflict-dev-ops@610w.png 610w, /images/2017/01/conflict-dev-ops@610w2x.png 1220w, /images/2017/01/conflict-dev-ops@450w.png 450w, /images/2017/01/conflict-dev-ops@450w2x.png 900w, /images/2017/01/conflict-dev-ops@330w.png 330w, /images/2017/01/conflict-dev-ops@330w2x.png 660w, /images/2017/01/conflict-dev-ops@545w.png 545w, /images/2017/01/conflict-dev-ops@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/01/conflict-dev-ops.png" alt="Dev vs. Ops" title="Dev vs. Ops"></picture></p><p>Solving the conflict by transferring learnings from the manufacturing value stream to the technology value stream is the main focus of the book. The author’s are focusing on the <em>Three Ways</em>. The <em>Three Ways</em> are the core principals behind DevOps: Flow, Feedback, and Continual Learning.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2017/01/three-ways@730w.webp 730w, /images/2017/01/three-ways@730w2x.webp 1460w, /images/2017/01/three-ways@610w.webp 610w, /images/2017/01/three-ways@610w2x.webp 1220w, /images/2017/01/three-ways@450w.webp 450w, /images/2017/01/three-ways@450w2x.webp 900w, /images/2017/01/three-ways@330w.webp 330w, /images/2017/01/three-ways@330w2x.webp 660w, /images/2017/01/three-ways@545w.webp 545w, /images/2017/01/three-ways@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2017/01/three-ways@730w.png 730w, /images/2017/01/three-ways@730w2x.png 1460w, /images/2017/01/three-ways@610w.png 610w, /images/2017/01/three-ways@610w2x.png 1220w, /images/2017/01/three-ways@450w.png 450w, /images/2017/01/three-ways@450w2x.png 900w, /images/2017/01/three-ways@330w.png 330w, /images/2017/01/three-ways@330w2x.png 660w, /images/2017/01/three-ways@545w.png 545w, /images/2017/01/three-ways@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2017/01/three-ways.png" alt="The Three Ways" title="The Three Ways"></picture></p><p><em><a href="http://itrevolution.com/devops-handbook" target="_blank" rel="noopener">The DevOps Handbook</a></em> is clearly stating the core of DevOps. The book is not focusing on tools like Infrastructure as Code, Containers, or Configuration Management. It is all about people, culture, and processes. The book is creating a language to describe DevOps and gain a common understanding. Highly recommended no matter if you have a technical or business background.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>2016 in Review: Thank You!</title>
      <link>https://cloudonaut.io/2016-in-review-thank-you/</link>
      <description>
        <![CDATA[<p>The year 2016 is coming to an end. We’d like to look back on the past year together with you. Thank you for reading our blog, sharing our]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/retrospective/">retrospective</category>
      <guid isPermaLink="true">https://cloudonaut.io/2016-in-review-thank-you/</guid>
      <pubDate>Thu, 29 Dec 2016 09:05:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The year 2016 is coming to an end. We’d like to look back on the past year together with you. Thank you for reading our blog, sharing our work, and giving valuable feedback. It was a pleasure to be part of an inspiring community.</p><h2 id="Facts-and-numbers"><a href="#Facts-and-numbers" class="headerlink" title="Facts and numbers"></a>Facts and numbers</h2><p>What was going on at <a href="https://cloudonaut.io/">cloudonaut.io</a> in 2016?</p><ul><li>240,000 pages viewed</li><li>112,500 people visiting the site</li><li>72 articles published</li><li>45,500 written words</li></ul><h2 id="Most-read"><a href="#Most-read" class="headerlink" title="Most read"></a>Most read</h2><p>The following articles have been read the most in 2016.</p><ul><li><a href="/5-aws-mistakes-you-should-avoid/">5 AWS mistakes you should avoid</a></li><li><a href="/create-a-serverless-restful-api-with-api-gateway-swagger-lambda-and-dynamodb/">Create a serverless RESTful API with API Gateway, Swagger, Lambda, and DynamoDB</a></li><li><a href="/wordpress-on-aws-smooth-and-pain-free/">WordPress on AWS: smooth and pain-free</a></li><li><a href="/integrate-sqs-and-lambda-serverless-architecture-for-asynchronous-workloads/">Integrate SQS and Lambda: serverless architecture for asynchronous workloads</a></li><li><a href="/serverless-image-resizing-at-any-scale/">Serverless image resizing at any scale</a></li><li><a href="/the-life-of-a-serverless-microservice-on-aws/">The Life of a Serverless Microservice on AWS</a></li><li><a href="/manage-aws-ec2-ssh-access-with-iam/">Manage AWS EC2 SSH access with IAM</a></li><li><a href="/3-simple-ways-of-saving-up-to-90-of-ec2-costs/">3 simple ways of saving up to 90% of EC2 costs</a></li><li><a href="/create-a-serverless-restful-api-with-api-gateway-cloudformation-lambda-and-dynamodb/">Create a serverless RESTful API with API Gateway, CloudFormation, Lambda, and DynamoDB</a></li><li><a href="/6-tips-and-tricks-for-aws-command-line-ninjas/">6 tips and tricks for AWS command-line ninjas</a></li></ul><h2 id="Projects"><a href="#Projects" class="headerlink" title="Projects"></a>Projects</h2><p>A few projects we have been working on, beside our consulting business.</p><ul><li><a href="https://marbot.io/" target="_blank" rel="noopener">marbot</a> - Forward alerts from Amazon Web Services to your DevOps team via Slack.</li><li><a href="https://github.com/widdix/aws-s3-virusscan" target="_blank" rel="noopener">S3 Virus Scan</a> - Antivirus for S3 buckets.</li><li><a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">AWS CloudFormation templates</a> - Free Templates for AWS CloudFormation.</li><li><a href="/improve-security-groups-using-vpc-flow-logs-aws-config/">Improve Security (Groups) using VPC Flow Logs &amp; AWS Config</a></li></ul><h2 id="Online-Training"><a href="#Online-Training" class="headerlink" title="Online Training"></a>Online Training</h2><p>We do enjoy teaching. That’s why we have created three online trainings this year.</p><ul><li><a href="http://bit.ly/aws-navigating-management-console" target="_blank" rel="noopener">Navigating the AWS Management Console</a> (Pluralsight)</li><li><a href="http://bit.ly/aws-automating-cloudformation" target="_blank" rel="noopener">Automating AWS with CloudFormation</a> (Pluralsight)</li><li><a href="https://acloud.guru/learn/aws-application-loadbalancer" target="_blank" rel="noopener">Deep Dive into Application Load Balancer (ALB)</a> (A Cloud Guru)</li></ul><h2 id="Publications"><a href="#Publications" class="headerlink" title="Publications"></a>Publications</h2><p>Besides writing our blog we published the following in 2016.</p><ul><li><a href="https://dzone.com/guides/building-and-deploying-applications-on-the-cloud" target="_blank" rel="noopener">Building and Deploying Applications on the Cloud</a></li><li><a href="https://www.manning.com/books/cloud-native-applications" target="_blank" rel="noopener">Cloud Native Applications</a></li></ul><h2 id="Thank-you"><a href="#Thank-you" class="headerlink" title="Thank you!"></a>Thank you!</h2><p>Again, thank you for reading our blog and supporting us. We are looking forward to writing about AWS and working on exciting projects in 2017. </p><p>Wishing you a happy new year!</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Delivery Pipeline as Code: AWS CloudFormation and AWS CodePipeline</title>
      <link>https://cloudonaut.io/delivery-pipeline-as-code-aws-cloudformation-codepipeline/</link>
      <description>
        <![CDATA[<p>The assembly line is the heart of any factory. Workers and supervisors are giving their best to ensure a steady flow of raw materials to]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/continuous-delivery/">Continuous Delivery</category>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <category domain="https://cloudonaut.io/tag/codepipeline/">codepipeline</category>
      <guid isPermaLink="true">https://cloudonaut.io/delivery-pipeline-as-code-aws-cloudformation-codepipeline/</guid>
      <pubDate>Thu, 22 Dec 2016 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>The assembly line is the heart of any factory. Workers and supervisors are giving their best to ensure a steady flow of raw materials to the end products. The delivery pipeline is an important part of DevOps and the equivalent to the assembly line. A delivery pipeline allows you to define and automate the process of making changes to your systems.</p><p>Read on to learn how to combine two tools: Infrastructure as Code and delivery pipelines. You will gain superpower by making use of this pattern!</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2016/12/delivery-pipeline-as-code@730w.webp 730w, /images/2016/12/delivery-pipeline-as-code@730w2x.webp 1460w, /images/2016/12/delivery-pipeline-as-code@610w.webp 610w, /images/2016/12/delivery-pipeline-as-code@610w2x.webp 1220w, /images/2016/12/delivery-pipeline-as-code@450w.webp 450w, /images/2016/12/delivery-pipeline-as-code@450w2x.webp 900w, /images/2016/12/delivery-pipeline-as-code@330w.webp 330w, /images/2016/12/delivery-pipeline-as-code@330w2x.webp 660w, /images/2016/12/delivery-pipeline-as-code@545w.webp 545w, /images/2016/12/delivery-pipeline-as-code@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2016/12/delivery-pipeline-as-code@730w.png 730w, /images/2016/12/delivery-pipeline-as-code@730w2x.png 1460w, /images/2016/12/delivery-pipeline-as-code@610w.png 610w, /images/2016/12/delivery-pipeline-as-code@610w2x.png 1220w, /images/2016/12/delivery-pipeline-as-code@450w.png 450w, /images/2016/12/delivery-pipeline-as-code@450w2x.png 900w, /images/2016/12/delivery-pipeline-as-code@330w.png 330w, /images/2016/12/delivery-pipeline-as-code@330w2x.png 660w, /images/2016/12/delivery-pipeline-as-code@545w.png 545w, /images/2016/12/delivery-pipeline-as-code@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2016/12/delivery-pipeline-as-code.png" alt="Delivery Pipeline as Code" title="Delivery Pipeline as Code"></picture></p><h2 id="What-is-Infrastructure-as-Code"><a href="#What-is-Infrastructure-as-Code" class="headerlink" title="What is Infrastructure as Code?"></a>What is Infrastructure as Code?</h2><p>You can create your cloud infrastructure by clicking through the Management Console, the web interface offered by AWS.  But automating the process of provisioning and updating your infrastructure allows you to increase reliability and decrease effort. Describing the target state of your infrastructure in code and using a tool to transform the current state of your infrastructure into the target state is called <em>Infrastructure as Code</em>.</p><p>Our article <a href="/understanding-infrastructure-as-code/">Understanding Infrastructure as Code</a> explains the concept of Infrastructure as Code in more detail.</p><p>AWS is offering an <em>Infrastructure as Code</em> tool: <a href="http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html" target="_blank" rel="noopener">AWS CloudFormation</a>. You can define your infrastructure consisting of virtual machines, networking configuration, databases, and any other AWS service within a template. CloudFormation can use the template containing the target state to create or update your infrastructure.</p><h2 id="What-is-a-delivery-pipeline"><a href="#What-is-a-delivery-pipeline" class="headerlink" title="What is a delivery pipeline?"></a>What is a delivery pipeline?</h2><p>Beside Infrastructure as Code every DevOps toolbox should contain delivery pipelines as well. Pushing every source code change through an automated pipeline ending with a deployment to your production system is called Continuous Delivery. A delivery pipeline defines the process of deploying changes to production. It consists at least of the following steps:</p><ul><li>Building and Packaging</li><li>Testing small units and the whole system</li><li>Deploying to production</li></ul><p>Think of a delivery pipeline as the assembly line in a DevOps world. AWS is offering a service allowing you to define and execute delivery pipelines: <a href="http://docs.aws.amazon.com/codepipeline/latest/userguide/welcome.html" target="_blank" rel="noopener">AWS CodePipeline</a>. </p><h2 id="Delivery-Pipeline-as-Code"><a href="#Delivery-Pipeline-as-Code" class="headerlink" title="Delivery Pipeline as Code"></a>Delivery Pipeline as Code</h2><p>Your delivery pipeline will become as valuable to your company as an assembly line within a factory. It contains all the knowledge needed to deploy a change to production.</p><p>Therefore creating the delivery pipeline should be reproducible and automated in the same way as creating your infrastructure. The delivery pipeline itself should be defined in code. Again, Infrastructure as Code is the tool of your choice.</p><h2 id="Example-CloudFormation-and-CodePipeline"><a href="#Example-CloudFormation-and-CodePipeline" class="headerlink" title="Example: CloudFormation and CodePipeline"></a>Example: CloudFormation and CodePipeline</h2><p>The following example shows how to use Infrastructure as Code to create a deployment pipeline. The example uses CodePipeline and CloudFormation to deploy a static website on EC2.</p><p>The following snippet contains the definition of a CodePipeline consisting of three stages:</p><ol><li>Source: References a GitHub repository. A commit to the repository triggers the deployment pipeline automatically.</li><li>Deploy: CodeDeploy is used to deploy a static web application on EC2.</li><li>Test: A Lambda function sends HTTP request to the EC2 instance to validate the deployment.</li></ol><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="string">...</span></span><br><span class="line"><span class="attr">CodePipeline:</span></span><br><span class="line">  <span class="attr">DependsOn:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">EC2Instance</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">&quot;AWS::CodePipeline::Pipeline&quot;</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">ArtifactStore:</span></span><br><span class="line">      <span class="attr">Location:</span> <span class="type">!Ref</span> <span class="string">ArtifactStore</span></span><br><span class="line">      <span class="attr">Type:</span> <span class="string">S3</span></span><br><span class="line">    <span class="attr">RoleArn:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;CodePipelineIAMRole.Arn&#125;&#x27;</span></span><br><span class="line">    <span class="attr">Stages:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">        <span class="attr">Actions:</span> </span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Source</span></span><br><span class="line">          <span class="attr">ActionTypeId:</span></span><br><span class="line">            <span class="attr">Category:</span> <span class="string">Source</span> </span><br><span class="line">            <span class="attr">Owner:</span> <span class="string">ThirdParty</span> </span><br><span class="line">            <span class="attr">Version:</span> <span class="number">1</span> </span><br><span class="line">            <span class="attr">Provider:</span> <span class="string">GitHub</span></span><br><span class="line">          <span class="attr">OutputArtifacts:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">staticwebsite</span></span><br><span class="line">          <span class="attr">Configuration:</span></span><br><span class="line">            <span class="attr">Owner:</span> <span class="type">!Ref</span> <span class="string">GitHubOwner</span></span><br><span class="line">            <span class="attr">Repo:</span> <span class="type">!Ref</span> <span class="string">GitHubRepo</span></span><br><span class="line">            <span class="attr">Branch:</span> <span class="string">master</span></span><br><span class="line">            <span class="attr">OAuthToken:</span> <span class="type">!Ref</span> <span class="string">GitHubOAuthToken</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Deploy</span></span><br><span class="line">        <span class="attr">Actions:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Deploy</span></span><br><span class="line">          <span class="attr">ActionTypeId:</span></span><br><span class="line">            <span class="attr">Category:</span> <span class="string">Deploy</span> </span><br><span class="line">            <span class="attr">Owner:</span> <span class="string">AWS</span> </span><br><span class="line">            <span class="attr">Version:</span> <span class="number">1</span> </span><br><span class="line">            <span class="attr">Provider:</span> <span class="string">CodeDeploy</span></span><br><span class="line">          <span class="attr">InputArtifacts:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">staticwebsite</span></span><br><span class="line">          <span class="attr">Configuration:</span></span><br><span class="line">            <span class="attr">ApplicationName:</span> <span class="type">!Ref</span> <span class="string">Application</span></span><br><span class="line">            <span class="attr">DeploymentGroupName:</span> <span class="type">!Ref</span> <span class="string">DeploymentGroup</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Test</span></span><br><span class="line">        <span class="attr">Actions:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Name:</span> <span class="string">Test</span></span><br><span class="line">          <span class="attr">ActionTypeId:</span></span><br><span class="line">            <span class="attr">Category:</span> <span class="string">Invoke</span> </span><br><span class="line">            <span class="attr">Owner:</span> <span class="string">AWS</span> </span><br><span class="line">            <span class="attr">Version:</span> <span class="number">1</span> </span><br><span class="line">            <span class="attr">Provider:</span> <span class="string">Lambda</span></span><br><span class="line">          <span class="attr">Configuration:</span></span><br><span class="line">            <span class="attr">FunctionName:</span> <span class="type">!Ref</span> <span class="string">TestLambda</span></span><br><span class="line">            <span class="attr">UserParameters:</span> <span class="type">!Sub</span> <span class="string">&#x27;http://$&#123;EC2Instance.PublicDnsName&#125;&#x27;</span></span><br><span class="line"><span class="string">...</span></span><br></pre></td></tr></table></figure><p>Find the whole source code at <a href="https://github.com/andreaswittig/codepipeline-codedeploy-example" target="_blank" rel="noopener">andreaswittig&#x2F;codepipeline-codedeploy-example</a>.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>In my opinion, making use of Infrastructure as Code to manage your deployment pipeline is an important DevOps pattern. AWS CloudFormation and AWS CodePipeline allow you to benefit from an automated, reproducible, and transparent deployment pipeline.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Designing asynchronous event systems with AWS IoT and Serverless Application Model (SAM)</title>
      <link>https://cloudonaut.io/designing-asynchronous-event-systems-with-aws-iot-serverless-application-model-sam/</link>
      <description>
        <![CDATA[<p>An event system receives and processes events by following rules that are defined inside the system. All processing happens asynchronousl]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/serverless/">Serverless</category>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <category domain="https://cloudonaut.io/tag/iot/">iot</category>
      <guid isPermaLink="true">https://cloudonaut.io/designing-asynchronous-event-systems-with-aws-iot-serverless-application-model-sam/</guid>
      <pubDate>Mon, 05 Dec 2016 12:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>An event system receives and processes events by following rules that are defined inside the system. All processing happens asynchronously. When an event is sent to the system, it is processed at some point in time, but you will not get an immediate response. Asynchronous processing has advantages if you want to build a scalable solution because it frees you from the burden of an immediate response. Instead you can queue them and process them as fast as you can. In this article, I will demonstrate how the <a href="https://aws.amazon.com/iot-platform/" target="_blank" rel="noopener">AWS IoT</a> service can be used to process events that have nothing to do with IoT. I will also use the new <a href="https://github.com/awslabs/serverless-application-model" target="_blank" rel="noopener">Serverless Application Model (SAM)</a> to deploy the solution. Needless to mention that the solution will be serverless and highly cost effective for workloads up to 1 mio events per day.</p><h2 id="How-AWS-IoT-works"><a href="#How-AWS-IoT-works" class="headerlink" title="How AWS IoT works"></a>How AWS IoT works</h2><p>AWS IoT can do many things, but here I focus on messages, topics, rules, and actions.</p><p>A <strong>message</strong> is sent to a topic. In this case, a message is an event. AWS IoT can deal with JSON so I choose JSON as my data representation.</p><p>A <strong>topic</strong> has a name like <code>event/buy</code> and as you can see you can add a hierarchy by using up to 7 forward slashes (<code>/</code>).</p><p>A <strong>rule</strong> subscribes to a topic and triggers actions when a message is received. As simple rule can subscribe to the topic <code>event/buy</code>. But you can also use wildcards like <code>event/+</code> or <code>event/*</code>. <code>+</code> is used for exactly one hierarchy while <code>*</code> matches to any number of hierarchies. </p><p>AWS IoT comes with many built-in <strong>actions</strong>. To mention just a few:</p><ul><li>Write the message to DynamoDB.</li><li>Save the message as a file to S3.</li><li>Send the message to SNS.</li><li>Invoke a Lambda function to process the message.</li></ul><p>So a message is sent to a topic. If a rule matches the topic’s name, it triggers the defined actions. That’s an event system, isn’t it?</p><h2 id="Architecture"><a href="#Architecture" class="headerlink" title="Architecture"></a>Architecture</h2><p>The event system I design in this article can handle events that are generated at exchanges like buy and sell events. It is important to store all events on durable storage for archival. For some reasons buy events need to be checked for fraud. If a fraud event is detected, external systems must be notified. The following figure shows the architecture of the system.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2016/12/iot-event-system@730w.webp 730w, /images/2016/12/iot-event-system@730w2x.webp 1460w, /images/2016/12/iot-event-system@610w.webp 610w, /images/2016/12/iot-event-system@610w2x.webp 1220w, /images/2016/12/iot-event-system@450w.webp 450w, /images/2016/12/iot-event-system@450w2x.webp 900w, /images/2016/12/iot-event-system@330w.webp 330w, /images/2016/12/iot-event-system@330w2x.webp 660w, /images/2016/12/iot-event-system@545w.webp 545w, /images/2016/12/iot-event-system@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2016/12/iot-event-system@730w.png 730w, /images/2016/12/iot-event-system@730w2x.png 1460w, /images/2016/12/iot-event-system@610w.png 610w, /images/2016/12/iot-event-system@610w2x.png 1220w, /images/2016/12/iot-event-system@450w.png 450w, /images/2016/12/iot-event-system@450w2x.png 900w, /images/2016/12/iot-event-system@330w.png 330w, /images/2016/12/iot-event-system@330w2x.png 660w, /images/2016/12/iot-event-system@545w.png 545w, /images/2016/12/iot-event-system@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2016/12/iot-event-system.png" alt="AWS IoT Architecture" title="AWS IoT Architecture"></picture></p><p><em>The diagram was created with <a href="https://bit.ly/2PpMc2M" target="_blank" rel="noopener">Cloudcraft - Visualize your cloud architecture like a pro</a>.</em></p><h2 id="Event-Flow"><a href="#Event-Flow" class="headerlink" title="Event Flow"></a>Event Flow</h2><ol><li>It all starts with a HTTP API (provided by API Gateway) that triggers a Lambda function for every HTTP <code>POST</code> call <code>/event</code>.</li><li>The Lambda <code>event-api</code> does some input validation. Depending on the payload the event is published on a topic like <code>event/buy</code>, <code>event/sell</code>, …</li><li>One rule subscribes to all <code>event/+</code> topics with an action to write the message to DynamoDB.</li><li>Another rule subscribes only to the <code>event/buy</code> topic and triggers the <code>buy-event</code> Lambda for every message.</li><li>The <code>buy-event</code> Lambda decides if the event is fraud or not. If it is fraud, it publishes the event to the <code>alert/fraud</code> topic.</li><li>A rule subscribes to the <code>alert/fraud</code> topic and triggers two actions: Save message to S3 and send event to SNS.</li></ol><h2 id="Implementation"><a href="#Implementation" class="headerlink" title="Implementation"></a>Implementation</h2><p>I use the brand new Serverless Application Model (SAM) for this example. SAM builds upon CloudFormation, so most of the interesting pieces happen inside file that I will name <code>template.yml</code>.</p><p>You can find the full source code on <a href="https://github.com/michaelwittig/sam-iot-example" target="_blank" rel="noopener">GitHub</a>.</p><p>The first file defines Node.js dependencies that are needed in the Lambda functions.</p><p>package.json</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;sam-iot-example&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;author&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Michael Wittig&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;license&quot;</span><span class="punctuation">:</span> <span class="string">&quot;MIT&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;dependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;uuid&quot;</span><span class="punctuation">:</span> <span class="string">&quot;3.0.1&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;devDependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;aws-sdk&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2.6.9&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="REST-API"><a href="#REST-API" class="headerlink" title="REST API"></a>REST API</h3><p>This is how you define a APi Gateway with SAM.</p><p>template.yml</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Transform:</span> <span class="string">&#x27;AWS::Serverless-2016-10-31&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">EventApiLambda:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::Serverless::Function&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Handler:</span> <span class="string">&#x27;event-api-handler.create&#x27;</span></span><br><span class="line">      <span class="attr">Runtime:</span> <span class="string">&#x27;nodejs6.10&#x27;</span></span><br><span class="line">      <span class="attr">Policies:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;iot:Publish&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:iot:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:topic/event/*&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;iot:DescribeEndpoint&#x27;</span></span><br><span class="line">          <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">      <span class="attr">Events:</span></span><br><span class="line">        <span class="attr">Http:</span></span><br><span class="line">          <span class="attr">Type:</span> <span class="string">Api</span></span><br><span class="line">          <span class="attr">Properties:</span></span><br><span class="line">            <span class="attr">Path:</span> <span class="string">/event</span></span><br><span class="line">            <span class="attr">Method:</span> <span class="string">post</span></span><br></pre></td></tr></table></figure><p>And here comes the implementation that will run inside Lambda. The name of the file must match with the <code>Handler</code> from above.</p><p>event-api-handler.js</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> uuid = <span class="built_in">require</span>(<span class="string">&#x27;uuid&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> cache = <span class="built_in">require</span>(<span class="string">&#x27;./cache.js&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">publish</span>(<span class="params">iotdata, payload</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> iotdata.<span class="title function_">publish</span>(&#123;</span><br><span class="line">    <span class="attr">topic</span>: <span class="string">`event/<span class="subst">$&#123;payload.type&#125;</span>`</span>,</span><br><span class="line">    <span class="attr">qos</span>: <span class="number">0</span>,</span><br><span class="line">    <span class="attr">payload</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(payload),</span><br><span class="line">  &#125;).<span class="title function_">promise</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span>.<span class="property">create</span> = <span class="function">(<span class="params">event, context, cb</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(event));</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> payload = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(event.<span class="property">body</span>); <span class="comment">// safely parse body JSON</span></span><br><span class="line">  &#125; <span class="keyword">catch</span>(err) &#123;</span><br><span class="line">    <span class="title function_">cb</span>(<span class="literal">null</span>, &#123;<span class="attr">statusCode</span>: <span class="number">400</span>&#125;);</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (payload.<span class="property">id</span> === <span class="literal">undefined</span> || payload.<span class="property">id</span> === <span class="literal">null</span>) &#123;</span><br><span class="line">    payload.<span class="property">id</span> = uuid.<span class="title function_">v4</span>(); <span class="comment">// assign id if no id was passed in</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (payload.<span class="property">type</span> === <span class="literal">undefined</span> || payload.<span class="property">type</span> === <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="title function_">cb</span>(<span class="literal">null</span>, &#123;<span class="attr">statusCode</span>: <span class="number">400</span>&#125;);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    cache.<span class="property">iotdata</span></span><br><span class="line">      .<span class="title function_">then</span>(<span class="function">(<span class="params">iotdata</span>) =&gt;</span> <span class="title function_">publish</span>(iotdata, payload))</span><br><span class="line">      .<span class="title function_">then</span>(<span class="function">() =&gt;</span> <span class="title function_">cb</span>(<span class="literal">null</span>, &#123;<span class="attr">statusCode</span>: <span class="number">204</span>&#125;))</span><br><span class="line">      .<span class="title function_">catch</span>(<span class="function">(<span class="params">err</span>) =&gt;</span> <span class="title function_">cb</span>(err));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="Event-archival"><a href="#Event-archival" class="headerlink" title="Event archival"></a>Event archival</h3><p>Now it’s time to create a rule that inserts the events into a DynamoDB table.</p><p>template.yml</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line">[<span class="string">...</span>]</span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  [<span class="string">...</span>]</span><br><span class="line">  <span class="attr">EventTable:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::DynamoDB::Table&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AttributeDefinitions:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">AttributeName:</span> <span class="string">id</span></span><br><span class="line">        <span class="attr">AttributeType:</span> <span class="string">S</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">AttributeName:</span> <span class="string">timestamp</span></span><br><span class="line">        <span class="attr">AttributeType:</span> <span class="string">S</span></span><br><span class="line">      <span class="attr">KeySchema:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">AttributeName:</span> <span class="string">id</span></span><br><span class="line">        <span class="attr">KeyType:</span> <span class="string">HASH</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">AttributeName:</span> <span class="string">timestamp</span></span><br><span class="line">        <span class="attr">KeyType:</span> <span class="string">RANGE</span></span><br><span class="line">      <span class="attr">ProvisionedThroughput:</span></span><br><span class="line">        <span class="attr">ReadCapacityUnits:</span> <span class="number">5</span></span><br><span class="line">        <span class="attr">WriteCapacityUnits:</span> <span class="number">5</span></span><br><span class="line">  <span class="attr">EventTableRole:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span> </span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">&#x27;Allow&#x27;</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">Service:</span> <span class="string">&#x27;iot.amazonaws.com&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">      <span class="attr">Policies:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">&#x27;dynamodb&#x27;</span></span><br><span class="line">        <span class="attr">PolicyDocument:</span> </span><br><span class="line">          <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">          <span class="attr">Statement:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">            <span class="attr">Action:</span> <span class="string">&#x27;dynamodb:PutItem&#x27;</span></span><br><span class="line">            <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:dynamodb:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:table/$&#123;EventTable&#125;&#x27;</span></span><br><span class="line">  <span class="attr">EventTableRule:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IoT::TopicRule&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">TopicRulePayload:</span></span><br><span class="line">        <span class="attr">Actions:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">DynamoDB:</span></span><br><span class="line">            <span class="attr">HashKeyField:</span> <span class="string">id</span></span><br><span class="line">            <span class="attr">HashKeyValue:</span> <span class="string">&#x27;$&#123;id&#125;&#x27;</span></span><br><span class="line">            <span class="attr">RangeKeyField:</span> <span class="string">timestamp</span> <span class="comment"># unfortunately this field is required by CloudFormation</span></span><br><span class="line">            <span class="attr">RangeKeyValue:</span> <span class="string">&#x27;$&#123;timestamp()&#125;&#x27;</span> <span class="comment"># unfortunately this field is required by CloudFormation</span></span><br><span class="line">            <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;EventTableRole.Arn&#x27;</span></span><br><span class="line">            <span class="attr">TableName:</span> <span class="type">!Ref</span> <span class="string">EventTable</span></span><br><span class="line">        <span class="attr">RuleDisabled:</span> <span class="literal">false</span></span><br><span class="line">        <span class="attr">Sql:</span> <span class="string">&quot;SELECT * FROM &#x27;event/+&#x27;&quot;</span></span><br></pre></td></tr></table></figure><h3 id="Buy-Event-Lambda"><a href="#Buy-Event-Lambda" class="headerlink" title="Buy Event Lambda"></a>Buy Event Lambda</h3><p>This is a rule that subscribes to the <code>event/buy</code> topic and triggers a Lambda function for every message.</p><p>template.yml</p><figure class="highlight nix"><table><tr><td class="code"><pre><span class="line">[...]</span><br><span class="line"><span class="params">Resources:</span></span><br><span class="line">  [...]</span><br><span class="line">  <span class="params">BuyEventLambda:</span></span><br><span class="line">    <span class="params">Type:</span> &#x27;AWS::Serverless::Function&#x27;</span><br><span class="line">    <span class="params">Properties:</span></span><br><span class="line">      <span class="params">Handler:</span> &#x27;buy-event-handler.analyze&#x27;</span><br><span class="line">      <span class="params">Runtime:</span> &#x27;nodejs6.<span class="number">10</span>&#x27;</span><br><span class="line">      <span class="params">Policies:</span></span><br><span class="line">      <span class="operator">-</span> <span class="params">Version:</span> &#x27;<span class="number">201</span>2-<span class="number">1</span>0-<span class="number">17</span>&#x27;</span><br><span class="line">        <span class="params">Statement:</span></span><br><span class="line">        <span class="operator">-</span> <span class="params">Effect:</span> Allow</span><br><span class="line">          <span class="params">Action:</span> &#x27;iot:Publish&#x27;</span><br><span class="line">          <span class="params">Resource:</span> <span class="operator">!</span>Sub &#x27;arn:aws:iot:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:topic<span class="operator">/</span>alert<span class="operator">/</span>fraud&#x27;</span><br><span class="line">        <span class="operator">-</span> <span class="params">Effect:</span> Allow</span><br><span class="line">          <span class="params">Action:</span> &#x27;iot:DescribeEndpoint&#x27;</span><br><span class="line">          <span class="params">Resource:</span> &#x27;<span class="operator">*</span>&#x27;</span><br><span class="line">      <span class="params">Events:</span></span><br><span class="line">        <span class="params">IoT:</span></span><br><span class="line">          <span class="params">Type:</span> IoTRule</span><br><span class="line">          <span class="params">Properties:</span></span><br><span class="line">            <span class="params">Sql:</span> <span class="string">&quot;SELECT * FROM &#x27;event/buy&#x27;&quot;</span></span><br><span class="line">            <span class="params">AwsIotSqlVersion:</span> &#x27;<span class="number">201</span>6-<span class="number">0</span>3-<span class="number">23</span>&#x27;</span><br></pre></td></tr></table></figure><p>The is the implementation of the fraud detection that runs inside Lambda.</p><p>buy-event-handler.js</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> cache = <span class="built_in">require</span>(<span class="string">&#x27;./cache.js&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">publish</span>(<span class="params">iotdata, payload</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> iotdata.<span class="title function_">publish</span>(&#123;</span><br><span class="line">    <span class="attr">topic</span>: <span class="string">`alert/fraud`</span>,</span><br><span class="line">    <span class="attr">qos</span>: <span class="number">0</span>,</span><br><span class="line">    <span class="attr">payload</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(payload),</span><br><span class="line">  &#125;).<span class="title function_">promise</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">isFraud</span>(<span class="params">payload</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() &lt; <span class="number">0.5</span>); <span class="comment">// very simple implementation :)</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span>.<span class="property">analyze</span> = <span class="function">(<span class="params">payload, context, cb</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">JSON</span>.<span class="title function_">stringify</span>(payload));</span><br><span class="line">  <span class="title function_">isFraud</span>()</span><br><span class="line">    .<span class="title function_">then</span>(<span class="function"><span class="params">fraud</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (fraud === <span class="literal">true</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> cache.<span class="property">iotdata</span></span><br><span class="line">          .<span class="title function_">then</span>(<span class="function">(<span class="params">iotdata</span>) =&gt;</span> <span class="title function_">publish</span>(iotdata, payload));</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> fraud;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">    .<span class="title function_">then</span>(<span class="function">() =&gt;</span> <span class="title function_">cb</span>(<span class="literal">null</span>, &#123;<span class="attr">statusCode</span>: <span class="number">204</span>&#125;))</span><br><span class="line">    .<span class="title function_">catch</span>(<span class="function">(<span class="params">err</span>) =&gt;</span> <span class="title function_">cb</span>(err));</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="Fraud-archival"><a href="#Fraud-archival" class="headerlink" title="Fraud archival"></a>Fraud archival</h3><p>And here we define what happens whit messages that are published to the <code>alert/fraud</code> topic.</p><p>template.yml</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line">[<span class="string">...</span>]</span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  [<span class="string">...</span>]</span><br><span class="line"> <span class="attr">ArchiveFraudBucket:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::S3::Bucket&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">ArchiveFraudS3Role:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span> </span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">&#x27;Allow&#x27;</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">Service:</span> <span class="string">&#x27;iot.amazonaws.com&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">      <span class="attr">Policies:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">&#x27;s3&#x27;</span></span><br><span class="line">        <span class="attr">PolicyDocument:</span> </span><br><span class="line">          <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">          <span class="attr">Statement:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">            <span class="attr">Action:</span> <span class="string">&#x27;s3:PutObject&#x27;</span></span><br><span class="line">            <span class="attr">Resource:</span> <span class="type">!Sub</span> <span class="string">&#x27;arn:aws:s3:::$&#123;ArchiveFraudBucket&#125;/*&#x27;</span></span><br><span class="line">  <span class="attr">ArchiveFraudTopic:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::SNS::Topic&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span> &#123;&#125;</span><br><span class="line">  <span class="attr">ArchiveFraudTopicRole:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IAM::Role&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span> </span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">&#x27;Allow&#x27;</span></span><br><span class="line">          <span class="attr">Principal:</span></span><br><span class="line">            <span class="attr">Service:</span> <span class="string">&#x27;iot.amazonaws.com&#x27;</span></span><br><span class="line">          <span class="attr">Action:</span> <span class="string">&#x27;sts:AssumeRole&#x27;</span></span><br><span class="line">      <span class="attr">Policies:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">&#x27;s3&#x27;</span></span><br><span class="line">        <span class="attr">PolicyDocument:</span> </span><br><span class="line">          <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">          <span class="attr">Statement:</span> </span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">            <span class="attr">Action:</span> <span class="string">&#x27;sns:Publish&#x27;</span></span><br><span class="line">            <span class="attr">Resource:</span> <span class="type">!Ref</span> <span class="string">ArchiveFraudTopic</span></span><br><span class="line">  <span class="attr">ArchiveFraudRule:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::IoT::TopicRule&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">TopicRulePayload:</span></span><br><span class="line">        <span class="attr">Actions:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">S3:</span></span><br><span class="line">            <span class="attr">BucketName:</span> <span class="type">!Ref</span> <span class="string">ArchiveFraudBucket</span></span><br><span class="line">            <span class="attr">Key:</span> <span class="string">&#x27;$&#123;id&#125;.json&#x27;</span></span><br><span class="line">            <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;ArchiveFraudS3Role.Arn&#x27;</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Sns:</span></span><br><span class="line">            <span class="attr">MessageFormat:</span> <span class="string">RAW</span></span><br><span class="line">            <span class="attr">RoleArn:</span> <span class="type">!GetAtt</span> <span class="string">&#x27;ArchiveFraudTopicRole.Arn&#x27;</span></span><br><span class="line">            <span class="attr">TargetArn:</span> <span class="type">!Ref</span> <span class="string">ArchiveFraudTopic</span></span><br><span class="line">        <span class="attr">AwsIotSqlVersion:</span> <span class="string">&#x27;2016-03-23&#x27;</span></span><br><span class="line">        <span class="attr">RuleDisabled:</span> <span class="literal">false</span></span><br><span class="line">        <span class="attr">Sql:</span> <span class="string">&quot;SELECT * FROM &#x27;alert/fraud&#x27;&quot;</span></span><br></pre></td></tr></table></figure><h3 id="Shared-caching-library"><a href="#Shared-caching-library" class="headerlink" title="Shared caching library"></a>Shared caching library</h3><p>And finally we have some shared code that is needed by both Lambdas. It creates a singleton of an <code>AWS.IotData</code> client with the needed variable <code>endpointAddress</code>.</p><p>cache.js</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="meta">&#x27;use strict&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">AWS</span> = <span class="built_in">require</span>(<span class="string">&#x27;aws-sdk&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> iotApiVersion = <span class="string">&#x27;2015-05-28&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> iot = <span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">Iot</span>(&#123;</span><br><span class="line">  <span class="attr">apiVersion</span>: iotApiVersion</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span>.<span class="property">iotdata</span> = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">  iot.<span class="title function_">describeEndpoint</span>(&#123;&#125;, <span class="function">(<span class="params">err, data</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) &#123;</span><br><span class="line">      <span class="title function_">reject</span>(err);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="title function_">resolve</span>(<span class="keyword">new</span> <span class="variable constant_">AWS</span>.<span class="title class_">IotData</span>(&#123;</span><br><span class="line">        <span class="attr">apiVersion</span>: iotApiVersion,</span><br><span class="line">        <span class="attr">endpoint</span>: data.<span class="property">endpointAddress</span></span><br><span class="line">      &#125;));</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>You may be surprised how little code was needed. Most of the stuff functionality is provided by AWS IoT and we only need to configure it trough the CloudFormation template and the SAM extensions.</p><h2 id="Deploy"><a href="#Deploy" class="headerlink" title="Deploy"></a>Deploy</h2><p>Make sure to update the AWS CLI. Otherwise, you may not have support for SAM:</p><figure class="highlight ada"><table><tr><td class="code"><pre><span class="line">sudo pip install <span class="comment">--upgrade awscli</span></span><br></pre></td></tr></table></figure><p>Select a region that supports the IoT service</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> <span class="attribute">AWS_DEFAULT_REGION</span>=us-east-1</span><br></pre></td></tr></table></figure><p>Then create a artifacts bucket:</p><figure class="highlight armasm"><table><tr><td class="code"><pre><span class="line"><span class="symbol">aws</span> <span class="built_in">s3</span> mb <span class="built_in">s3</span>:<span class="comment">//$USER-artifacts</span></span><br></pre></td></tr></table></figure><p>Clone the repository:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git <span class="built_in">clone</span> git@github.com:michaelwittig/sam-iot-example.git</span><br><span class="line"><span class="built_in">cd</span> sam-iot-example/</span><br></pre></td></tr></table></figure><p>Then install the Node.js dependencies:</p><figure class="highlight ada"><table><tr><td class="code"><pre><span class="line">npm install <span class="comment">--production</span></span><br></pre></td></tr></table></figure><p>Then deploy the template using SAM:</p><figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">aws cloudformation package <span class="attr">--template-file</span> template<span class="selector-class">.yml</span> <span class="attr">--output-template-file</span> template<span class="selector-class">.tmp</span><span class="selector-class">.yml</span> <span class="attr">--s3-bucket</span> <span class="string">&quot;$USER-artifacts&quot;</span></span><br><span class="line">aws cloudformation deploy <span class="attr">--template-file</span> template<span class="selector-class">.tmp</span><span class="selector-class">.yml</span> <span class="attr">--stack-name</span> sam-iot-example <span class="attr">--capabilities</span> CAPABILITY_IAM</span><br></pre></td></tr></table></figure><p>Done. You now have a running event system.</p><h2 id="Usage"><a href="#Usage" class="headerlink" title="Usage"></a>Usage</h2><ol><li>Go to <a href="https://console.aws.amazon.com/apigateway/" target="_blank" rel="noopener">https://console.aws.amazon.com/apigateway/</a> </li><li>Select <code>sam-iot-example</code></li><li>Select <code>/event</code> -&gt; <code>POST</code> and click <code>Test</code></li><li>Fill the Request Body with: <code>{&quot;type&quot;:&quot;buy&quot;,&quot;price&quot;:123.45}</code> and submit a few times</li><li>Go to <a href="https://console.aws.amazon.com/dynamodb/" target="_blank" rel="noopener">https://console.aws.amazon.com/dynamodb/</a></li><li>Select <code>Tables</code></li><li>Select the table that starts with <code>sam-iot-example-EventTable-</code></li><li>Click on Items, and you would see a few events</li><li>Go to <a href="https://console.aws.amazon.com/s3/" target="_blank" rel="noopener">https://console.aws.amazon.com/s3/</a></li><li>Select the bucket that starts with <code>sam-iot-example-archivefraudbucket-</code></li><li>You should see a few fraud events</li></ol><h2 id="Cleanup"><a href="#Cleanup" class="headerlink" title="Cleanup"></a>Cleanup</h2><p>Remove archived events from S3 Bucket by using the AWS Management Console. The name of the bucket starts with <code>sam-iot-example-archivefraudbucket-</code></p><p>Then remove the stack:</p><figure class="highlight dsconfig"><table><tr><td class="code"><pre><span class="line"><span class="string">aws</span> <span class="string">cloudformation</span> <span class="built_in">delete-stack</span> <span class="built_in">--stack-name</span> <span class="string">sam-iot-example</span></span><br></pre></td></tr></table></figure><p>Then remove the artifacts bucket:</p><figure class="highlight armasm"><table><tr><td class="code"><pre><span class="line"><span class="symbol">aws</span> <span class="built_in">s3</span> rb <span class="built_in">s3</span>:<span class="comment">//$USER-artifacts --force</span></span><br></pre></td></tr></table></figure><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><ul><li>The Serverless Application Model (SAM) make deploying CloudFormation templates very easy. I like it:)</li><li>Using topics to decouple your event system is very powerful. You can always add topic rules and actions if the business process changes or just someone else is interested in an event in the system. Keep the Lambda small. Better have more small Lambdas than few big ones.</li><li>The event system is very cost effective for workloads up to 1 mio messages per day. If you want to process more than that, I recommend that you first calculate the costs and compare them to an architecture using Kinesis.</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS Advent has started: Deploy your AWS Infrastructure Continuously</title>
      <link>https://cloudonaut.io/aws-advent-deploy-your-aws-infrastructure-continuously/</link>
      <description>
        <![CDATA[<p>AWS Advent is a yearly exploration of AWS in 24 parts contributed by volunteers from the AWS community. Follow <a href="https://x.com/aws]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/continuous-delivery/">Continuous Delivery</category>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <category domain="https://cloudonaut.io/tag/lambda/">lambda</category>
      <category domain="https://cloudonaut.io/tag/codepipeline/">codepipeline</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-advent-deploy-your-aws-infrastructure-continuously/</guid>
      <pubDate>Thu, 01 Dec 2016 09:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>AWS Advent is a yearly exploration of AWS in 24 parts contributed by volunteers from the AWS community. Follow <a href="https://x.com/awsadvent" target="_blank" rel="noopener">@AWSAdvent</a> and get inspired on a daily basis. </p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2016/12/aws-advent@730w.webp 730w, /images/2016/12/aws-advent@730w2x.webp 1460w, /images/2016/12/aws-advent@610w.webp 610w, /images/2016/12/aws-advent@610w2x.webp 1220w, /images/2016/12/aws-advent@450w.webp 450w, /images/2016/12/aws-advent@450w2x.webp 900w, /images/2016/12/aws-advent@330w.webp 330w, /images/2016/12/aws-advent@330w2x.webp 660w, /images/2016/12/aws-advent@545w.webp 545w, /images/2016/12/aws-advent@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2016/12/aws-advent@730w.jpg 730w, /images/2016/12/aws-advent@730w2x.jpg 1460w, /images/2016/12/aws-advent@610w.jpg 610w, /images/2016/12/aws-advent@610w2x.jpg 1220w, /images/2016/12/aws-advent@450w.jpg 450w, /images/2016/12/aws-advent@450w2x.jpg 900w, /images/2016/12/aws-advent@330w.jpg 330w, /images/2016/12/aws-advent@330w2x.jpg 660w, /images/2016/12/aws-advent@545w.jpg 545w, /images/2016/12/aws-advent@545w2x.jpg 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2016/12/aws-advent.jpg" alt="AWS Advent has started" title="AWS Advent has started"></picture></p><p>I open this year’s AWS Advent with a post on <strong>Deploy your AWS Infrastructure Continuously</strong></p><p>Continuously integrating and deploying your source code is the new standard in many successful Internet companies. But what about your infrastructure? Can you deploy a change to your infrastructure in an automated way? Can you run automated tests on your infrastructure to ensure that a change has no unintended side effects? In this post, I will show you how you can apply the same processes to your AWS infrastructure that you apply to your source code. You will learn how the AWS services CloudFormation, CodePipeline and Lambda can be combined to continuously deploy infrastructure.</p><p><a href="https://www.awsadvent.com/2016/12/01/deploy-your-aws-infrastructure-continuously/" target="_blank" rel="noopener">Enjoy the full article on www.awsadvent.com</a></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>DynamoDB pitfall: limited throughout due to hot partitions</title>
      <link>https://cloudonaut.io/dynamodb-pitfall-limited-throughput-due-to-hot-partitions/</link>
      <description>
        <![CDATA[<p>Is your application suffering from throttled or even rejected requests from DynamoDB? Even if you are not consuming all the provisioned r]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/dynamodb/">dynamodb</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/dynamodb-pitfall-limited-throughput-due-to-hot-partitions/</guid>
      <pubDate>Thu, 24 Nov 2016 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Is your application suffering from throttled or even rejected requests from DynamoDB? Even if you are not consuming all the provisioned read or write throughput of your table? You run into a common pitfall! Read on to learn how Hellen debugged and fixed the same issue.</p><p>Hellen is working on her first serverless application: a TODO list. She uses DynamoDB to store information about users, tasks, and events for analytics.</p><h2 id="Problem"><a href="#Problem" class="headerlink" title="Problem"></a>Problem</h2><p>Today users of Hellen’s TODO application started complaining: requests were getting slower and slower and sometimes even a cryptic error message <code>ProvisionedThroughputExceededException</code> appeared.</p><p>First Hellen checks the CloudWatch metrics showing the provisioned and consumed read and write throughput of her DynamoDB tables. Everything seems to be fine. The consumed throughput is far below the provisioned throughput for all tables as shown in the following figure.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2016/11/dynamodb-throughput-problem@730w.webp 730w, /images/2016/11/dynamodb-throughput-problem@730w2x.webp 1460w, /images/2016/11/dynamodb-throughput-problem@610w.webp 610w, /images/2016/11/dynamodb-throughput-problem@610w2x.webp 1220w, /images/2016/11/dynamodb-throughput-problem@450w.webp 450w, /images/2016/11/dynamodb-throughput-problem@450w2x.webp 900w, /images/2016/11/dynamodb-throughput-problem@330w.webp 330w, /images/2016/11/dynamodb-throughput-problem@330w2x.webp 660w, /images/2016/11/dynamodb-throughput-problem@545w.webp 545w, /images/2016/11/dynamodb-throughput-problem@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2016/11/dynamodb-throughput-problem@730w.png 730w, /images/2016/11/dynamodb-throughput-problem@730w2x.png 1460w, /images/2016/11/dynamodb-throughput-problem@610w.png 610w, /images/2016/11/dynamodb-throughput-problem@610w2x.png 1220w, /images/2016/11/dynamodb-throughput-problem@450w.png 450w, /images/2016/11/dynamodb-throughput-problem@450w2x.png 900w, /images/2016/11/dynamodb-throughput-problem@330w.png 330w, /images/2016/11/dynamodb-throughput-problem@330w2x.png 660w, /images/2016/11/dynamodb-throughput-problem@545w.png 545w, /images/2016/11/dynamodb-throughput-problem@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2016/11/dynamodb-throughput-problem.png" alt="Problem: Provisioned vs. Consumed Throughput" title="Problem: Provisioned vs. Consumed Throughput"></picture></p><p>Hellen is at lost. What is wrong with her DynamoDB tables? She starts researching for possible causes for her problem. Hellen finds detailed information about the <a href="http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GuidelinesForTables.html#GuidelinesForTables.Partitions" target="_blank" rel="noopener">partition behavior</a> of DynamoDB.</p><p>Her DynamoDB tables do consist of multiple partitions. The number of partitions per table depends on the provisioned throughput and the amount of used storage.</p><figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">MAX</span>( (Provisioned Read Throughput / <span class="number">3</span>,<span class="number">000</span>), (Provisioned Write Throughput / <span class="number">1</span>,<span class="number">000</span>), (Used Storage / <span class="number">10</span> GB)) </span><br></pre></td></tr></table></figure><p>Details of Hellen’s table storing analytics data:</p><figure class="highlight asciidoc"><table><tr><td class="code"><pre><span class="line">Provisioned Read Throughput: 2,000 Units</span><br><span class="line">Provisioned Write Throughput: 3,000 Units</span><br><span class="line"><span class="section">Used Storage: 2 GB</span></span><br><span class="line"><span class="section">========================================</span></span><br><span class="line">Number of Partitions: 3</span><br></pre></td></tr></table></figure><p>Provisioned throughput gets evenly distributed among all shards. Therefore the TODO application can write with a maximum of 1000 Write Capacity Units per second to a single partition.</p><p>Hellen is looking at the CloudWatch metrics again. The consumed write capacity seems to be limited to 1000 units. Exactly the maximum write capacity per partition.</p><p>Time to have a look at the data structure. Hellen is revising the data structure and DynamoDB table definition of the analytics table.</p><p>Hellen uses the <code>Date</code> attribute of each analytics event as the partition key for the table and the <code>Timestamp</code> attribute as range key as shown in the following example.</p><figure class="highlight mathematica"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="string">&quot;Date&quot;</span><span class="operator">:</span> <span class="string">&quot;2016-11-21&quot;</span><span class="operator">,</span> <span class="operator">&lt;-</span> <span class="built_in">Partition</span> <span class="built_in">Key</span></span><br><span class="line">  <span class="string">&quot;Timestamp&quot;</span><span class="operator">:</span> <span class="number">1479718722848</span><span class="operator">,</span> <span class="operator">&lt;-</span> <span class="built_in">Range</span> <span class="built_in">Key</span></span><br><span class="line">  <span class="string">&quot;EventType&quot;</span><span class="operator">:</span> <span class="string">&quot;TASK_CREATED&quot;</span><span class="operator">,</span></span><br><span class="line">  <span class="string">&quot;UserId&quot;</span><span class="operator">:</span> <span class="string">&quot;UUID&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="Solution"><a href="#Solution" class="headerlink" title="Solution"></a>Solution</h2><p>Now Hellen sees the light: as she uses the <code>Date</code> as the partition key, all write requests hit the same partition during a day. So the maximum write throughput of her application is around 1000 units per second.</p><p>Hellen changes the partition key for the table storing analytics data as follows.</p><figure class="highlight mathematica"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="string">&quot;Date&quot;</span><span class="operator">:</span> <span class="string">&quot;2016-11-21&quot;</span><span class="operator">,</span> </span><br><span class="line">  <span class="string">&quot;Timestamp&quot;</span><span class="operator">:</span> <span class="number">1479718722848</span><span class="operator">,</span> <span class="operator">&lt;-</span> <span class="built_in">Range</span> <span class="built_in">Key</span></span><br><span class="line">  <span class="string">&quot;EventType&quot;</span><span class="operator">:</span> <span class="string">&quot;TASK_CREATED&quot;</span><span class="operator">,</span></span><br><span class="line">  <span class="string">&quot;UserId&quot;</span><span class="operator">:</span> <span class="string">&quot;UUID&quot;</span> <span class="operator">&lt;-</span> <span class="built_in">Partition</span> <span class="built_in">Key</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>She uses the <code>UserId</code> attribute as the partition key and <code>Timestamp</code> as the range key. Writes to the analytics table are now distributed on different partitions based on the user. The application makes use of the full provisioned write throughput now.</p><p>Hellen opens the CloudWatch metrics again. The write throughput is now exceeding the mark of 1000 units and is able to use the whole provisioned throughput of 3000 units.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2016/11/dynamodb-throughput-solution@730w.webp 730w, /images/2016/11/dynamodb-throughput-solution@730w2x.webp 1460w, /images/2016/11/dynamodb-throughput-solution@610w.webp 610w, /images/2016/11/dynamodb-throughput-solution@610w2x.webp 1220w, /images/2016/11/dynamodb-throughput-solution@450w.webp 450w, /images/2016/11/dynamodb-throughput-solution@450w2x.webp 900w, /images/2016/11/dynamodb-throughput-solution@330w.webp 330w, /images/2016/11/dynamodb-throughput-solution@330w2x.webp 660w, /images/2016/11/dynamodb-throughput-solution@545w.webp 545w, /images/2016/11/dynamodb-throughput-solution@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2016/11/dynamodb-throughput-solution@730w.png 730w, /images/2016/11/dynamodb-throughput-solution@730w2x.png 1460w, /images/2016/11/dynamodb-throughput-solution@610w.png 610w, /images/2016/11/dynamodb-throughput-solution@610w2x.png 1220w, /images/2016/11/dynamodb-throughput-solution@450w.png 450w, /images/2016/11/dynamodb-throughput-solution@450w2x.png 900w, /images/2016/11/dynamodb-throughput-solution@330w.png 330w, /images/2016/11/dynamodb-throughput-solution@330w2x.png 660w, /images/2016/11/dynamodb-throughput-solution@545w.png 545w, /images/2016/11/dynamodb-throughput-solution@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2016/11/dynamodb-throughput-solution.png" alt="Solution: Provisioned vs. Consumed Throughput" title="Solution: Provisioned vs. Consumed Throughput"></picture></p><p>Problem solved, Hellen is happy! No more complaints from the users of the TODO list.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>Think twice when designing your data structure and especially when defining the partition key: <a href="http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GuidelinesForTables.html" target="_blank" rel="noopener">Guidelines for Working with Tables</a>. To get the most out of DynamoDB read and write request should be distributed among different partition keys. Otherwise, a hot partition will limit the maximum utilization rate of your DynamoDB table.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>A pattern for Continuously Deployed, Immutable and Stateful applications on AWS</title>
      <link>https://cloudonaut.io/a-pattern-for-continuously-deployed-immutable-and-stateful-applications-on-aws/</link>
      <description>
        <![CDATA[<p>If you are faced with the challenge of running a stateful application on AWS, you will recognize that many building blocks no longer work]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <category domain="https://cloudonaut.io/tag/ebs/">ebs</category>
      <category domain="https://cloudonaut.io/tag/route53/">route53</category>
      <guid isPermaLink="true">https://cloudonaut.io/a-pattern-for-continuously-deployed-immutable-and-stateful-applications-on-aws/</guid>
      <pubDate>Mon, 14 Nov 2016 21:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>If you are faced with the challenge of running a stateful application on AWS, you will recognize that many building blocks no longer work as before. </p><p>Usually I preach my clients stateless systems. Stateless means that that state is managed by another system. On AWS, this can be DynamoDB, RDS, S3, or other storage services. Managing a stateless system is less complex than managing a stateful system. You can terminate single instances at any time without loosing data. This makes deployments of new software versions simpler. It also allows you to scale horizontally by adding and removing EC2 instances.</p><p>But what about stateful applications like MongoDB, Kafka, Elasticsearch, and GlusterFS?</p><p>In this post, I will explain you how you can deploy stateful applications on AWS without losing the ability to run rolling updates and provide a highly available system.</p><h2 id="A-pattern-for-stateful-applications-on-AWS"><a href="#A-pattern-for-stateful-applications-on-AWS" class="headerlink" title="A pattern for stateful applications on AWS"></a>A pattern for stateful applications on AWS</h2><p>Every EC2 instance (aka node) that is part of the stateful cluster is supervised by a dedicated Auto Scaling Group (ASG). The ASG ensures that a node keeps running by comparing the current state with the desired state.<br>All the state is stored on EBS volumes dedicated to an ASG. The EBS volume is a standalone entity that is long lived and attached over the network.<br>Many stateful applications (like GlusterFS, MongoDB, ElasticSearch, Kafka, …) require or prefer fixed IP addresses for their nodes. A dedicated Elastic Network Interface (ENI) per node provides that fixed IP address.<br>Finally you need a place to register all the IP addresses of the nodes. So clients can connect to the stateful cluster. You can use a simple DNS entry with an A record for every node pointing to the IP address of the dedicated ENI.<br>The following figure demonstrated the idea.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2016/10/pattern1@730w.webp 730w, /images/2016/10/pattern1@730w2x.webp 1460w, /images/2016/10/pattern1@610w.webp 610w, /images/2016/10/pattern1@610w2x.webp 1220w, /images/2016/10/pattern1@450w.webp 450w, /images/2016/10/pattern1@450w2x.webp 900w, /images/2016/10/pattern1@330w.webp 330w, /images/2016/10/pattern1@330w2x.webp 660w, /images/2016/10/pattern1@545w.webp 545w, /images/2016/10/pattern1@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2016/10/pattern1@730w.png 730w, /images/2016/10/pattern1@730w2x.png 1460w, /images/2016/10/pattern1@610w.png 610w, /images/2016/10/pattern1@610w2x.png 1220w, /images/2016/10/pattern1@450w.png 450w, /images/2016/10/pattern1@450w2x.png 900w, /images/2016/10/pattern1@330w.png 330w, /images/2016/10/pattern1@330w2x.png 660w, /images/2016/10/pattern1@545w.png 545w, /images/2016/10/pattern1@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2016/10/pattern1.png" alt="A pattern for Stateful applications on AWS" title="A pattern for Stateful applications on AWS"></picture></p><p><em>The diagram was created with <a href="https://bit.ly/2PpMc2M" target="_blank" rel="noopener">Cloudcraft - Visualize your cloud architecture like a pro</a>.</em></p><p>Let’s look at how this can implemented on AWS.</p><h2 id="Stateful-Infrastructure-as-Code"><a href="#Stateful-Infrastructure-as-Code" class="headerlink" title="Stateful Infrastructure as Code"></a>Stateful Infrastructure as Code</h2><p>I love automation and I prefer tools provided by AWS over self managed tools whenever possible. Therfore CloudFormation is a perfect fit here:</p><ul><li>You can fully automate your infrastructure deployment with CloudFormation</li><li>Your infrastructure templates are versioned together with your code</li><li>You can deploy the templates in your CD pipeline</li><li>CloudFormation is fully managed by AWS and comes at no extra costs</li></ul><p>The previous figure can be divided into two responsibilities:</p><ul><li>Base Layer: Contains resources that are needed by all nodes</li><li>Node Layer: Contains resources that are need by a single node</li></ul><p>The following figure illustrates the two layers.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2016/10/pattern2@730w.webp 730w, /images/2016/10/pattern2@730w2x.webp 1460w, /images/2016/10/pattern2@610w.webp 610w, /images/2016/10/pattern2@610w2x.webp 1220w, /images/2016/10/pattern2@450w.webp 450w, /images/2016/10/pattern2@450w2x.webp 900w, /images/2016/10/pattern2@330w.webp 330w, /images/2016/10/pattern2@330w2x.webp 660w, /images/2016/10/pattern2@545w.webp 545w, /images/2016/10/pattern2@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2016/10/pattern2@730w.png 730w, /images/2016/10/pattern2@730w2x.png 1460w, /images/2016/10/pattern2@610w.png 610w, /images/2016/10/pattern2@610w2x.png 1220w, /images/2016/10/pattern2@450w.png 450w, /images/2016/10/pattern2@450w2x.png 900w, /images/2016/10/pattern2@330w.png 330w, /images/2016/10/pattern2@330w2x.png 660w, /images/2016/10/pattern2@545w.png 545w, /images/2016/10/pattern2@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2016/10/pattern2.png" alt="Stateful Infrastructure as Code: Two layers" title="Stateful Infrastructure as Code: Two layers"></picture></p><p><em>The diagram was created with <a href="https://bit.ly/2PpMc2M" target="_blank" rel="noopener">Cloudcraft - Visualize your cloud architecture like a pro</a>.</em></p><p>The base template only contains the Route 53 Record Set that is used by the nodes to advertise their IP addresses.<br>The node template contains a ASG, ENI and EBS Volume. Let’s look at the node template resources in more detail.</p><h2 id="Auto-Scaling-Group-ASG"><a href="#Auto-Scaling-Group-ASG" class="headerlink" title="Auto Scaling Group (ASG)"></a>Auto Scaling Group (ASG)</h2><p style="float: left;"><img src="/images/2016/10/asg.png" alt="Auto Scaling Group (ASG)"></p><p>The Auto Scaling Group observes a single EC2 Instance and does not do any scaling at all! Instead the ASG has two purposes.</p><ul><li>The ASG is used to automatically recover a failed EC2 Instance.</li><li>Additionally, the ASG and CloudFormation’s Update Policy are used to perform an update of the EC2 Instance by terminating the old version and starting a new EC2 Instance with the new version. When a fresh EC2 Instance starts it attaches the EBS volume and the ENI with a small script injected via User Data.</li></ul><h2 id="Elastic-Block-Store-EBS-Volume"><a href="#Elastic-Block-Store-EBS-Volume" class="headerlink" title="Elastic Block Store (EBS) Volume"></a>Elastic Block Store (EBS) Volume</h2><p style="float: left;"><img src="/images/2016/10/ebs.png" alt="Elastic Block Store (EBS) Volume"></p><p>The EBS volume is attached and mounted during start-up of the EC2 Instance. The volume does not go away when the EC2 Instance is terminated for any reason.The EBS volumes are used to outsource the state of the applications. I know about the performance penalty compared to Instance Store, but I don’t trade performance for automation and maintainability in an asynchronous system that runs in the background without direct user impact. The benefit is that the data replication that needs to be performed when the node comes back online is very small when using EBS volumes. If you run on instance store the node needs to replicate the whole data set from another node which can take some time.</p><h2 id="Elastic-Network-Interface-ENI"><a href="#Elastic-Network-Interface-ENI" class="headerlink" title="Elastic Network Interface (ENI)"></a>Elastic Network Interface (ENI)</h2><p style="float: left;"><img src="/images/2016/10/eni.png" alt="Elastic Network Interface (ENI)"></p><p>Distributed systems like Kafka communicate over network protocols based on the IP protocol (TCP&#x2F;IP or UDP). IP addresses are used to identify the source and destination of a packet. By default, the ASG assigns a random private IP addresses to the EC2 Instances when launching a new node. So it is very likely that the new node has a different IP address then the old one. The problem is, that the other nodes don’t know about that fact. They still try to contact the node on the old IP address. To bypass all problems regarded to that problem I decided to go with fixed private IP addresses. Therefore I create a standalone ENI for every ASG. During startup I attach the ENI as a second network interface (eht1) to the EC2 Instance.</p><h2 id="Route-53-Record-Set"><a href="#Route-53-Record-Set" class="headerlink" title="Route 53 Record Set"></a>Route 53 Record Set</h2><p style="float: left;"><img src="/images/2016/10/recordset.png" alt="Route 53 Record Set"></p><p>All the EC2 Instances must be announced the clients of the application. An easy solution is to put all the private IP addresses into a DNS A record. The clients must make a request the the DNS server and get back the list of all available IP addresses. I can also add new nodes and append them to the list. Clients that newly connect will than find the updated DNS entry. Older clients must either get informed by the system itself (e.g. nodes discover each over via a gossip protocol and announce the changes to their clients) or must reload the node list from time to time.  A DNS lookup like dig amazon.com will return the entries.<br>$ dig amazon.com<br>[…]<br>;; ANSWER SECTION:<br>amazon.com.                13        IN        A        54.239.17.6<br>amazon.com.                13        IN        A        54.239.25.200<br>amazon.com.                13        IN        A        54.239.25.208<br>amazon.com.                13        IN        A        54.239.26.128<br>amazon.com.                13        IN        A        54.239.17.7<br>amazon.com.                13        IN        A        54.239.25.192<br>[…]</p><h2 id="Limitations-and-benefits"><a href="#Limitations-and-benefits" class="headerlink" title="Limitations and benefits"></a>Limitations and benefits</h2><ul><li>I use the described architecture to run MongoDB, Kafka, Elasticsearch and GlusterFS clusters. I don’t run more than 9 machines in those clusters. My solution does not scale to much bigger clusters. You end up with an unmanageable amount of Auto Scaling Groups.</li><li>I’m fully aware of the fact that EBS volumes are slower than Instance Store. But for some systems agility is more important than performance. E.g. systems that run asynchronously in the background.</li><li>I replace all my EC2 instances at least once per week because of patches and Chaos Monkey likely terminates one node per cluster per day. Therefore I know that my solution works in an (artificially) unstable environment.</li><li>I automatically deploy changes to the clusters in less than two hours. Each git push is potentially deployed to production but not before I run extensive smoke and performance tests in my staging environment.</li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>My Opinion on Serverless</title>
      <link>https://cloudonaut.io/my-opinion-on-serverless/</link>
      <description>
        <![CDATA[<p>I attended Serverlessconf in London to speak with people involved in the Serverless world. Vendors, Framework maintainers, and users like]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/category/serverless/">Serverless</category>
      <category domain="https://cloudonaut.io/tag/serverless/">serverless</category>
      <guid isPermaLink="true">https://cloudonaut.io/my-opinion-on-serverless/</guid>
      <pubDate>Wed, 09 Nov 2016 20:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>I attended Serverlessconf in London to speak with people involved in the Serverless world. Vendors, Framework maintainers, and users like myself. I reflected on the discussed topics and here it is: my opinion.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2016/11/opinion@730w.webp 730w, /images/2016/11/opinion@730w2x.webp 1460w, /images/2016/11/opinion@610w.webp 610w, /images/2016/11/opinion@610w2x.webp 1220w, /images/2016/11/opinion@450w.webp 450w, /images/2016/11/opinion@450w2x.webp 900w, /images/2016/11/opinion@330w.webp 330w, /images/2016/11/opinion@330w2x.webp 660w, /images/2016/11/opinion@545w.webp 545w, /images/2016/11/opinion@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2016/11/opinion@730w.png 730w, /images/2016/11/opinion@730w2x.png 1460w, /images/2016/11/opinion@610w.png 610w, /images/2016/11/opinion@610w2x.png 1220w, /images/2016/11/opinion@450w.png 450w, /images/2016/11/opinion@450w2x.png 900w, /images/2016/11/opinion@330w.png 330w, /images/2016/11/opinion@330w2x.png 660w, /images/2016/11/opinion@545w.png 545w, /images/2016/11/opinion@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2016/11/opinion.png" alt="Opinion" title="Opinion"></picture></p><h2 id="Beta-vs-Stable"><a href="#Beta-vs-Stable" class="headerlink" title="Beta vs Stable"></a>Beta vs Stable</h2><p>I can’t believe that business rely on beta software such as Azure Functions (in Preview) or Google Cloud Functions (in Alpha). There is a reason why providers attach the beta label to their software. Because it’s not stable! So please don’t run a business on top of that. Pet projects are more appropriate.</p><h2 id="Vendor-Evangelists"><a href="#Vendor-Evangelists" class="headerlink" title="Vendor Evangelists"></a>Vendor Evangelists</h2><p>Please question those Vendor Evangelists. They are 100% not neutral! They get paid to tell you that their product is great. Don’t spread their arguments without reflecting on them. It’s your mind. You have the freedom to think by yourself.</p><h2 id="Lambda-chains-vs-event-driven"><a href="#Lambda-chains-vs-event-driven" class="headerlink" title="Lambda chains vs event driven"></a>Lambda chains vs event driven</h2><p>I talked to many people and one hot topic was Lambda chains. A Lambda function calls a Lambda function calls a Lambda function.<br>If your Lambda needs to talk to other Lambdas a lot you may need to slice your problem better. Checkout bounded contexts to improve your modeling skills. Try to model your business process asynchronously by using an event driven design and avoid synchronous chains.</p><p>I can recommend the talk <a href="https://www.youtube.com/watch?v=SB4IAJbgWS8&index=29&list=PLnwBrRU5CSTmM4OsUzDaog2pqadQSxKBv" target="_blank" rel="noopener">Serverless for Developers: Tips for Your Next App</a> by <a href="https://awslambdainaction.com/" target="_blank" rel="noopener">Danilo Poccia</a>.</p><h2 id="Serverless-the-name"><a href="#Serverless-the-name" class="headerlink" title="Serverless - the name"></a>Serverless - the name</h2><p>I have not spoken to a single person who was not aware of the fact that the code still runs on servers. I recommend that we focus on a different topic than finding a name in the coming months.<br>Why not spend that energy to think about the event driven topic?</p><h2 id="Platform-is-what-matters"><a href="#Platform-is-what-matters" class="headerlink" title="Platform is what matters"></a>Platform is what matters</h2><p>AWS Lambda alone is only the tip of the ice berg. The value comes from the AWS platform. Use Lambda to glue AWS services together and you will realize the potential. When evaluating other providers this is what matters!</p><h2 id="I-want-to-fix-it-myself-syndrom"><a href="#I-want-to-fix-it-myself-syndrom" class="headerlink" title="I want to fix it myself syndrom"></a>I want to fix it myself syndrom</h2><blockquote><p>Oh my god: Lambda is down. I can’t fix it. I rely on AWS to fix it.</p></blockquote><p>That’s called specialization and is the driver for our wealth. Someone maintains my car. Someone manufactures iPhones. Someone produces my food. I have no idea how this works. That’s fine!<br>But I hardly believe that I’m better at what I do in my field compared to people outside of my field. So why should this not apply to the Lambda staff?</p><h2 id="There-is-still-Ops"><a href="#There-is-still-Ops" class="headerlink" title="There is still Ops"></a>There is still Ops</h2><p>Let me tell you what I have observed in various projects:</p><ul><li>There is less Ops in EC2 compared to run your own VMware solution.</li><li>There is less Ops in RDS compared to run your own Oracle database.</li><li>There is less Ops in Kinesis compared to your own Kafka cluster.</li></ul><p>The same is true for Lambda.<br>By the way: There is less Dev in Lambda compared to your Play&#x2F;Spring&#x2F;Express Framework as well. We get more productive. That’s fine.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>WordPress on AWS: smooth and pain free</title>
      <link>https://cloudonaut.io/wordpress-on-aws-smooth-and-pain-free/</link>
      <description>
        <![CDATA[<p>I’m not a fan of WordPress, as it is neither cloud-ready nor serverless. That’s why this blog runs on <a href="https://aws.amazon.com/clo]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/ec2/">ec2</category>
      <category domain="https://cloudonaut.io/tag/rds/">rds</category>
      <category domain="https://cloudonaut.io/tag/cloudfront/">cloudfront</category>
      <category domain="https://cloudonaut.io/tag/alb/">alb</category>
      <guid isPermaLink="true">https://cloudonaut.io/wordpress-on-aws-smooth-and-pain-free/</guid>
      <pubDate>Mon, 31 Oct 2016 19:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>I’m not a fan of WordPress, as it is neither cloud-ready nor serverless. That’s why this blog runs on <a href="https://aws.amazon.com/cloudfront/" target="_blank" rel="noopener">CloudFront</a> and <a href="https://aws.amazon.com/s3/" target="_blank" rel="noopener">S3</a> and is built by <a href="https://hexo.io/" target="_blank" rel="noopener">Hexo</a>. But <a href="https://w3techs.com/technologies/history_overview/content_management/all/y" target="_blank" rel="noopener">25% of all websites are proudly published with WordPress</a>. You will learn about the easiest way to run WordPress on AWS . Including fault tolerance and scalability.</p><p>The following figure shows the architecture of a fault tolerant and scalable WordPress environment on AWS.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2016/10/wordpress-overview@730w.webp 730w, /images/2016/10/wordpress-overview@730w2x.webp 1460w, /images/2016/10/wordpress-overview@610w.webp 610w, /images/2016/10/wordpress-overview@610w2x.webp 1220w, /images/2016/10/wordpress-overview@450w.webp 450w, /images/2016/10/wordpress-overview@450w2x.webp 900w, /images/2016/10/wordpress-overview@330w.webp 330w, /images/2016/10/wordpress-overview@330w2x.webp 660w, /images/2016/10/wordpress-overview@545w.webp 545w, /images/2016/10/wordpress-overview@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2016/10/wordpress-overview@730w.png 730w, /images/2016/10/wordpress-overview@730w2x.png 1460w, /images/2016/10/wordpress-overview@610w.png 610w, /images/2016/10/wordpress-overview@610w2x.png 1220w, /images/2016/10/wordpress-overview@450w.png 450w, /images/2016/10/wordpress-overview@450w2x.png 900w, /images/2016/10/wordpress-overview@330w.png 330w, /images/2016/10/wordpress-overview@330w2x.png 660w, /images/2016/10/wordpress-overview@545w.png 545w, /images/2016/10/wordpress-overview@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2016/10/wordpress-overview.png" alt="WordPress on AWS" title="WordPress on AWS"></picture></p><p>Read on to learn more!</p><h2 id="The-problem"><a href="#The-problem" class="headerlink" title="The problem"></a>The problem</h2><p>WordPress is a PHP application using two different data storages: a MySQL database and files on disk. Storing files on disk is a problem if you want to make use of a fleet of EC2 instances to run WordPress in a fault tolerant and scalable fashion.</p><p>The following example and figure are illustrating the problem.</p><ol><li>Mary is uploading a new image for her blog post. The image file is stored on EC2 instance number 1.</li><li>Mary is reading through her article. EC2 instance number 1 is answering her HTTP request to get the image.</li><li>Bob is reading Mary’s article. EC2 instance number 2 is answering his HTTP request with a <code>404 Not Found</code> error, as the image is stored only on EC2 instance number 1.</li></ol><p><img class="img-fluid" src="/images/2016/10/wordpress-stateful-server.gif" alt="WordPress stateful server" title="WordPress stateful server"></p><p>Tricky, you need to find a way to either synchronize files between all EC2 instances or outsource the files to a managed service.</p><h2 id="The-solution-1st-try"><a href="#The-solution-1st-try" class="headerlink" title="The solution, 1st try"></a>The solution, 1st try</h2><p>I tried to solve the problem of running WordPress on AWS before. <a href="/wordpress-on-aws-you-are-holding-it-wrong/">WordPress on AWS: you are holding it wrong</a> describes the solution in detail.</p><p>To summarize it briefly:</p><ul><li>Using a WordPress plugin to store user uploads like images on S3 instead of storing them on disk.</li><li>Disabling all other WordPress features that are writing files to disk: install and update plugins or themes, the auto-updater, and writing a <code>.htaccess</code> file.</li><li>Automating the process of installing WordPress with all of its plugins and themes during the boot process of each EC2 instance.</li></ul><p>But not being able to install plugins and themes and to use the auto-updater is inconvenient.</p><h2 id="The-solution-2nd-try"><a href="#The-solution-2nd-try" class="headerlink" title="The solution, 2nd try"></a>The solution, 2nd try</h2><p>The Elastic File System service (EFS) joined the AWS family in January 2016. EFS is a highly available and scalable network file system that you can connect to your EC2 instances by using the NFS protocol. The perfect place to outsource files.</p><p>The following figure shows how each EC2 instance running WordPress is connecting to EFS to read and write files. You don’t need to distinguish between user uploads and files belonging to WordPress itself.</p><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2016/10/wordpress-efs-only@730w.webp 730w, /images/2016/10/wordpress-efs-only@730w2x.webp 1460w, /images/2016/10/wordpress-efs-only@610w.webp 610w, /images/2016/10/wordpress-efs-only@610w2x.webp 1220w, /images/2016/10/wordpress-efs-only@450w.webp 450w, /images/2016/10/wordpress-efs-only@450w2x.webp 900w, /images/2016/10/wordpress-efs-only@330w.webp 330w, /images/2016/10/wordpress-efs-only@330w2x.webp 660w, /images/2016/10/wordpress-efs-only@545w.webp 545w, /images/2016/10/wordpress-efs-only@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2016/10/wordpress-efs-only@730w.png 730w, /images/2016/10/wordpress-efs-only@730w2x.png 1460w, /images/2016/10/wordpress-efs-only@610w.png 610w, /images/2016/10/wordpress-efs-only@610w2x.png 1220w, /images/2016/10/wordpress-efs-only@450w.png 450w, /images/2016/10/wordpress-efs-only@450w2x.png 900w, /images/2016/10/wordpress-efs-only@330w.png 330w, /images/2016/10/wordpress-efs-only@330w2x.png 660w, /images/2016/10/wordpress-efs-only@545w.png 545w, /images/2016/10/wordpress-efs-only@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2016/10/wordpress-efs-only.png" alt="WordPress with EFS" title="WordPress with EFS"></picture></p><p><em>The diagram was created with <a href="https://bit.ly/2PpMc2M" target="_blank" rel="noopener">Cloudcraft - Visualize your cloud architecture like a pro</a>.</em></p><p>As a network call is necessary every time your EC2 instances want to read a file from EFS, it is important to have file caching in place. </p><p>Especially for <code>.php</code> files, as WordPress needs to access a lot of <code>.php</code> files for each incoming request. Something you can solve with <a href="http://php.net/manual/en/book.opcache.php" target="_blank" rel="noopener">opcache</a> quickly.</p><p>Caching static files is necessary as well. Because each visitor of your WordPress blog will need to load the same <code>.css</code> and <code>.js</code> files a CDN like CloudFront is a perfect fit.</p><p>The following figure shows the complete architecture of WordPress on AWS based on these services:</p><ul><li>CloudFront: CDN for dynamic and static content</li><li>ELB (Elastic Load Balancer): load balancer forwarding requests to EC2 instances and terminating SSL</li><li>EC2 (Elastic Compute Cloud): virtual machines running the web servers</li><li>EFS (Elastic File System): storage for WordPress files (WordPress core, plugins, themes, and user uploads)</li><li>RDS (Relational Database Service): MySQL database</li></ul><p><picture class="img-fluid"><source type="image/webp" srcset="/images/2016/10/wordpress-overview@730w.webp 730w, /images/2016/10/wordpress-overview@730w2x.webp 1460w, /images/2016/10/wordpress-overview@610w.webp 610w, /images/2016/10/wordpress-overview@610w2x.webp 1220w, /images/2016/10/wordpress-overview@450w.webp 450w, /images/2016/10/wordpress-overview@450w2x.webp 900w, /images/2016/10/wordpress-overview@330w.webp 330w, /images/2016/10/wordpress-overview@330w2x.webp 660w, /images/2016/10/wordpress-overview@545w.webp 545w, /images/2016/10/wordpress-overview@545w2x.webp 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><source srcset="/images/2016/10/wordpress-overview@730w.png 730w, /images/2016/10/wordpress-overview@730w2x.png 1460w, /images/2016/10/wordpress-overview@610w.png 610w, /images/2016/10/wordpress-overview@610w2x.png 1220w, /images/2016/10/wordpress-overview@450w.png 450w, /images/2016/10/wordpress-overview@450w2x.png 900w, /images/2016/10/wordpress-overview@330w.png 330w, /images/2016/10/wordpress-overview@330w2x.png 660w, /images/2016/10/wordpress-overview@545w.png 545w, /images/2016/10/wordpress-overview@545w2x.png 1090w" sizes="(min-width: 1200px) 730px, (min-width: 992px) 610px, (min-width: 768px) 450px, (min-width: 576px) 330px, 545px"><img class="img-fluid" src="/images/2016/10/wordpress-overview.png" alt="WordPress on AWS" title="WordPress on AWS"></picture></p><p><em>The diagram was created with <a href="https://bit.ly/2PpMc2M" target="_blank" rel="noopener">Cloudcraft - Visualize your cloud architecture like a pro</a>.</em></p><h2 id="The-deployment"><a href="#The-deployment" class="headerlink" title="The deployment"></a>The deployment</h2><p>Do you want to run WordPress on AWS by using EFS as described above? I have created a CloudFormation template that you can use to create the needed cloud infrastructure within minutes including the following features:</p><ul><li>Reliable: Fault tolerant and scalable due to multi-AZ usage</li><li>Secure: HTTPS only</li><li>Fast: CDN caching static content</li><li>Growing: scalable file storage for endless user uploads</li></ul><p><a href="https://github.com/widdix/aws-cf-templates/blob/master/wordpress" target="_blank" rel="noopener">Learn more</a> about the CloudFormation template for running WordPress on AWS and launch your stack within minutes.</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>AWS CloudFormation Update Evaluation - YAML, Cross-Stack References, Simplified Substitution</title>
      <link>https://cloudonaut.io/aws-cloudformation-update-evaluation-yaml-cross-stack-references-simplified-substitution/</link>
      <description>
        <![CDATA[<p>In mid-September, AWS released a <a href="https://aws.amazon.com/blogs/aws/aws-cloudformation-update-yaml-cross-stack-references-simplifi]]>
      </description>
      <author>Andreas and Michael Wittig</author>
      <category domain="https://cloudonaut.io/tag/cloudformation/">cloudformation</category>
      <guid isPermaLink="true">https://cloudonaut.io/aws-cloudformation-update-evaluation-yaml-cross-stack-references-simplified-substitution/</guid>
      <pubDate>Mon, 24 Oct 2016 20:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>In mid-September, AWS released a <a href="https://aws.amazon.com/blogs/aws/aws-cloudformation-update-yaml-cross-stack-references-simplified-substitution/" target="_blank" rel="noopener">big update to CloudFormation</a>.</p><p>The update contained:</p><ul><li><strong>YAML Support</strong> – You can now write your CloudFormation templates in YAML.</li><li><strong>Cross Stack References</strong> – You can now export values from one stack and use them in another.</li><li><strong>Simplified Substitution</strong> – You can more easily embed variables in strings.</li></ul><p>After one month of using the new features, I want to share my learnings with you.</p><h2 id="YAML-Support"><a href="#YAML-Support" class="headerlink" title="YAML Support"></a>YAML Support</h2><p>During the last 4 weeks I discovered three main advantages of using YAML over JSON to describe my CloudFormation templates:</p><ol><li>Support for multi-line strings</li><li>It is possible to use comments within a template</li><li>YAML is more compact than JSON</li></ol><p>Let me explain them in more detail.</p><h3 id="Support-for-multi-line-strings"><a href="#Support-for-multi-line-strings" class="headerlink" title="Support for multi-line strings"></a>Support for multi-line strings</h3><p>The <code>UserData</code> property usually consists of many lines. In JSON there was no elegant way to express this. Instead, you used the <code>Fn::Join</code> function of CloudFormation.</p><figure class="highlight prolog"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;Resources&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;MyLC&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;Type&quot;</span>: <span class="string">&quot;AWS::AutoScaling::LaunchConfiguration&quot;</span>,</span><br><span class="line">      <span class="string">&quot;Properties&quot;</span>:</span><br><span class="line">        <span class="string">&quot;UserData&quot;</span>: &#123;<span class="string">&quot;Fn::Join&quot;</span>: [<span class="string">&quot;\n&quot;</span>], [</span><br><span class="line">          <span class="string">&quot;#!/bin/bash -x&quot;</span>,</span><br><span class="line">          <span class="string">&quot;/opt/aws/bin/cfn-init -v --stack my-stack --resource MyLC --region eu-west-1&quot;</span>,</span><br><span class="line">          <span class="string">&quot;/opt/aws/bin/cfn-signal -e $? --stack my-stac --resource MyASG --region eu-west-1&quot;</span></span><br><span class="line">        ]&#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><i class="fa fa-thumbs-up"></i> In YAML this looks much nicer:</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">MyLC:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::AutoScaling::LaunchConfiguration&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">UserData:</span> <span class="type">!Base64</span> <span class="string">|</span></span><br><span class="line"><span class="string">        #!/bin/bash -x</span></span><br><span class="line"><span class="string">        /opt/aws/bin/cfn-init -v --stack my-stack --resource MyLC --region eu-west-1</span></span><br><span class="line"><span class="string">        /opt/aws/bin/cfn-signal -e $? --stack my-stack --resource MyASG --region eu-west-1</span></span><br></pre></td></tr></table></figure><h3 id="Use-comments-within-a-template"><a href="#Use-comments-within-a-template" class="headerlink" title="Use comments within a template"></a>Use comments within a template</h3><p>In YAML you can add comments.</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">MyLC:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::AutoScaling::LaunchConfiguration&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="comment"># This how a comment looks like</span></span><br><span class="line">      <span class="attr">UserData:</span> <span class="type">!Base64</span> <span class="string">|</span></span><br><span class="line"><span class="string">        #!/bin/bash -x</span></span><br><span class="line"><span class="string">        /opt/aws/bin/cfn-init -v --stack my-stack --resource MyLC --region eu-west-1</span></span><br><span class="line"><span class="string">        /opt/aws/bin/cfn-signal -e $? --stack my-stack --resource MyASG --region eu-west-1</span></span><br></pre></td></tr></table></figure><p><i class="fa fa-thumbs-up"></i> Nothing fancy, but very helpful :)</p><h3 id="YAML-is-more-compact-than-JSON"><a href="#YAML-is-more-compact-than-JSON" class="headerlink" title="YAML is more compact than JSON"></a>YAML is more compact than JSON</h3><p>I converted all our <a href="https://github.com/widdix/aws-cf-templates" target="_blank" rel="noopener">Free Templates for AWS CloudFormation</a> from JSON to YAML. See how the number of lines changed:</p><table><thead><tr><th>template</th><th>JSON lines</th><th>YAML lines</th></tr></thead><tbody><tr><td>ec2&#x2F;ec2-auto-recovery</td><td>460</td><td>403 (-13%)</td></tr><tr><td>jenkins&#x2F;jenkins2-ha-agents</td><td>1636</td><td>1599 (-3%)</td></tr><tr><td>jenkins&#x2F;jenkins2-ha</td><td>747</td><td>656 (-13%)</td></tr><tr><td>security&#x2F;account-password-policy</td><td>160</td><td>161 (+0%)</td></tr><tr><td>security&#x2F;cloudtrail</td><td>134</td><td>101 (-25%)</td></tr><tr><td>security&#x2F;config</td><td>92</td><td>77 (-17%)</td></tr><tr><td>static-website&#x2F;static-website</td><td>147</td><td>183 (+24%)</td></tr><tr><td>vpc&#x2F;vpc-2azs</td><td>289</td><td>253 (-13%)</td></tr><tr><td>vpc&#x2F;vpc-3azs</td><td>356</td><td>306 (-15%)</td></tr><tr><td>vpc&#x2F;vpc-4azs</td><td>423</td><td>359 (-16%)</td></tr><tr><td>vpc&#x2F;vpc-nat-gateway</td><td>39</td><td>51 (+30%)</td></tr><tr><td>vpc&#x2F;vpc-nat-instance</td><td>518</td><td>446 (-14%)</td></tr><tr><td>wordpress&#x2F;wordpress-ha</td><td>670</td><td>602 (-11%)</td></tr></tbody></table><p><i class="fa fa-thumbs-up"></i> On average the templates get smaller.</p><h3 id="Ouch"><a href="#Ouch" class="headerlink" title="Ouch"></a>Ouch</h3><p><i class="fa fa-thumbs-down"></i> What I do not like is that there is more than one way to represent strings:</p><ul><li>Sometimes you need quotes; sometimes they are optional</li><li>Sometimes you need single quotes; sometimes you can use double quotes</li></ul><p>The rules seem to be <a href="http://stackoverflow.com/questions/3790454/in-yaml-how-do-i-break-a-string-over-multiple-lines/21699210#21699210" target="_blank" rel="noopener">more complicated</a> than just using double quotes all the time like in JSON. I finally ended up with using single quotes all the time unless the string contains only [a-zA-Z0-9]. However, that is just my personal style. It ends up in a total mess if multiple people work on a single template.</p><h2 id="Cross-Stack-References"><a href="#Cross-Stack-References" class="headerlink" title="Cross Stack References"></a>Cross Stack References</h2><p>Instead of putting everything in a single template it can make sense to split them up. One very common example is the VPC. You create one CloudFormation stack that contains your VPC. Each application that you run is also a stack, but they depend on the VPC stack. Before Cross Stack References you could solve this problem with Parameters.</p><h3 id="Application-template"><a href="#Application-template" class="headerlink" title="Application template"></a>Application template</h3><figure class="highlight smalltalk"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="comment">&quot;AWSTemplateFormatVersion&quot;</span>: <span class="comment">&quot;2010-09-09&quot;</span>,</span><br><span class="line">  <span class="comment">&quot;Parameters&quot;</span>: &#123;</span><br><span class="line">    <span class="comment">&quot;Subnet&quot;</span>: &#123;</span><br><span class="line">      <span class="comment">&quot;Description&quot;</span>: <span class="comment">&quot;Use Subnet output from vpc stack.&quot;</span>,</span><br><span class="line">      <span class="comment">&quot;Type&quot;</span>: <span class="comment">&quot;AWS::EC2::Subnet::Id&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">&quot;Resources&quot;</span>: &#123;</span><br><span class="line">    <span class="comment">&quot;VirtualMachine&quot;</span>: &#123;</span><br><span class="line">      <span class="comment">&quot;Type&quot;</span>: <span class="comment">&quot;AWS::EC2::Instance&quot;</span>,</span><br><span class="line">      <span class="comment">&quot;Properties&quot;</span>: &#123;</span><br><span class="line">        <span class="comment">&quot;SubnetId&quot;</span>: &#123;<span class="comment">&quot;Ref&quot;</span>: <span class="comment">&quot;Subnet&quot;</span>&#125;,</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>with Cross Stack References you can export values in one stack and import them in another stack. Let’s see how this works.</p><h3 id="VPC-template"><a href="#VPC-template" class="headerlink" title="VPC template"></a>VPC template</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">VPC:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::VPC&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">CidrBlock:</span> <span class="type">!Sub</span> <span class="string">&#x27;10.0.0.0/16&#x27;</span></span><br><span class="line">  <span class="attr">SubnetAPublic:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::Subnet&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AvailabilityZone:</span> <span class="type">!Select</span> [<span class="number">0</span>, <span class="type">!GetAZs</span> <span class="string">&#x27;&#x27;</span>]</span><br><span class="line">      <span class="attr">CidrBlock:</span> <span class="string">&#x27;10.0.0.0/20&#x27;</span></span><br><span class="line">      <span class="attr">VpcId:</span> <span class="type">!Ref</span> <span class="string">VPC</span></span><br><span class="line"></span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">Subnet:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&#x27;Subnet.&#x27;</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!Ref</span> <span class="string">SubnetAPublic</span></span><br><span class="line">    <span class="attr">Export:</span></span><br><span class="line">      <span class="attr">Name:</span> <span class="string">&#x27;vpc-subnet&#x27;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>The vpc stack now exports the subnet id under the name <code>vpc-subnet</code>.</p><h3 id="Application-template-1"><a href="#Application-template-1" class="headerlink" title="Application template"></a>Application template</h3><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&#x27;2010-09-09&#x27;</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">VirtualMachine:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">&#x27;AWS::EC2::Instance&#x27;</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">SubnetId:</span> <span class="type">!ImportValue</span> <span class="string">&#x27;vpc-subnet&#x27;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>The application stack imports the subnet id.</p><p><i class="fa fa-thumbs-up"></i> With Cross Stack References you can pass data from one stack to another.</p><h3 id="Ouch-1"><a href="#Ouch-1" class="headerlink" title="Ouch"></a>Ouch</h3><p><i class="fa fa-thumbs-down"></i> It is not possible to export and import a list of values at the moment. You can export a comma separated string, but you are not able to import that string as a list.</p><p><i class="fa fa-exclamation"></i> Keep in mind that you can not change exported values.</p><h2 id="Simplified-Substitution"><a href="#Simplified-Substitution" class="headerlink" title="Simplified Substitution"></a>Simplified Substitution</h2><p>Let’s again have a look at the <code>UserData</code> property. It usually not only consists of many lines, but it also references some resources. In JSON I used <code>Fn::Join</code> and <code>Ref</code> for this like crazy:</p><figure class="highlight nix"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;Resources&quot;</span>: &#123;</span><br><span class="line">    <span class="string">&quot;MyLC&quot;</span>: &#123;</span><br><span class="line">      <span class="string">&quot;Type&quot;</span>: <span class="strin