<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Route to Cloud - by HPF]]></title><description><![CDATA[Learning Could Technologies |
Deploying IT Systems | 
Building and Flying FPV drones]]></description><link>https://blogs.houessou.com</link><generator>RSS for Node</generator><lastBuildDate>Mon, 13 Apr 2026 12:03:16 GMT</lastBuildDate><atom:link href="https://blogs.houessou.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[VPC Lattice in Production]]></title><description><![CDATA[Hey guys,
I just renewed my AWS Advanced Networking Specialty, and let me tell you… this one really humbled me.
Between all the new services, the subtle networking tricks, and the fact that I hadn’t done real deep network hands-on in a little while, ...]]></description><link>https://blogs.houessou.com/vpc-lattice-in-production</link><guid isPermaLink="true">https://blogs.houessou.com/vpc-lattice-in-production</guid><category><![CDATA[AWS]]></category><category><![CDATA[hybrid network]]></category><category><![CDATA[networking]]></category><category><![CDATA[Microservices]]></category><dc:creator><![CDATA[Pierre-Francois H]]></dc:creator><pubDate>Fri, 12 Dec 2025 15:07:21 GMT</pubDate><content:encoded><![CDATA[<p>Hey guys,</p>
<p>I just renewed my <strong>AWS Advanced Networking Specialty</strong>, and let me tell you… this one really humbled me.</p>
<p>Between all the new services, the subtle networking tricks, and the fact that I hadn’t done real deep network hands-on in a little while, I definitely had a few cold sweats during the exam.</p>
<p>And to make it worse, I waited <strong>10 full days</strong> for the results; easily the most stressful part.</p>
<p>But hey, it’s done. I passed. 🙌🏾</p>
<p>And now that the pressure is over, I wanted to share something interesting.</p>
<p>During a recent project, and also while preparing for the exam, I rediscovered a service that I think doesn’t get the love it deserves: <strong>Amazon VPC Lattice</strong>.</p>
<p>It ended up helping us solve a <em>very</em> tricky service-exposure problem in a <strong>complex hybrid, multi-hop network architecture with overlapping CIDRs</strong>.</p>
<p>If you’ve ever dealt with that kind of setup, you already know: this is where traditional patterns start showing their limits.</p>
<p>So let’s talk about it.</p>
<h2 id="heading-context-and-challenge">Context and challenge</h2>
<p>Let me describe what we were working with.</p>
<p>The client operates a multi-site enterprise network. Multiple physical locations, each with its own network equipment, all interconnected through an internal enterprise backbone.</p>
<p>Here's the complexity: <strong>every site uses the same internal IP ranges</strong>, with overlapping CIDRs across the board. To enable cross-site communication, each site exposes services using a pool of routable IPs combined with NAT.</p>
<p>One site, <strong>Site A</strong>, has a hybrid connection to AWS via Direct Connect. In AWS, we have multiple VPCs (Dev, Test, Prod, and a shared endpoints VPC) all connected through a Transit Gateway.</p>
<p>Site A has full connectivity to all its services - both on-premises and in AWS. Other sites can consume Site A's on-prem services through the routable IP + NAT setup. Everything works as expected.</p>
<p><strong>The new objective:</strong></p>
<blockquote>
<p>Enable other sites on the enterprise network to consume services running in AWS, via Site A's Direct Connect connection, while minimizing impact on the existing landing zone infrastructure.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765478821806/258bad78-c6a8-4e1b-b5b9-3831837c1271.png" alt class="image--center mx-auto" /></p>
<p><strong>The challenges we needed to address:</strong></p>
<ol>
<li><p>AWS private IP ranges are not routable from the enterprise network</p>
</li>
<li><p>Risk of IP overlap between AWS and the enterprise network</p>
</li>
<li><p>Need for bidirectional connectivity with integrated DNS resolution</p>
</li>
</ol>
<p>The traditional approaches - chaining NAT devices, allocating more routable IPs (not even an option in our case), or setting up multiple ALBs with custom DNS - all had significant drawbacks in terms of complexity or operational overhead.</p>
<p>We needed a solution that could abstract the service exposure, handle routing elegantly, and integrate smoothly with the existing architecture.</p>
<p>That's where VPC Lattice came in.</p>
<h2 id="heading-what-is-vpc-lattice">What is VPC Lattice?</h2>
<p>Amazon VPC Lattice is a managed application networking service that fundamentally changes how we think about service-to-service communication.</p>
<p>At its core, VPC Lattice abstracts away the traditional networking complexity. Instead of dealing with IP addresses, routing tables, and load balancers, you work with <a target="_blank" href="https://docs.aws.amazon.com/vpc-lattice/latest/ug/service-networks.html"><strong>service networks</strong></a> - logical application layer constructs that handle connectivity, security, and observability for you.</p>
<p>Here's what makes it powerful:</p>
<p><strong>Protocol flexibility:</strong> It supports HTTP, HTTPS, gRPC, TLS, and TCP. Whether you're running REST APIs, microservices, or legacy TCP applications, Lattice has you covered.</p>
<p><strong>Compute agnostic:</strong> Works with ECS, EKS, EC2, Lambda - basically any AWS compute service. Also works with on-prem resources via IP address. You're not locked into a specific deployment pattern.</p>
<p><strong>Security built-in:</strong> Native integration with AWS IAM for authentication and authorization. You can implement zero-trust principles without building custom proxy layers.</p>
<p><strong>Hybrid-ready:</strong> And this is the key part for our use case - VPC Lattice can connect services across VPCs, accounts, and even between AWS and on-premises environments.</p>
<p>Read more about VPC Lattice <a target="_blank" href="https://aws.amazon.com/blogs/networking-and-content-delivery/amazon-vpc-vattice-modernize-and-simplify-your-enterprise-network-architectures/">here</a>.</p>
<p>VPC Lattice handles four main connectivity patterns:</p>
<ul>
<li><p>Connecting applications within a single AWS Region</p>
</li>
<li><p>Enabling hybrid connectivity between AWS and on-premises systems</p>
</li>
<li><p>Managing internet-based application access to AWS services</p>
</li>
<li><p>Facilitating cross-region application communication</p>
</li>
</ul>
<p>For our scenario, that second pattern - hybrid connectivity - was exactly what we needed.</p>
<h2 id="heading-solution-overview">Solution overview</h2>
<p>Here's how we used VPC Lattice to solve the problem.</p>
<p>Instead of exposing individual AWS services through traditional NAT or load balancers, we created a <strong>VPC Lattice service network</strong> that acts as a controlled gateway layer.</p>
<p>The architecture looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765507756721/6e35548c-8348-45ff-a33c-fb4b56f49bdd.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-the-flow">The flow</h3>
<ol>
<li><p>Services in AWS (ECS tasks, Lambda functions, EKS workloads, RDS databases) are registered as targets in VPC Lattice</p>
</li>
<li><p>Each service gets a Lattice service endpoint with its own DNS name</p>
</li>
<li><p>VPCs are associated with the Lattice service network - this creates network connectivity without requiring complex routing</p>
</li>
<li><p>The Lattice service network is exposed to Site A through a single, stable endpoint</p>
</li>
<li><p>Site A publishes this endpoint to the enterprise network using one routable IP (remember - we had limited routable IPs available)</p>
</li>
<li><p>Other sites (Site B, Site C) can now consume AWS services through standard DNS resolution and HTTP/HTTPS calls</p>
</li>
</ol>
<p>DNS resolution for on-premises consumers required a custom DNS configuration since remote sites can't directly resolve Lattice endpoints (this would resolve the non-routable IP of the Lattice endpoint):</p>
<ol>
<li><p>A remote site wants to call an AWS service: <code>service1.sitea.aws.cloud</code></p>
</li>
<li><p>The enterprise DNS returns the routable IP of Site A's NAT device - not an AWS IP</p>
</li>
<li><p>Traffic hits the NAT at Site A, which rewrites the source IP (from routable to non-routable internal IP)</p>
</li>
<li><p>The NAT forwards the request to the Lattice endpoint located in the internal perimeter VPC (reachable via Direct Connect and Transit Gateway)</p>
</li>
<li><p>Lattice inspects the host header and path, then routes the request to the appropriate target service in the correct VPC</p>
</li>
<li><p>The response flows back through the same path: <code>Lattice → TGW → DX → NAT → Enterprise Network → Remote Site</code></p>
</li>
</ol>
<h3 id="heading-what-this-gives-us">What this gives us</h3>
<p><strong>Single point of exposure:</strong> Only one routable IP needed at the Site A edge. All AWS services are accessed through the Lattice service network.</p>
<p><strong>No IP overlap issues:</strong> VPC Lattice operates at Layer 7 (application layer). Services are identified by DNS names, not IP addresses. This completely bypasses the overlapping CIDR problem.</p>
<p><strong>Integrated service discovery:</strong> Each service registered in Lattice gets a custom DNS entry in a private hosted zone shared across VPCs (check out this <a target="_blank" href="https://aws-solutions-library-samples.github.io/networking/amazon-vpc-lattice-automated-dns-configuration-on-aws.html">post</a> for more). For on-premises access, we used custom DNS that points to Site A's NAT, which then forwards to Lattice. This gave us consistent service names across both environments.</p>
<p><strong>Fine-grained access control:</strong> We can use IAM policies and Lattice auth policies to control exactly which services are accessible from on-premises, down to specific API paths if needed.</p>
<p><strong>Simplified routing:</strong> The Transit Gateway only needs to know how to reach the Lattice service network association. All the service-level routing is handled by Lattice itself.</p>
<p><strong>Scalability:</strong> Adding new services is trivial - register them in Lattice, update DNS, done. No firewall rules, no NAT configuration, no routing table updates.</p>
<h3 id="heading-why-this-worked">Why this worked</h3>
<p>The key insight here is that VPC Lattice abstracts the network layer complexity into application-layer service connectivity. Traditional approaches would have required:</p>
<ul>
<li><p>Multiple routable IPs (one per exposed service) - we didn't have them</p>
</li>
<li><p>Complex NAT chains to handle IP translation - operational nightmare</p>
</li>
<li><p>Custom DNS infrastructure to route requests - more moving parts</p>
</li>
<li><p>Manual routing table management across VPCs - doesn't scale</p>
</li>
</ul>
<p>VPC Lattice eliminated all of that.</p>
<p>We went from "how do we expose these 100+ services without running out of routable IPs and creating a routing mess" to "register services in Lattice, associate services, publish one endpoint."</p>
<p>The on-premises teams at Site B and Site C just got a list of DNS names exposing the services. From their perspective, they're making standard HTTPS calls. They don't know (or care) about the underlying AWS networking complexity.</p>
<p>And from our perspective, we have centralized visibility, IAM-based security, and the ability to add or remove services without touching the underlying network infrastructure.</p>
<h2 id="heading-alternative-approaches">Alternative approaches</h2>
<p>Before we settled on VPC Lattice, we evaluated the standard approach: combining Network Load Balancers (NLB) and Application Load Balancers (ALB).</p>
<p>The traditional flow would be:</p>
<p><code>DNS resolution → NAT at Site A → NLB in perimeter VPC → ALB → Target service</code></p>
<p>This requires configuring NAT per environment, creating target groups and listener rules per application at the ALB level, and managing routing across multiple layers.</p>
<p><strong>Why we didn't go this route:</strong></p>
<p>While the NLB+ALB approach offers very good performance (under 1ms latency) and cost, it came with deal-breakers.</p>
<p>Assuming the NLB and ALB live in the perimeter VPC, here's where the architecture breaks down:</p>
<ul>
<li><p>The NLB gives you a static IP for NAT mapping (maybe one per environment if traffic segregation is required)</p>
</li>
<li><p>The ALB provides listener rules based on host headers or paths - great for routing</p>
</li>
<li><p><strong>But</strong> ALB target groups only support EC2 instances, Lambda functions (in the same VPC/account), or IP addresses</p>
</li>
</ul>
<p>For services running in different VPCs or accounts - which is our exact scenario - you can't just point the ALB at an ECS service or an EKS pod directly. You need static IPs.</p>
<p>This forces application teams in other accounts/VPCs to deploy additional NLBs in their own environments just to get a static IP that the central ALB can target. But most modern services run on containers (EKS/ECS), which typically use ALBs with dynamic, non-static IPs.</p>
<p>So you end up with this mess:</p>
<ul>
<li><p>Central perimeter: NLB → ALB</p>
</li>
<li><p>Each application account/VPC: Another NLB just for IP stability → ALB (for the actual service) → ECS/EKS targets</p>
</li>
</ul>
<p>That's two layers of load balancers per service, duplicated infrastructure across accounts, and significant operational overhead just to work around ALB's targeting limitations.</p>
<p><strong>Other challenges:</strong></p>
<ul>
<li><p>Fragmented security: Policies scattered across NAT, multiple NLBs, multiple ALBs, and target security groups</p>
</li>
<li><p>Manual observability: No unified view. You're correlating logs across multiple load balancers, VPCs and accounts</p>
</li>
<li><p>High operational complexity: Every new service requires coordinating across network teams (NAT rules), central platform teams (perimeter ALB), and application teams (their own NLB)</p>
</li>
</ul>
<p>VPC Lattice eliminated the operational overhead, centralized security and observability management, and gave us the flexibility to scale.</p>
<h2 id="heading-key-recommendations">Key recommendations</h2>
<p>If you're considering VPC Lattice for hybrid connectivity:</p>
<ul>
<li><p><strong>Evaluate your architecture complexity</strong>. If you have services across multiple VPCs and accounts, Lattice eliminates the need for application teams to deploy extra infrastructure just to get static IPs for routing.</p>
</li>
<li><p><strong>Check protocol requirements</strong>. Lattice supports HTTP/HTTPS/gRPC and TCP. FTP and other legacy protocols need alternative paths.</p>
</li>
<li><p><strong>Enable observability from day one</strong>. CloudWatch Logs and access logging are Lattice's biggest advantages - don't waste them.</p>
</li>
<li><p><strong>Document DNS patterns clearly</strong>. The custom DNS + NAT flow for on-premises isn't intuitive. Clear docs save support tickets.</p>
</li>
<li><p><strong>Weigh cost vs. operational complexity</strong>. Lattice costs more but simplifies operations dramatically. Calculate both direct costs and operational overhead.</p>
</li>
<li><p><strong>Use IAM policies for service-level security</strong>. Don't rely solely on security groups. Lattice's auth policies enable fine-grained, identity-based access control.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[I've tried the AWS MCP Server]]></title><description><![CDATA[Hi guys!
So years ago, GenAI was emerging and I gave a presentation about how to apply Generative AI to everyday business processes. The goal was simple: showcase what was possible with services like Amazon Bedrock and demonstrate how AI could transf...]]></description><link>https://blogs.houessou.com/ive-tried-the-aws-mcp-server</link><guid isPermaLink="true">https://blogs.houessou.com/ive-tried-the-aws-mcp-server</guid><category><![CDATA[genai]]></category><category><![CDATA[mcp]]></category><category><![CDATA[AWS]]></category><dc:creator><![CDATA[Pierre-Francois H]]></dc:creator><pubDate>Tue, 12 Aug 2025 12:29:30 GMT</pubDate><content:encoded><![CDATA[<p>Hi guys!</p>
<p>So years ago, GenAI was emerging and I gave a presentation about how to apply Generative AI to everyday business processes. The goal was simple: showcase what was possible with services like <a target="_blank" href="https://aws.amazon.com/bedrock/?trk=82e18c1c-360a-4da4-bb66-5c3277617f54&amp;sc_channel=ps&amp;ef_id=Cj0KCQjwqebEBhD9ARIsAFZMbfyWcHLRnlKBAv-wALNE95JcJbawm05HanDTak4ZwKmDbdQ1OOrXihAaAtgREALw_wcB:G:s&amp;s_kwcid=AL!4422!3!692006005915!e!!g!!amazon%20bedrock!21054971261!157173597057&amp;gad_campaignid=21054971261&amp;gbraid=0AAAAADjHtp9nlz4RTXpAD2ulJc2KyKhz_&amp;gclid=Cj0KCQjwqebEBhD9ARIsAFZMbfyWcHLRnlKBAv-wALNE95JcJbawm05HanDTak4ZwKmDbdQ1OOrXihAaAtgREALw_wcB">Amazon Bedrock</a> and demonstrate how AI could transform how we work.</p>
<p>One scenario stuck with people:</p>
<blockquote>
<p>Mr. Nobody wants to talk to his AWS resources.<br />He speaks; a bot listens; it pulls context from the environment and, when asked, takes action.</p>
</blockquote>
<p>Back then, that was more vision than product. Amazon Q was still young and couldn’t reach into the console the way we wanted. If you needed an assistant for AWS infrastructure, you had to build your own.</p>
<p>Fast-forward to today: Amazon Q can now provide details about your resources right inside the console. The thing that felt “impossible” turned into a menu option. So why bother building your own at all?</p>
<p>Because the path from proof-of-concept to production reality teaches you <em>when to build, when to buy,</em> and how the underlying architecture is changing.</p>
<h2 id="heading-goals-amp-constraints">Goals &amp; Constraints</h2>
<p>For this scenario, my goals were simple:</p>
<ul>
<li><p>Make an application that will work simpler with AI</p>
</li>
<li><p>Boost day-to-day productivity</p>
</li>
<li><p>Plug in enterprise context (AWS resources across multiple accounts)</p>
</li>
<li><p>Keep the overall infrastructure fast and cheap enough to matter</p>
</li>
</ul>
<p>At that time, building a custom solution was the only way to explore this possibility. I knew I needed:</p>
<ul>
<li><p>An LLM to understand natural language requests (Claude with Amazon Bedrock)</p>
</li>
<li><p>Custom tools to interact with AWS services/resources</p>
</li>
<li><p>An agent to orchestrate everything together</p>
</li>
</ul>
<p>The plan on paper looked clean:</p>
<pre><code class="lang-plaintext">User → Agent → Custom Tools → AWS APIs → Answer/Action
</code></pre>
<p>I followed the standard pattern for AI agents at the time (at least to my knowledge then). I built custom Python functions to serve as tools for the agent:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">search_ec2_instances_by_tag</span>(<span class="hljs-params">tag_key</span>):</span>
    <span class="hljs-string">"""Custom tool to search EC2 instances by tag"""</span>
    ec2 = boto3.client(<span class="hljs-string">'ec2'</span>)
    response = ec2.describe_instances(
        Filters=[{<span class="hljs-string">'Name'</span>: <span class="hljs-string">f'tag:<span class="hljs-subst">{tag_key}</span>'</span>, <span class="hljs-string">'Values'</span>: [<span class="hljs-string">'*'</span>]}]
    )
    <span class="hljs-comment"># Process and return results...</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start_ec2_instance</span>(<span class="hljs-params">instance_id</span>):</span>
    <span class="hljs-string">"""Custom tool to start an EC2 instance"""</span>
    ec2 = boto3.client(<span class="hljs-string">'ec2'</span>)
    response = ec2.start_instances(InstanceIds=[instance_id])
    <span class="hljs-comment"># Handle response...</span>
</code></pre>
<p>Each tool required careful implementation, error handling, and testing. The LangChain integration looked like this:</p>
<pre><code class="lang-python">tools = [
    Tool(
        name=<span class="hljs-string">"search_ec2_instances_by_tag"</span>,
        func=search_ec2_instances_by_tag,
        description=<span class="hljs-string">"Lists EC2 instances by tag key"</span>
    ),
    Tool(
        name=<span class="hljs-string">"start_ec2_instance"</span>, 
        func=start_ec2_instance,
        description=<span class="hljs-string">"Starts an EC2 instance"</span>
    )
    <span class="hljs-comment"># ... more custom tools</span>
]

agent = initialize_agent(
    tools, 
    llm, 
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    memory=memory,
    verbose=<span class="hljs-literal">True</span>
)
</code></pre>
<p><strong>The tradeoffs</strong></p>
<p>This approach worked but it came with significant challenges:</p>
<ul>
<li><p>Every new AWS action was another tool to build, test, and document.</p>
</li>
<li><p>The wrappers had to keep up with AWS API changes.</p>
</li>
<li><p>Capability matched my free time, not business demand.</p>
</li>
<li><p>I was basically shipping a tiny, bespoke AWS CLI.</p>
</li>
</ul>
<p>Great for a talk and demos. Less great for production and scale.</p>
<h2 id="heading-from-custom-tools-to-mcp">From Custom Tools to MCP</h2>
<p>While Q matured inside the console, the <a target="_blank" href="https://docs.anthropic.com/en/docs/mcp"><strong>Model Context Protocol (MCP)</strong></a> landed with a different promise: standardize how AI apps talk to tools and data. No more bespoke plumbing for every integration. Just a protocol and servers that expose capabilities in a consistent way.</p>
<p>AWS Labs shipped two official MCP servers I cared about:</p>
<ul>
<li><p><strong>aws-api</strong>: access to AWS operations</p>
</li>
<li><p><strong>aws-docs</strong>: fast, contextual access to AWS documentation</p>
</li>
</ul>
<p>The architecture simplified overnight:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754947548146/50f8849a-48ee-4789-90ca-59bb6b1961c5.gif" alt class="image--center mx-auto" /></p>
<p>My code shrank while the surface area grew.</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SimpleAgent</span>:</span>
    <span class="hljs-string">"""Simplified agent that directly uses Bedrock and MCP servers."""</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, app_config, mcp_client, aws_session=None</span>):</span>
        self.app_config = app_config
        self.mcp_client = mcp_client
        self.aws_session = aws_session <span class="hljs-keyword">or</span> boto3.Session()
        self.bedrock_client = <span class="hljs-literal">None</span>
        self.conversation_history = []
        self._initialized = <span class="hljs-literal">False</span>
        self._last_tool_calls = []

    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">_analyze_intent</span>(<span class="hljs-params">self, user_message: str</span>) -&gt; Dict[str, Any]:</span>
        <span class="hljs-string">"""Analyze user intent and determine what tools to use."""</span>
        ...
        <span class="hljs-comment"># Get available tools</span>
        available_tools = self.mcp_client.get_available_tools()
        tools_description = self._format_tools_for_prompt(available_tools)

        <span class="hljs-comment"># Create intent analysis prompt with improved context awareness</span>
        ...
</code></pre>
<p><strong>What changed for me</strong></p>
<ul>
<li><p><strong>Fewer moving parts.</strong> I dropped a lot of LangChain-specific orchestration and custom wrappers.</p>
</li>
<li><p><strong>First-class AWS.</strong> Instead of my custom EC2 functions, I now have access to the full AWS CLI through the <code>aws-api</code> server</p>
</li>
<li><p><strong>Built-in documentation.</strong> The <code>aws-docs</code> server provides something I never implemented in my custom solution: intelligent documentation access.</p>
</li>
</ul>
<h2 id="heading-when-custom-still-wins">When Custom Still Wins</h2>
<p>Q is excellent when you live <em>inside</em> the console. But custom assistants still win when you need:</p>
<ul>
<li><p><strong>Cross-tool workflows.</strong> AWS + Jira + GitHub + PagerDuty in one thread.</p>
</li>
<li><p><strong>Multi-cloud or on-prem bridges.</strong> Q won’t run your GCP/Azure/VMware playbooks.</p>
</li>
<li><p><strong>Custom guardrails.</strong> Your change windows, naming rules, org policy, and hand-offs.</p>
</li>
<li><p><strong>Deep domain context.</strong> Proprietary docs, acronyms, runbooks, and tribal knowledge.</p>
</li>
</ul>
<p>In other words: when your workflow isn’t just “ask AWS something,” custom still pays off.</p>
<h2 id="heading-key-takeaways">Key Takeaways</h2>
<p>Building this custom AWS assistant taught me several lessons that I think will remain relevant for a long time (I know, AI is moving fast…):</p>
<ol>
<li><p><strong>Standards &gt; Snowflakes.</strong> MCP changed the shape of the problem. I write less glue and deliver more capability.</p>
</li>
<li><p><strong>Build → Buy → Blend.</strong> Building teaches you enough to know <em>what</em> to buy later and eventually what to keep custom.</p>
</li>
<li><p><strong>Reduce learning debt.</strong> The value of the proof-of-concept wasn’t the code; it was the intuition about where agents help and where they get in the way.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>More than the result, the lesson was understanding how the GenAI technology stack keeps evolving. Whether you're using Amazon Q (or similar), building custom solutions, or exploring MCP servers, understanding these architectural patterns helps you make better decisions about when to build, when to buy, and when to migrate.</p>
<p>The Model Context Protocol represents the current state of this evolution: standardized ways to integrate AI with external systems that remain valuable even as managed services continue expanding their capabilities.</p>
<hr />
<p><em>The complete implementation, including both the custom tool approach and the MCP-based solution, is available in my</em> <a target="_blank" href="https://github.com/hpfpv/genai-chatbot-bedrock-agents"><em>GitHub repository</em></a><em>.</em></p>
]]></content:encoded></item><item><title><![CDATA[Automate Blog Writing with AWS Bedrock & Step Functions]]></title><description><![CDATA[Hi guys!
Ever came back from a conference full of ideas... and zero time to write them down? That was me at AWS re:Invent 2024. I had notes, voice memos, and way too many ideas — but no time (or energy) to turn them into blog posts.
So I built a tool...]]></description><link>https://blogs.houessou.com/automate-blog-writing-with-aws-bedrock-and-step-functions</link><guid isPermaLink="true">https://blogs.houessou.com/automate-blog-writing-with-aws-bedrock-and-step-functions</guid><category><![CDATA[AWS]]></category><category><![CDATA[Amazon Bedrock]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[Prompt Engineering]]></category><category><![CDATA[serverless]]></category><dc:creator><![CDATA[Pierre-Francois H]]></dc:creator><pubDate>Tue, 03 Jun 2025 00:05:38 GMT</pubDate><content:encoded><![CDATA[<p>Hi guys!</p>
<p>Ever came back from a conference full of ideas... and zero time to write them down? That was me at <strong>AWS re:Invent 2024</strong>. I had notes, voice memos, and way too many ideas — but no time (or energy) to turn them into blog posts.</p>
<p>So I built a tool to do it for me.</p>
<p>Let me show you how I used <strong>Step Functions</strong>, <strong>Lambda</strong>, and <strong>Bedrock</strong> to automate the whole thing — from raw document to publish-ready blog post.</p>
<h2 id="heading-overview">Overview</h2>
<h3 id="heading-the-problem">The Problem</h3>
<p>Writing takes time. Especially when your ideas are trapped in voice recordings, transcripts, or long-form notes.</p>
<p>I needed a way to go from this:</p>
<blockquote>
<p><em>“Hey future me, remember to write about the GenAI session tomorrow…”</em></p>
</blockquote>
<p>To this:</p>
<blockquote>
<p>A clean, structured blog post with a nice intro, summary, and sections — ready to copy-paste.</p>
</blockquote>
<h3 id="heading-what-i-built">What I Built</h3>
<p>I created a <strong>document processing pipeline</strong> that can:</p>
<ul>
<li><p>Take in different types of files (audio, text, images)</p>
</li>
<li><p>Summarize the content with <strong>Amazon Bedrock</strong></p>
</li>
<li><p>Generate a blog post draft with structure and flow</p>
</li>
<li><p>Save it automatically — ready to review or publish</p>
</li>
</ul>
<p>It’s built entirely with <strong>serverless AWS services</strong>. No infra to manage.</p>
<h2 id="heading-architecture">Architecture</h2>
<p>Here’s the pipeline stack:</p>
<ul>
<li><p><strong>Amazon S3</strong>: File input (like <code>.txt</code>, <code>.jpeg</code>, <code>.mp3</code>, <code>.docx</code>) and output storage</p>
</li>
<li><p><strong>S3 Event Notifications</strong>: Automatically trigger the workflow when files are uploaded</p>
</li>
<li><p><strong>AWS Step Functions</strong>: Orchestrates the entire workflow with smart routing and parallel processing</p>
</li>
<li><p><strong>AWS Transcribe</strong>: Converts audio/video files to text</p>
</li>
<li><p><strong>AWS Rekognition</strong>: Extracts text and labels from images</p>
</li>
<li><p><strong>AWS Lambda</strong>: Multiple functions for preprocessing, content formatting, and output generation</p>
</li>
<li><p><strong>Amazon Bedrock (Claude)</strong>: AI-powered content analysis and blog post generation</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748833362206/7a41bd1c-9025-4829-b908-2a4bdc7d4034.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-the-flow">The Flow</h3>
<p>Here's how it works in real life:</p>
<ol>
<li><p><strong>Upload the files</strong> to S3 - text documents, audio recordings, images, or any content you want to turn into a blog post.</p>
</li>
<li><p>The upload triggers an <strong>AWS Step Functions</strong> workflow via <strong>S3 Event Notifications</strong>.</p>
</li>
<li><p><strong>Smart routing</strong> - The system automatically detects your file type and processes accordingly:</p>
<ul>
<li><p>Audio/video files get transcribed first</p>
</li>
<li><p>Images get processed for visual analysis</p>
</li>
<li><p>Text go straight to content processing</p>
</li>
</ul>
</li>
<li><p>All content (whether from text, transcribed audio, or image analysis) gets cleaned up and formatted consistently. This ensures everything is ready for the AI to work with</p>
</li>
<li><p>The formatted content goes to Amazon Bedrock which invokes <a target="_blank" href="https://us-east-1.console.aws.amazon.com/bedrock/home#/model-catalog/serverless/anthropic.claude-3-5-haiku-20241022-v1:0"><strong>Claude 3.5 Haiku</strong></a> <strong>to</strong> analyze the material and generate a polished blog post.</p>
</li>
<li><p>The system saves both a summary and the final blog post in markdown format, ready to publish.</p>
</li>
</ol>
<p>All done in a few minutes.</p>
<h3 id="heading-prompt-engineering">Prompt Engineering</h3>
<p>Prompt engineering was the key to unlocking the full potential of this workflow. I followed best practices from Anthropic to design a clear, structured prompt that delivers accurate output from Claude.</p>
<ul>
<li><p><strong>Defined Role</strong>: “You are an expert content analyst…” sets Claude’s persona and aligns its responses with the task.</p>
</li>
<li><p><strong>Clear Delimiters</strong>: &lt;transcript&gt;…&lt;/transcript&gt; separates input from instructions to avoid confusion.</p>
</li>
<li><p><strong>Precise Formatting Rules</strong>: Word limits, Markdown headers, and a strict JSON schema ensure reliable and consistent formatting.</p>
</li>
<li><p><strong>Example I/O</strong>: A sample transcript and expected JSON response provide Claude with a concrete model to follow.</p>
</li>
<li><p><strong>Strict Output Schema</strong>: By requiring exactly two escaped JSON fields (summary and blog_post), the output is always machine-readable and ready to use without post-processing.</p>
</li>
</ul>
<p>Here’s the prompt I used with <strong>Claude</strong> for this project:</p>
<pre><code class="lang-plaintext">You are an expert content analyst and technical writer. Your role is to transform transcripts into structured, accessible content for digital platforms.
   &lt;transcript&gt;
   [Insert transcript content here]
   &lt;/transcript&gt;

   Task Instructions
   Your task is to analyze the provided transcript and create two complementary outputs that serve different audience needs:
   1. Executive Summary: A concise overview for busy readers who need key takeaways quickly
   2. Detailed Blog Post: An in-depth article for readers seeking comprehensive understanding

   &lt;detailed_requirements&gt;
   Summary Requirements
   - Maximum 150 words
   - Capture the most important points and main themes
   - Write in clear, accessible language
   - Focus on actionable insights or key conclusions

   Blog Post Requirements  
   - Write in engaging, professional Markdown format
   - Use proper header hierarchy (hash for main title, double hash for sections, triple hash for subsections)
   - Include logical flow: compelling introduction → structured body content → strong conclusion  
   - Add at least one relevant media placeholder: ![Descriptive alt text](image-url-placeholder)
   - Incorporate technical details and examples from the transcript
   - Make content scannable with good formatting and clear transitions
   &lt;/detailed_requirements&gt;

   &lt;examples&gt;
   Example Input Transcript:
   "Today we discussed the implementation of microservices architecture at our company. We found that breaking down our monolithic application into smaller services improved our deployment speed by 40%. However, we also encountered challenges with service discovery and inter-service communication. The key lessons were: start small, invest in monitoring, and have a clear API strategy."

   Example Output:
   {
   "summary": "The team successfully implemented microservices architecture, achieving 40% faster deployments by breaking down their monolithic application. Key challenges included service discovery and inter-service communication. Critical success factors identified were: starting with small services, investing in comprehensive monitoring, and establishing a clear API strategy from the beginning.",
   "blog_post": "Microservices Migration: Lessons from the Trenches\n\nIntroduction\n\nTransitioning from monolithic to microservices architecture represents one of the most significant technical decisions modern development teams face...\n\nKey Results\n\n![Microservices Architecture Diagram](image-url-placeholder)\n\nOur migration delivered impressive results:\n- 40% improvement in deployment speed\n- Enhanced team autonomy and development velocity\n\nChallenges Encountered\n\nService Discovery\nOne of our biggest hurdles was implementing effective service discovery...\n\nConclusion\n\nOur microservices journey taught us that success requires careful planning, gradual implementation, and robust monitoring infrastructure."
   }
   &lt;/examples&gt;

   Processing Instructions
   Before generating your final response, take time to analyze the transcript systematically:

   &lt;thinking&gt;
   Think through these steps:
   1. What are the 3-5 most important points in this transcript?
   2. Who is the likely audience for this content?
   3. What technical concepts need explanation or context?
   4. What would make an engaging blog post structure for this topic?
   5. Where would visual elements (diagrams, charts, screenshots) add value?
   &lt;/thinking&gt;

   Output Format
   Return your response as a properly formatted JSON object with exactly these two fields. Ensure all JSON syntax is correct, with proper escaping of quotes and newlines:

   {
   "summary": "Your 150-word summary here",
   "blog_post": "Your complete Markdown blog post here"
   }

   If the transcript is unclear, incomplete, or lacks sufficient information for a meaningful analysis, include this in your summary and create a shorter blog post that acknowledges the limitations while extracting what value is possible.
</code></pre>
<h3 id="heading-why-step-functions-bedrock">Why Step Functions + Bedrock?</h3>
<p>Step Functions made it easy to manage the flow:</p>
<ul>
<li><p>Visual execution</p>
</li>
<li><p>Built-in retries</p>
</li>
<li><p>Clean separation between tasks</p>
</li>
</ul>
<p>Bedrock let me call <strong>Claude</strong> without hosting anything.</p>
<p>It’s fast, scalable, and reusable.</p>
<h2 id="heading-how-i-used-it-at-reinvent">How I Used It at re:Invent</h2>
<p>At re:Invent, I recorded voice notes every day.</p>
<p>At night, I dropped them into S3.</p>
<p>The next morning, I had a draft ready to post — with:</p>
<ul>
<li><p>An intro,</p>
</li>
<li><p>Key takeaways,</p>
</li>
<li><p>Structured sections,</p>
</li>
<li><p>And a nice little closing paragraph.</p>
</li>
</ul>
<p>I even used the drafts to help prepare LinkedIn posts faster. Huge win.</p>
<h2 id="heading-other-use-cases">Other Use Cases</h2>
<p>This pipeline is more than just blogging. You could use the same framework to:</p>
<ul>
<li><p>Turn meeting transcripts into reports.</p>
</li>
<li><p>Summarize research papers for internal newsletters.</p>
</li>
<li><p>Convert interviews into publish-ready articles.</p>
</li>
<li><p>Feed summaries into a chatbot or FAQ generator.</p>
</li>
</ul>
<p>Basically: <strong>turn unstructured input info into structured output</strong>.</p>
<h2 id="heading-wrap-up">Wrap-Up</h2>
<p>This little project saved me hours of post-event work. It’s also a great example of how to embed GenAI into your workflow, not just for fun, but for real productivity.</p>
<p>Whether you’re a cloud architect, a content creator, or somewhere in between, integrating Bedrock with automation tools like Step Functions opens up real possibilities.</p>
<p>Want to build your own version? The full source code is coming soon.</p>
]]></content:encoded></item><item><title><![CDATA[Exploring RAG Systems, Resilience, and AI at AWS re:Invent Days 3 & 4]]></title><description><![CDATA[AWS re:Invent 2024 continues to be an incredible journey of learning, innovation, and a few surprises. Here's my recap of days 3 and 4, filled with technical insights, hands-on labs, and even some live entertainment.
Day 3: Exploring AI Innovations a...]]></description><link>https://blogs.houessou.com/exploring-rag-systems-resilience-and-ai-at-aws-reinvent-days-3-4</link><guid isPermaLink="true">https://blogs.houessou.com/exploring-rag-systems-resilience-and-ai-at-aws-reinvent-days-3-4</guid><category><![CDATA[reinvent2024]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[AWS]]></category><category><![CDATA[reInvent]]></category><category><![CDATA[#AWSreInvent2024]]></category><dc:creator><![CDATA[Pierre-Francois H]]></dc:creator><pubDate>Sat, 07 Dec 2024 13:28:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733552279888/596b89c6-b58a-4132-b073-c6c31ae5ad57.avif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>AWS re:Invent 2024 continues to be an incredible journey of learning, innovation, and a few surprises. Here's my recap of days 3 and 4, filled with technical insights, hands-on labs, and even some live entertainment.</p>
<h2 id="heading-day-3-exploring-ai-innovations-and-practical-architectures">Day 3: Exploring AI Innovations and Practical Architectures</h2>
<h3 id="heading-operational-excellence">Operational Excellence</h3>
<p>The day started with an insightful workshop that showcased best practices and strategies - most of them relying on the use of GenAI or ML-powered services - to ensure workload stability, performance, and security while meeting organizational goals. Key points included:</p>
<ul>
<li><p>To ensure high performance and scalability, it's important to test workloads thoroughly before they go live. <a target="_blank" href="https://aws.amazon.com/solutions/implementations/distributed-load-testing-on-aws/"><strong>Distributed Load Testing</strong></a> (DLT) makes this easier by automating large-scale tests to find and fix potential bottlenecks.</p>
</li>
<li><p>To ensure resilience, eliminate single points of failure and test system strength. AWS tools like <a target="_blank" href="https://aws.amazon.com/resilience-hub/">Resilience Hub</a> and <a target="_blank" href="https://aws.amazon.com/fis/">Fault Injection Simulator</a> help check recovery plans. For proactive monitoring, we can use services like Amazon <a target="_blank" href="https://docs.aws.amazon.com/devops-guru/latest/userguide/welcome.html">DevOps Guru</a> to detect and solve issues before they affect operations.</p>
</li>
</ul>
<h3 id="heading-building-scalable-rag-applications-using-amazon-bedrock-and-knowledge-bases">Building Scalable RAG Applications Using Amazon Bedrock and Knowledge Bases</h3>
<p>This session focused on designing and scaling Retrieval-Augmented Generation (RAG) systems with Amazon Bedrock. It covered practical strategies, including:</p>
<p>• <strong>Data Optimization:</strong> Effective preprocessing techniques to streamline data pipelines for RAG systems.</p>
<p>• <strong>Knowledge Base Integration:</strong> Using external knowledge sources to enrich and improve LLM-generated outputs.</p>
<p>• <strong>Scalable:</strong> Infrastructure design patterns for building resilient systems capable of handling the unique demands of RAG-based applications.</p>
<p>The addition of <a target="_blank" href="https://aws.amazon.com/about-aws/whats-new/2024/12/amazon-bedrock-knowledge-bases-structured-data-retrieval/">structured data retrieval in Bedrock Knowledge</a> Bases aligns perfectly with RAG principles, simplifying the integration of complex datasets and enabling new possibilities for customer support, analytics, and content generation use cases.</p>
<h3 id="heading-amazon-nova-foundation-models">Amazon Nova Foundation Models</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733542826356/ee624055-b387-460a-9780-fe516fb67aa5.png" alt class="image--center mx-auto" /></p>
<p>Amazon announced a new family of foundation models called Amazon Nova, featuring four distinct models with different capabilities: <strong>Nova Micro</strong> - A text-only model with 128K context, <strong>Nova Light</strong> - A multimodal model for fast processing, <strong>Nova Pro</strong> - An advanced multimodal model, and <strong>Nova Premiere</strong> - The most capable model for complex reasoning.</p>
<p>This session highlighted <a target="_blank" href="https://aws.amazon.com/ai/generative-ai/nova/">Amazon’s Nova</a> series of AI foundation models, showcasing their advanced intelligence, fast processing speeds, excellent cost-effectiveness, and, most importantly, easy integration with Bedrock.</p>
<h2 id="heading-day-4-hands-on-labs-and-inspirational-keynotes">Day 4: Hands-On Labs and Inspirational Keynotes</h2>
<h3 id="heading-keynote-by-dr-werner-vogels-managing-complexity">Keynote by Dr. Werner Vogels: Managing Complexity</h3>
<p>Dr. Vogels delivered a standout keynote on designing for resilience and simplicity in modern architectures. Key insights:</p>
<ul>
<li><p><strong>Breaking Down Monoliths:</strong> Modular designs reduce complexity and allow for better fault isolation.</p>
</li>
<li><p><strong>Building for Failure:</strong> Systems should not only withstand failures but also adapt and recover from them.</p>
</li>
<li><p><strong>Simplicity in Innovation:</strong> Balancing innovation with operational simplicity ensures long-term scalability.</p>
</li>
</ul>
<p>His talk was inspiring and packed with actionable advice, providing a clear roadmap for handling complexity in cloud-native systems.</p>
<h3 id="heading-prompt-engineering-with-amazon-bedrock">Prompt Engineering with Amazon Bedrock</h3>
<p>This hands-on session was all about crafting better prompts to improve LLM responses. The steps involved:</p>
<ol>
<li><p><strong>Deploying a Single-Page Application:</strong> Using Amazon S3, AWS Lambda, and API Gateway for user interaction with Bedrock models.</p>
</li>
<li><p><strong>Experimenting with Prompts:</strong> Testing and fine-tuning prompts in an Amazon SageMaker notebook.</p>
</li>
<li><p><strong>Observing the Impact:</strong> Witnessing how small changes to prompts can significantly enhance the output quality.</p>
</li>
</ol>
<p>This lab showed how important prompt design is for getting the most out of generative AI. It was also my first experience with <strong>AWS SimuLearn</strong>, where you converse with an AI chatbot to identify technical and business requirements, and then transition to a self-paced lab to deploy and test the solution.</p>
<h3 id="heading-build-and-deploy-llm-tools-using-llm-agents">Build and Deploy LLM Tools Using LLM Agents</h3>
<p>This session focused on creating intelligent tools using Amazon Bedrock. Key activities included:</p>
<ul>
<li><p><strong>Agent Configuration:</strong> Actions Groups, linking APIs, and associating Lambda functions to create actionable agents.</p>
</li>
<li><p><strong>Testing and Deployment:</strong> Running scenarios to test agent behavior and deploying them for real-world use.</p>
</li>
<li><p><strong>Practical Integration:</strong> Techniques for embedding agents into existing workflows to improve automation and efficiency.</p>
</li>
</ul>
<p>It was a highly interactive session that showed how intelligent agents could drive innovation in business processes.</p>
<h2 id="heading-replay-party-a-night-of-fun-and-music">Replay Party: A Night of Fun and Music</h2>
<p>The day ended with the <strong>re:Play Party</strong>, a chance to unwind and have some fun. The highlight? Seeing <strong>Weezer perform live</strong>!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733551824202/7abaf3ec-f201-476d-99e4-9a67d4fe04b3.jpeg" alt class="image--center mx-auto" /></p>
<p>I also tried my hand at an RC car game and nearly won. These lighter moments added a personal touch to what has been an intellectually intense event.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733552034514/dea4442a-9610-4507-9292-ba333ff09160.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-reflections-and-key-takeaways"><strong>Reflections and Key Takeaways</strong></h2>
<p>Days 3 and 4 at AWS re:Invent have been packed with learning and hands-on practice. From exploring the latest AI designs to understanding the nuances of operational resilience, each session has left me with valuable insights. And, of course, the Replay Party was a perfect way to balance work and play.</p>
<p>As my time at re:Invent 2024 comes to the end, I’m grateful for the opportunity to learn, connect, and grow in this dynamic environment.</p>
]]></content:encoded></item><item><title><![CDATA[AWS re:Invent 2024: Insights from the First Two Days]]></title><description><![CDATA[AWS re:Invent 2024 has proven to be an exhilarating journey filled with deep learning, inspiring conversations, and plenty of walking! Here’s a recap of my first two days at this extraordinary event.
Day 1: Workshops and Networking
VPC Security Appro...]]></description><link>https://blogs.houessou.com/aws-reinvent-2024-insights-from-the-first-two-days</link><guid isPermaLink="true">https://blogs.houessou.com/aws-reinvent-2024-insights-from-the-first-two-days</guid><category><![CDATA[AWS]]></category><category><![CDATA[reinvent2024]]></category><category><![CDATA[#AWSreInvent2024]]></category><dc:creator><![CDATA[Pierre-Francois H]]></dc:creator><pubDate>Wed, 04 Dec 2024 07:37:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733297430575/d54d64d7-c579-41c8-9f9e-941014014083.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>AWS re:Invent 2024 has proven to be an exhilarating journey filled with deep learning, inspiring conversations, and plenty of walking! Here’s a recap of my first two days at this extraordinary event.</p>
<h2 id="heading-day-1-workshops-and-networking">Day 1: Workshops and Networking</h2>
<h3 id="heading-vpc-security-approaches">VPC Security Approaches</h3>
<p>The day kicked off with an intensive workshop on layered VPC security. This level 300 session provided hands-on experience with:</p>
<ul>
<li><p>Advanced routing configurations</p>
</li>
<li><p>Security layer implementation</p>
</li>
<li><p>Best practices for secure Amazon VPC deployment</p>
</li>
</ul>
<h3 id="heading-aws-control-tower-customizations">AWS Control Tower Customizations</h3>
<p>My second session of the morning explored Control Tower customizations. It focused on scaling account provisioning while maintaining security compliance, using frameworks like <a target="_blank" href="https://docs.aws.amazon.com/controltower/latest/userguide/aft-overview.html">AFT</a> and <a target="_blank" href="https://docs.aws.amazon.com/controltower/latest/userguide/cfct-overview.html">CfCT</a>. These tools help organizations scale account provisioning and enforce security compliance with ease.</p>
<p>The customization capabilities allow for dynamic adjustments, ensuring compliance without sacrificing agility.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733293977200/a147f4a1-6658-43a0-8ecb-501ef3334576.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-afternoon-deep-dive-hybrid-and-edge-networking-architectures">Afternoon Deep Dive: Hybrid and Edge networking architectures</h3>
<p>The sessions highlighted combining hybrid architectures, including Local Zones and Outposts, to meet latency and connectivity requirements. Discussions also covered physical connectivity considerations for Outposts deployments and enterprise-scale solutions.</p>
<p>A key takeaway was understanding hybrid and edge networking solutions like Local Zones and AWS Outposts, which bring computational capabilities closer to users for guaranteed low-latency performance. While both options can serve the same purpose, it is important to note that AWS Outposts requires active connectivity to the control plane - which resides in AWS VPC.</p>
<h2 id="heading-day-2-keynotes-certified-lounge">Day 2: Keynotes, Certified Lounge,</h2>
<h3 id="heading-keynote-by-matt-garman">Keynote by Matt Garman</h3>
<p>The second day started with an insightful <a target="_blank" href="https://www.youtube.com/watch?v=LY7m5LQliAo">keynote</a> by AWS CEO Matt Garman. He announced groundbreaking advancements in AI and cloud infrastructure. Highlights included the development of “Ultracluster,” a massive AI supercomputer built with AWS Trainium chips, and the launch of Trainium2 and Trainium3 chips, offering significant performance improvements. AWS also unveiled the “Nova” series of AI foundation models and introduced Aurora DSQL for faster database performance, reinforcing its leadership in innovation and scalability.</p>
<h3 id="heading-certified-lounge-a-quiet-break-and-cool-swag">Certified Lounge: A Quiet Break and Cool Swag</h3>
<p>Before heading to the Expo, I made a stop at the Certified Lounge. It was a quiet space to relax, network with fellow AWS-certified professionals, and collect some stickers to personalize (yet again) my laptop. This small yet meaningful moment added a personal touch to my re:Invent experience.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733298400588/ebc37725-cb82-440c-8da3-c4bc5a429fe1.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-technical-insights-sessions-attended">Technical Insights: Sessions Attended</h3>
<h3 id="heading-multi-tenant-serverless-architectures-for-agility">Multi-Tenant Serverless Architectures for Agility</h3>
<p>This session focused on strategies for optimizing multi-tenant serverless architectures to enhance agility.</p>
<p>Key discussion points included:</p>
<ul>
<li><p><strong>Network Isolation Strategies:</strong> Ensuring tenant data remains isolated in multi-tier SaaS applications.</p>
</li>
<li><p><strong>Routing Decisions:</strong> Techniques for efficient traffic routing between application tiers.</p>
</li>
<li><p><strong>Tenant Deployment Models:</strong> Exploring options for deploying tenant-specific resources.</p>
</li>
<li><p><strong>Connectivity Patterns:</strong> Leveraging tools like PrivateLink and AWS Lattice for secure and scalable service discovery and communication.</p>
</li>
</ul>
<p>This session highlighted how multi-tenant architectures can balance agility and security while scaling to meet customer demands.</p>
<h3 id="heading-robust-easy-to-scale-architecture-with-cells">Robust Easy-to-Scale Architecture with Cells</h3>
<p>One standout session explored <strong>cell-based architecture</strong>, a design approach aimed at improving scalability and resilience by isolating workloads into independent cells.</p>
<p><strong>Key principles included:</strong></p>
<ul>
<li><p><strong>Fault Isolation Boundaries:</strong> Limit the blast radius of failures to individual cells.</p>
</li>
<li><p><strong>Dimensional Monitoring:</strong> Drill down into site, AZ, or cell-specific metrics for better observability while reducing noise with aggregated alarms.</p>
</li>
<li><p><strong>Staggered Deployments:</strong> Test changes in individual cells before scaling to the entire environment.</p>
</li>
</ul>
<p>By adopting this methodology, organizations can build fault-tolerant systems and safely scale their infrastructure with minimal downtime.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733296672323/b196f626-3bba-431a-b3ee-7286f6e8baaa.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-reflections-and-key-takeaways">Reflections and Key Takeaways</h2>
<p>The first two days at AWS re:Invent 2024 have been exciting and full of learning. Meeting new people, exploring ideas, and seeing the energy around innovation have made it a great experience. I’m excited to see what the rest of the event has in store!</p>
]]></content:encoded></item><item><title><![CDATA[Countdown to AWS re:Invent 2024]]></title><description><![CDATA[As a Cloud Architect, AWS re:Invent is more than just another conference—it's a chance to explore AWS's latest innovations, learn from experts, and connect with industry peers. This year, I'm particularly excited about sessions on layered security, g...]]></description><link>https://blogs.houessou.com/countdown-to-aws-reinvent-2024</link><guid isPermaLink="true">https://blogs.houessou.com/countdown-to-aws-reinvent-2024</guid><category><![CDATA[#AWSreInvent2024]]></category><category><![CDATA[awsreinvent]]></category><category><![CDATA[AWS]]></category><category><![CDATA[CloudComputing]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[Hybrid Cloud]]></category><dc:creator><![CDATA[Pierre-Francois H]]></dc:creator><pubDate>Fri, 08 Nov 2024 04:11:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731038889478/3787f6b7-d87d-4560-845b-ec7233b2f3cd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As a Cloud Architect, AWS re:Invent is more than just another conference—it's a chance to explore AWS's latest innovations, learn from experts, and connect with industry peers. This year, I'm particularly excited about sessions on layered security, generative AI, hybrid cloud architectures, and resilience strategies.</p>
<p>Each day is filled with advanced content and hands-on labs that I can't wait to share with our team at <a target="_blank" href="https://www.cofomo.com/">Cofomo</a>.</p>
<h3 id="heading-day-1-mastering-network-security-and-enhancing-ai-powered-apps"><strong>Day 1: Mastering Network Security and Enhancing AI-Powered Apps</strong></h3>
<p>The conference kicks off with <strong>NET303-R</strong>, focusing on multi-layered security strategies for Amazon VPC. I’m looking forward to hands-on guidance on multi-account, multi-region VPC network security, Route 53 DNS Firewall, AWS Network Firewall, and traffic mirroring—key elements for improving network segmentation and secure data flow.</p>
<p>In session <strong>FWM301-R</strong>, I’m excited to learn about the new features of AWS Amplify, especially its integration with generative AI. As a newcomer to this field, I'm excited to see how Amplify, along with AWS CDK and Amazon Bedrock, can make it easier to build AI-powered applications and improve full-stack development for more engaging user experiences.</p>
<h3 id="heading-day-2-architecting-for-resilience-across-regions-and-scaling-multi-tenant-apps"><strong>Day 2: Architecting for Resilience Across Regions and Scaling Multi-Tenant Apps</strong></h3>
<p>One of the sessions I must attend, <strong>ARC309-R1</strong>, will cover the key considerations between single-region and multi-region architectures. With real-world failover scenarios, this session focuses on ensuring that our applications stay resilient in any situation.</p>
<p>Another highlight, <strong>SVS322</strong>, will dive into serverless multi-tenant architectures using AWS Lambda, Amazon SQS, and EventBridge, with a focus on agility and data isolation. I’m particularly interested in learning best practices for optimizing performance and scalability in multi-tenant environments.</p>
<h3 id="heading-day-3-generative-ai-and-operational-resilience"><strong>Day 3: Generative AI and Operational Resilience</strong></h3>
<p>In <strong>SUP401</strong>, AWS's managed generative AI services take center stage. The hands-on workshop will cover architecture readiness, anomaly detection, and debugging strategies, which are crucial for building operational resilience. This advanced workshop supports our goal of improving proactive risk management for client workloads.</p>
<h3 id="heading-day-4-building-ai-driven-tools-with-amazon-bedrock"><strong>Day 4: Building AI-Driven Tools with Amazon Bedrock</strong></h3>
<p>Day 4 will focus on using Amazon Bedrock to develop smart, personalized tools. In sessions <strong>TNC227</strong> and <strong>TNC231</strong>, I’ll explore how to leverage Bedrock and AWS Lambda to create LLM-driven applications. Learning about prompt engineering techniques will be a valuable addition to my toolkit for building more tailored and effective solutions.</p>
<p>And much more…</p>
<p>Each session offers practical insights, from boosting security and resilience to using AI-driven innovations. I'm excited to connect with AWS enthusiasts, gain new perspectives, and share valuable takeaways. Stay tuned for updates and session highlights!</p>
]]></content:encoded></item><item><title><![CDATA[How to Set Up a Secure Hybrid Network on AWS]]></title><description><![CDATA[Establishing a secure and scalable hybrid network is essential for organizations looking to extend their on-premises infrastructure to the cloud. A robust infrastructure forms the backbone of any successful cloud workload.
This article provides a gui...]]></description><link>https://blogs.houessou.com/how-to-set-up-a-secure-hybrid-network-on-aws</link><guid isPermaLink="true">https://blogs.houessou.com/how-to-set-up-a-secure-hybrid-network-on-aws</guid><category><![CDATA[AWS]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[networking]]></category><category><![CDATA[network]]></category><category><![CDATA[aws networking]]></category><category><![CDATA[cloud network design]]></category><dc:creator><![CDATA[Pierre-Francois H]]></dc:creator><pubDate>Mon, 22 Jul 2024 10:00:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1721663078740/cc5caeac-fa03-4e45-b859-32567f2e863b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Establishing a secure and scalable hybrid network is essential for organizations looking to extend their on-premises infrastructure to the cloud. A robust infrastructure forms the backbone of any successful cloud workload.</p>
<p>This article provides a guide to deploying a secure hybrid network on AWS using Terraform. It leverages services such as Transit Gateway, VPC, Direct Connect and/or S2S VPN, Route 53, and AWS Network Firewall to ensure robust security and centralized management.</p>
<h2 id="heading-architecture-overview">Architecture Overview</h2>
<h3 id="heading-target-architecture"><strong>Target Architecture</strong></h3>
<p>The architecture uses AWS Transit Gateway as a central hub, connecting multiple VPCs and on-premises networks in a hub-and-spoke model. This setup allows for centralized and dynamic routing, supporting up to 20 Gbps per connection and enabling efficient peering regionally and inter-regionally.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721585234716/0dfb79df-bcd2-4814-baa0-2f34767d96fe.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-key-components"><strong>Key Components</strong></h3>
<ol>
<li><p><strong>AWS Transit Gateway</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721585068682/2c88dea6-aae4-44be-9543-6a7369952c20.png" alt class="image--center mx-auto" /></p>
<p> The AWS Transit Gateway acts as a central hub, simplifying the network by connecting multiple VPCs and on-premises networks. This setup uses a hub-and-spoke model, where the Transit Gateway is the hub, and the connected networks are the spokes. Here are some key considerations for the Transit Gateway:</p>
<ul>
<li><p><strong>Routing:</strong> Transit Gateway uses route tables to manage traffic flow between connected VPCs and on-premises networks. Each attachment (VPC, VPN, Direct Connect) can be linked to a specific route table, allowing precise control over traffic flow.</p>
</li>
<li><p><strong>Static and Dynamic Routing:</strong> Transit Gateway supports both static and dynamic routing. Static routes are manually set, while dynamic routing uses Border Gateway Protocol (BGP) for automatic route updates, ensuring efficient traffic flow and less manual configuration.</p>
</li>
<li><p><strong>Appliance Mode:</strong> This feature ensures that both incoming and outgoing traffic pass through the same network interface and Availability Zone, maintaining consistent routing throughout the flow.</p>
</li>
<li><p><strong>Centralized Management:</strong> Efficient peering and centralized management are achieved via AWS Resource Access Manager (RAM).</p>
</li>
</ul>
</li>
<li><p><strong>VPC Sharing</strong></p>
<p> We already know what a VPC is. VPC sharing allows you to share VPCs with multiple AWS accounts, facilitating centralized management of network resources while reducing the number of VPCs needed. Some key features include:</p>
<ul>
<li><p><strong>Resource Sharing and Centralized Management:</strong> Subnets are shared across accounts, enabling resource management from a central account while maintaining security boundaries with Security Groups and Network ACLs (micro-segmentation).</p>
</li>
<li><p><strong>Cost Efficiency:</strong> By reducing the number of VPCs, VPC sharing helps lower the costs associated with inter-VPC data transfer fees.</p>
</li>
</ul>
</li>
</ol>
<p>    This design choice implies that different workloads ENIs stay in the same subnet or VPC. You can choose to deploy VPC per environment as displayed on the diagram or follow your organization's compliance rules.</p>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721585522952/6e3dd7ee-5e6f-4dc4-8800-f914d65534af.png" alt class="image--center mx-auto" /></p>
<ol start="3">
<li><p><strong>Centralized Ingress/Egress and Inspection</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721585733708/b0155cd1-a1a9-48f4-8575-7869ee264fb3.png" alt class="image--center mx-auto" /></p>
<p> To enhance security, the architecture centralizes ingress and egress traffic.</p>
<p> <strong>Egress Traffic</strong></p>
<ul>
<li><p><strong>NAT Gateway:</strong> Centralized NAT Gateway handles outbound traffic from private subnets. This setup reduces the need for multiple NAT Gateways, simplifying management and reducing costs. It is also easy to apply restrictive policies for workloads that do not allow access to the Internet.</p>
</li>
<li><p><strong>AWS Network Firewall:</strong> Outbound traffic is inspected by the AWS Network Firewall before reaching the NAT Gateway.</p>
</li>
</ul>
</li>
</ol>
<p>    <strong>Ingress Traffic</strong></p>
<ul>
<li><p><strong>Application Load Balancer (ALB) and AWS WAF:</strong> The ALB, combined with AWS WAF, protects against web exploits and bots, ensuring that only legitimate traffic reaches your applications. ALB <a target="_blank" href="https://aws.amazon.com/blogs/containers/how-to-leverage-application-load-balancers-advanced-request-routing-to-route-application-traffic-across-multiple-amazon-eks-clusters/">advanced request routing</a> can be used to route traffic to multiple workloads</p>
</li>
<li><p><strong>AWS Network Firewall:</strong> Inbound traffic is inspected by the Network Firewall in the Perimeter before reaching the application servers.</p>
</li>
</ul>
<p>    <strong>Inspection</strong></p>
<p>    VPC-to-VPC or OnPrem-to-VPC traffic can be inspected by implementing a centralized Inspection VPC. You can choose which traffic is inspected by setting the appropriate routes on the corresponding Transit Gateway routing table.</p>
<ol start="4">
<li><p><strong>Hybrid DNS Resolution</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721585821358/8c98b573-68eb-4693-8b7e-e2d43b9cfda0.png" alt class="image--center mx-auto" /></p>
<p> Effective DNS resolution is critical for hybrid networks. This architecture leverages AWS Route 53 for both internal and hybrid DNS resolution.</p>
<p> <strong>Private Hosted Zones</strong></p>
<ul>
<li><p><strong>Private Hosted Zones:</strong> These zones allow DNS records to be visible only within specific VPCs. By associating private hosted zones with VPCs, internal DNS resolution is enabled, ensuring that internal services can communicate efficiently.</p>
</li>
<li><p><strong>VPC Attachments:</strong> Private hosted zones can be attached to multiple VPCs, allowing for seamless DNS resolution across different network segments within the AWS environment.</p>
</li>
</ul>
</li>
</ol>
<p>    <strong>Route 53 Resolver</strong></p>
<ul>
<li><p><strong>Inbound Resolver:</strong> The inbound resolver endpoints enable DNS queries from on-premises networks to be resolved within AWS. This setup ensures that on-premises systems can resolve AWS internal DNS names.</p>
<p>  What happens is that DNS servers OnPrem forward DNS queries for your AWS private hosted zones to the Inbound resolver endpoint which has a network interface in the Endpoint VPC. All private hosted zones should be associated with the VPC Endpoint allowing DNS resolution.</p>
</li>
<li><p><strong>Outbound Resolver:</strong> The outbound resolver endpoints allow DNS queries from within AWS VPCs to be forwarded to external DNS servers. You can add Resolver rules for domains hosted on OnPrem.</p>
</li>
</ul>
<p>    <strong>Services Endpoint</strong></p>
<p>    Centralizing access to VPC private endpoints enables secure communication between VPCs and services such as Amazon S3, EKS, SSM, and other AWS services. This approach reduces the need for internet gateways, enhances security, and simplifies network architecture management.</p>
<h2 id="heading-deployment">Deployment</h2>
<p>In this section, we’ll provide an overview of deploying this architecture using Terraform. You can find the complete code in the <a target="_blank" href="https://github.com/hpfpv/aug-secure-hybrid-network">GitHub repository</a>.<br />As illustrated in the diagram, the deployment uses two AWS accounts: one for Network resources and another for Perimeter resources. Additionally, the on-premises resources are deployed in a separate region, with an EC2 instance acting as the customer gateway.</p>
<p>The Terraform code is structured into modules that handle different components of the architecture:</p>
<ol>
<li><p><strong>Transit Gateway</strong>: Sets up the Transit Gateway as well as its routing tables. The TGW is shared with the principals provided in the <code>ram_principals</code> variable.</p>
</li>
<li><p><strong>Perimeter VPC</strong>: Creates the VPC responsible for egress and ingress traffic handling, including Network Firewalls, NAT Gateways, and Application Load Balancers. The corresponding TGW route table is associated with this VPC.</p>
</li>
<li><p><strong>Endpoint VPC</strong>: Endpoint services are listed in the variable <code>services</code>. TGW route table propagation and association are also added in the configuration.</p>
</li>
<li><p><strong>Development and Production VPCs</strong>: Establishes isolated environments for development and production workloads, utilizing centralized VPC endpoints for connectivity to AWS Services. These VPCs are shared with the organization principals listed in the <code>subnet_sharing_principals</code> variable. TGW route table propagation and association are also added in the configuration.</p>
</li>
<li><p><strong>On-Premises VPC</strong>: Sets up a simulated on-premises network environment in a different region, including VPN connections for secure communication with cloud VPCs. We use <a target="_blank" href="https://www.strongswan.org/">strongSwan</a> and <a target="_blank" href="https://docs.cilium.io/en/stable/network/bird/">bird</a> with an EC2 instance to handle IPSec VPN and BGP propagation.</p>
</li>
</ol>
<p>This modular approach ensures that each part of the network is configured correctly and can be easily managed and updated. The architecture provides a robust and secure hybrid network foundation, facilitating seamless integration and efficient management of both cloud and on-premises resources.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This architecture provides a scalable and secure multi-VPC AWS network infrastructure using AWS Transit Gateway, VPC sharing, centralized ingress/egress inspection, and hybrid DNS resolution. By following this guide, organizations can extend their on-premises infrastructure to the AWS cloud, ensuring strong security and centralized management.</p>
<p>For further questions or detailed walkthroughs, feel free to reach out or leave a comment below. Happy networking!</p>
<p><em>Good to read:</em></p>
<p><a target="_blank" href="https://docs.aws.amazon.com/whitepapers/latest/building-scalable-secure-multi-vpc-network-infrastructure/welcome.html"><em>Building a Scalable and Secure Multi-VPC AWS Network Infrastructure</em></a></p>
<p><a target="_blank" href="https://aws.amazon.com/solutions/implementations/landing-zone-accelerator-on-aws/"><em>Landing Zone Accelerator on AWS</em></a></p>
<p><a target="_blank" href="https://docs.aws.amazon.com/prescriptive-guidance/latest/security-reference-architecture/architecture.html"><em>AWS Security Reference Architecture</em></a></p>
]]></content:encoded></item><item><title><![CDATA[Deploying a sample to-do app on AWS - Container Version]]></title><description><![CDATA[Hi guys! 
Recently I have been studying for the new AWS SysOps certification (which I passed already) and as you may already know, this exam is more on the practical side of things. Since I am not currently employed as a Cloud Engineer/Cloud Administ...]]></description><link>https://blogs.houessou.com/todo-app-on-ecs-aws</link><guid isPermaLink="true">https://blogs.houessou.com/todo-app-on-ecs-aws</guid><category><![CDATA[AWS]]></category><category><![CDATA[ECS]]></category><category><![CDATA[containers]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Pierre-Francois H]]></dc:creator><pubDate>Sun, 13 Feb 2022 23:35:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1644793979387/0022wmkHU.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi guys! </p>
<p>Recently I have been studying for the new AWS SysOps certification (which I passed already) and as you may already know, this exam is more on the practical side of things. Since I am not currently employed as a Cloud Engineer/Cloud Administrator, I decided to build and deploy a container version of my  <a target="_blank" href="https://blogs.houessou.com/sample-todo-app-aws">sample serverless to-do app</a> in other to prepare for the most technical aspects of the exam. </p>
<p>The application's goal remains the same - <em>allow logged-in users to manage their to-do list</em>. The underlining architecture however will change a lot, especially the backend.</p>
<p>Services like ECS and ECR, Elastic Load Balancers and Autoscaling Group, API Gateway, and Cognito will be leveraged. We will make sure that our resources are securely deployed behind a VPC with the proper subnet configuration (NACLs, Route Tables, and Security groups), and will deploy a highly available application across availability zones. We also want to build a resilient application, properly monitored and maintained with appropriate CloudWatch alarms and actions (auto repairs, scaling policies...).</p>
<h2 id="heading-overview-andamp-basic-functionality">Overview &amp; Basic Functionality</h2>
<p>Refer to my previous  <a target="_blank" href="https://blogs.houessou.com/sample-todo-app-aws">blog post</a> to get an overview of the application and learn more about its functionalities.
All the code can be found <a target="_blank" href="https://github.com/hpfpv/todo-app-ecs-aws-community">here</a>.
There is no running demo for this app but you can still check out the serverless version's UI <a target="_blank" href="https://todo.houessou.com">here</a>.</p>
<h2 id="heading-application-components">Application Components</h2>
<p>The biggest change with this version of the application is that we decided to deploy services backed by docker containers. And since we are deploying on AWS, <em>Elastic Container Service</em> is our choice to host our backend containers.
Along with ECS, we need to have a well-defined VPC with networks spanning multiple availability zones for HA.
The below image should provide a good overview of each layer of the app and especially the technical components involved in the backend.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1631737544541/-loHqk0vX.png" alt="app-components.png" /></p>
<p>Let's go through our backend layer components:</p>
<p><strong>Services container</strong></p>
<p>The application services (Main and File services) are coded as Python functions served via a Flask App. 
The Flask server is responsible for routing the requests to the corresponding functions i.e.</p>
<pre><code><span class="hljs-comment"># get todos for the provided path parameter userID</span>
@app.route(<span class="hljs-string">'/&lt;userID&gt;/todos'</span>, methods=[<span class="hljs-string">'GET'</span>])
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">getTodos</span><span class="hljs-params">(userID)</span></span>:
    foo bar...
    ...
    <span class="hljs-keyword">return</span> todolist     <span class="hljs-comment"># as JSON</span>
</code></pre><p>Even though both services use the same design, they are built-in 2 separate container images stored in the AWS Elastic Container Registry. 
The reason for that choice is for the purpose of decoupling - to isolate the services so that an issue with one container does not affect the whole application.</p>
<blockquote>
<p>When building the container image, make sure to provide a specific tag and version as it is going to be used and eventually modified (version) during deployments. 
You should also avoid using <strong>latest</strong> for your production images; it refers to the latest image built without a version specified. Your deployment/update pipeline should add a version to modified images.</p>
</blockquote>
<p><strong>ECS Service and Cluster</strong></p>
<p>ECS allows you to run and maintain a specified number of instances of a task definition simultaneously in an Amazon ECS cluster (logical group of services). This is called a service. It defines the deployment configuration for your tasks. I like to compare it to an autoscaling group for tasks/containers. It specifies where our tasks will run (instances that are part of the cluster) and also how they register themselves to the load balancer to receive traffic (ports, target groups).</p>
<p><strong>ECS Task</strong></p>
<p>This is the instantiation of the task definition where we specify how the service's containers should be built. The task definition contains information such as container image, environment variables (i.e. the different DynamoDB tables name), container port, and log configuration for each container.
I decided to define both service containers in one task definition even though they are not linked and do not share information. This will ensure that we always have the same number of containers per service per task. 
In the Kubernetes world, that will mean having both containers running inside a single pod so both can scale at the same time. I would compare a Task to a Pod.</p>
<blockquote>
<p>Depending on your application, you can choose to define one service container per task definition, where services will benefit from being independently scalable.</p>
</blockquote>
<p><strong>Application Load Balancer</strong></p>
<p>With a task definition registered, we are ready to provide the infrastructure needed for our backend. Rather than directly exposing our services to the Internet, we will provide an Application Load Balancer (ALB) to sit in front of our services tiers. This would enable our frontend website code to communicate with a single DNS name while our backend service would be free to elastically scale in and out based on demand or if failures occur and new containers need to be provisioned.</p>
<p>For this application, we want to create one target group per service - so two target groups in total. By doing this, we make sure that the health of our services is evaluated individually and that some unhealthy containers for the main service do not affect the files service.
Having two target groups will also help the load balancer identify what request goes to what containers (service) based on the path. The ALB Listener rules will help with that: <em>main service requests go to main service containers, files service requests go to files service containers</em>.</p>
<p><strong>Authentication</strong></p>
<p>Authentication is handled by AWS Cognito + API Gateway. We use a Cognito user pool to store users' data. When a user logs in and a session is established with the app, the session token and related data are stored at the Frontend and sent over to the API endpoints. API Gateway then validates the session token against Cognito and allows users to perform application operations.
We use the API endpoints as HTTP proxies which forward traffic to the ALB. Requests are routed based on their path to the appropriate service containers.</p>
<p><strong>Data Layer</strong></p>
<p>DynamoDB and S3 are used to store all todos and related data. Our Flask app will be performing all Database and S3 operations connecting to the table and bucket, and getting requests from the frontend. DynamoDB and S3 are serverless services that provide auto-scaling along with high availability and durability.</p>
<h2 id="heading-application-architecture">Application Architecture</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644837094240/XVtd3s9aA.png" alt="architecture.png" /></p>
<p>The main difference with the serverless version of this app is on the backend side. Instead of API + Lambda, we now have API + ALB + ECS + plus some magic to make them work together. We also have to manage how the ECS stack scales to accommodate traffic. 
Let's describe the backend.</p>
<h3 id="heading-vpc-and-components">VPC and components</h3>
<p>Before we get to define your ECS service and task, we need to build a secure and highly available infrastructure to host our containers.</p>
<ul>
<li>For the sake of cost-saving, we went with the default VPC and preexisting subnets. </li>
<li>A total of 6 subnets is required to achieve HA - 3 private and 3 public subnets. Make sure to set up your routing tables accordingly.</li>
<li>We configured 2 security groups: one for the ALB allowing ingress traffic from the internet, and one for the containers instances (EC2s hosting the containers) allowing ingress only from the ALB security group. Here, make sure to allow traffic from port 31000 to 61000 (those ports are probably used by the Load balancer to map target groups).</li>
</ul>
<h3 id="heading-iam">IAM</h3>
<p> We created 4 IAM roles as follows:</p>
<ul>
<li><p>ECS Service Role: Amazon ECS uses the service-linked role named AWSServiceRoleForECS to enable Amazon ECS to call AWS APIs on your behalf.
The AWSServiceRoleForECS service-linked role trusts the ecs.amazonaws.com service principal to assume the role.
The role permissions policy allows Amazon ECS to complete actions on resources such as <em>Rules which allow ECS to attach network interfaces to instances</em>, <em>Rules which allow ECS to update load balancers</em>, <em>Rules that let ECS interact with container images</em>, <em>Rules that let ECS create and push logs to CloudWatch</em>.</p>
</li>
<li><p>ECS Task Role: With IAM roles for Amazon ECS tasks, you can specify an IAM role that can be used by the containers in a task. This is where we defined access permissions to the DynamoDb table and the S3 bucket. You also need to add rules to <em>Allow the ECS Tasks to download images from ECR</em> and<br /><em>Allow the ECS tasks to upload logs to CloudWatch</em>.</p>
</li>
<li><p>EC2 Instance Role: This role is required for the container instance to be able to serve a cluster and also push logs to Cloudwatch.</p>
</li>
<li><p>The last role is for the application auto-scaling group for ECS which allows it to automatically scale resources (containers) based on a predefined scaling policy.</p>
</li>
</ul>
<h3 id="heading-application-load-balancer">Application Load Balancer</h3>
<p>The load balancer is deployed on the public subnets and will have the below components:</p>
<p><strong>2 Target Groups</strong></p>
<p>Since our application has 2 services, we need to create one target group for each, in other to forward requests to proper containers and also monitor the health of services independently. </p>
<p><strong>ALB Listner and Listerner Rules</strong></p>
<p>The listener rules will handle requests routing to the appropriate service based on a <em>path pattern</em>:</p>
<ul>
<li>requests with paths like '<em> **</em>/todos<em>** </em>' will be forwarded to the main service containers</li>
<li>requests with paths like '<em> **</em>/files<em>** </em>' will be forwarded to the files service containers</li>
</ul>
<h3 id="heading-auto-scaling">Auto-scaling</h3>
<p>We need to implement auto-scaling in two places:</p>
<p><strong>Application auto-scaling</strong></p>
<p>How do we scale our service containers when there is a traffic spike? Application auto-scaling.
We created a CloudWatch Alarm to check for the <strong>HTTPCode_ELB_5XX_Count</strong> metric on the load balancer and trigger a scaling policy when the count is 10 for a period of 60 seconds.
When triggered, the application scaling policy doubles the number of tasks, which in our case scales both services at the same time.</p>
<p><strong>EC2 auto-scaling</strong></p>
<p>So they have been a lot of HTTPCode_ELB_5XX errors and our containers have increased in number to accommodate the traffic. Containers run on EC2 and more containers mean fewer resources available for our container instances. We need to set up an auto-scaling group to spin up new instances when necessary. The container Instances will be deployed in the private subnets.</p>
<h3 id="heading-api-gateway">API Gateway</h3>
<p>You may ask yourself why we need an API gateway while we already have the ALB which can be accessed on the frontend to serve requests.
Well, the reason for having an API gateway here is to handle authentication with Cognito. It is also quite easier to set CORS in the API gateway.
After authorizing the request against Cognito, the API will forward it to ALB which acts as an HTTP proxy. </p>
<p>Now that we have a description of how the components work together, we can discuss the deployment methods for the application.</p>
<h3 id="heading-security">Security</h3>
<p>Security groups are defined so that only the load balancer can talk to the containers. No traffics coming from the internet is directly routed to the containers. The backend is isolated in private networks.
Also, requests must be authorized by API Gateway before they get to the load balancer.</p>
<blockquote>
<p>With the current configs, the backend sends data over the internet to retrieve/store data from DynamoDB and S3. You can do it in a more secure way by implementing VPC endpoints for the DynamoDB table and the S3 bucket. All backend traffic will then reside in the private networks.</p>
</blockquote>
<h2 id="heading-iac-and-deployment-pipeline">IaC and Deployment Pipeline</h2>
<p>The will be 4 different pipelines to deploy our application: one per backend service, one for core resources of the backend, and one for the frontend. We decided to use a Github repository and Github actions for workflows.</p>
<p><strong>Frontend</strong></p>
<p>The frontend pipeline is pretty straightforward. The code is pushed to the S3 website bucket whenever the frontend folder is updated in the repository.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644791875665/OSSpYh87j.png" alt="frontend-pipeline.png" /></p>
<p><strong>Backend core components</strong></p>
<p>By core components, I mean VPC and related resources, ALB, ECS Cluster service and task, Launch configuration for EC2, API gateway, IAM roles...
All resources are defined inside a SAM template. There is also a different template to spin up the website (CloudFront, S3, OAI).
Every time the templates changed, a Cloudformation changeset is generated and the stack gets updated.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644792167629/-mIQaa9ll.png" alt="backend-core-pipeline.png" /></p>
<p><strong>Backend services</strong></p>
<p>The backend code is dockerized and images are pushed to our private ECR repository. The ECS task definition is then updated with the new image tag and redeployed to ECS. This happens every time the backend code is updated. Each service got a separate pipeline.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644793944031/7CG1zHhp6.png" alt="backend-services-pipline.png" /></p>
<h2 id="heading-takeaways">Takeaways</h2>
<p><strong>Go for Serverless!</strong></p>
<p>While I had fun spinning up containers with ECS, I found the operations burden (setting up the VPC, ensuring HA with scaling group and policies...) a lot heavier than when deploying serverless resources. Not to talk about the cost of having EC2s running at all times - especially when you cannot predict traffic.
It is true that some applications use cases require the exclusive use of containers. But now with more AWS services being serverless friendly, it is easier to redesign existing apps to take advantage of that.
And if you absolutely need to run containers, maybe because the app requires a rich ecosystem, well <strong><a target="_blank" href="https://aws.amazon.com/apprunner/">AWS App Runner</a></strong> can do it for you while abstracting a lot of infrastructure provisioning. And if you need deeper control over your infrastructure, go for Kubernetes.</p>
<blockquote>
<p><em>PS: I am preparing for the CKA exam at the time of publishing this blog post.</em></p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[#CloudGuruChallenge: Multi-Cloud Madness]]></title><description><![CDATA[Hi guys, back with another #CloudGuruChallenge.
This time, it is all about a multi-cloud madness inspired by  @ScottPletcher  from ACG. The goal of this challenge is to architect and build an image upload and recognition process using no less than th...]]></description><link>https://blogs.houessou.com/image-recognition-multicloud</link><guid isPermaLink="true">https://blogs.houessou.com/image-recognition-multicloud</guid><category><![CDATA[AWS]]></category><category><![CDATA[Azure]]></category><category><![CDATA[GCP]]></category><category><![CDATA[aws lambda]]></category><category><![CDATA[Computer Vision]]></category><dc:creator><![CDATA[Pierre-Francois H]]></dc:creator><pubDate>Sun, 17 Oct 2021 23:02:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1634337496245/DBDISY5rR.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi guys, back with another #CloudGuruChallenge.
This time, it is all about a multi-cloud madness inspired by  <a target="_blank" href="https://acloudguru.com/blog/author/scott-pletcher">@ScottPletcher </a> from ACG. The goal of this challenge is to <strong>architect and build an image upload and recognition process using no less than three different cloud providers</strong>.
Feel free to read about the challenge instructions <a target="_blank" href="https://acloudguru.com/blog/engineering/cloudguruchallenge-multi-cloud-madness">here</a>.</p>
<p>Whilst it seems to be simple, there is still a big uncertainty because the architecture is spanning across three different cloud providers.</p>
<p>Here is my approach to this challenge.</p>
<h2 id="overview">Overview</h2>
<p>I will go through the overall choice of architecture and services for the app and how I deployed it. All pertaining code can be find <a target="_blank" href="https://github.com/hpfpv/cloudguru-image-recognition-multicloud">here</a>.</p>
<p> <a target="_blank" href="https://imager.houessou.com">Application web UI</a> </p>
<h3 id="about-the-app">About the App</h3>
<p>The app functionality is simple on its own. It just provides analysis data from an uploaded image. The data is then stored into a NoSQL database along with the path to the image. The uploaded image + data are also displayed back to the user.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634161209958/AxkbsPvIh.png" alt="appflow.png" /></p>
<h3 id="application-components">Application Components</h3>
<p>Now that we have a basic understanding of the app, let's see how all of these functionalities translate to different technical components. Below image should provide a good overview of each layer of the app and the technical components involved.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634165923858/ufUyXPlOE.png" alt="app-components.png" /></p>
<p>Let's go through each component.</p>
<p><strong>Frontend</strong></p>
<p>The Frontend for the app is built of simple HTML and Javascript. There are two points of communication with the backend:</p>
<ul>
<li><p>Upload an Image: I have used Javascript SDK to store the uploaded image on S3. Before being uploaded, the name of the file is made unique using <strong>UUID</strong>. This name is saved and later used to retrieve the image's data.</p>
</li>
<li><p>Get analysis data of the uploaded image: the frontend calls an API which triggers a Lambda function. The above saved file name is sent as path parameter.</p>
</li>
</ul>
<p><strong>Backend</strong></p>
<p>I decided to host the brain of the app on AWS. This part of the infrastructure is basically made of Lambda functions and API Gateway.
For image recognition, I've used Azure Computer Vision API, and to save the results, I've opted for Google Cloud Firestore.
This setup is really efficient as it mostly uses managed services from the different cloud providers.
We will not have to manage servers or extra configuration - other than those required to make the services work together.</p>
<p>Now that we have information about the various components and services involved in the app, let's focus on how to integrate them in other to obtain a technical architecture.</p>
<h2 id="architecture">Architecture</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634218990525/aIFW0yvbQ.png" alt="architecture.png" /></p>
<h3 id="frontend">Frontend</h3>
<p><strong>Website: </strong> The static html, javascript and css files generated for the website will be stored in an S3 bucket. The S3 bucket is configured to host a website and will provide an endpoint through which the app can be accessed. To have a better performance on the frontend, the S3 bucket is selected as an Origin to a CloudFront distribution. The CloudFront will act as a CDN for the app frontend and provide faster access through the app pages.</p>
<p><strong>Upload picture:</strong> A Javascript function handles the file upload to a separate S3 bucket (files bucket). Before being uploaded, the name of the file is made unique using <strong>UUID</strong>. The function saves the file name in a local storage variable. This name is later used as the path parameter of the request sent to the API to retrieve image's data.</p>
<h3 id="backend">Backend</h3>
<p>Our application's backend relies on services from all 3 providers. Let's break it down.</p>
<p><strong>Azure</strong></p>
<p>The purpose of our app being to <em>extract rich information from images</em>, I have decided to give Azure Cognitive services a try and deployed a Computer Vision API to serve this purpose.
The service is fairly easy to deploy and offers a publicly accessible API which can be integrated with the other parts of the app.
Since the API is accessible through the internet, we don't have to worry much about extra config to allow the communication. An access key is provided to add to your request when calling the API (although not the most secure option).</p>
<p><strong>AWS</strong></p>
<p>On the AWS side we have deployed two Lambda functions to handle the application logic.</p>
<ul>
<li><p>analyzeImage: triggered when an image is added to the files bucket. Retrieves the file url from the events and send a request to the publicly accessible Azure Computer Vision API.
The response of this request contains the analysis data and is store in a dictionary.
The function now stores the data into a Cloud Firestore database collection. A new document is created in the collection for each analysis.</p>
</li>
<li><p>getAnalysis: retrieves the data store in the Cloud Firestore based on the file name. This function is backing a REST API to communicate with the frontend.</p>
</li>
</ul>
<blockquote>
<p>We can see that both lambda functions interact with the Cloud Firestore database hosted in GCP. Additional configuration is required for communication between AWS and GCP resources. More on that later.</p>
</blockquote>
<p><strong>GCP</strong></p>
<p>Here we will deploy the NoSQL database to store the analysis data. We will create a Cloud Firestore collection and use the SDK for python to perform read and write operations.</p>
<h3 id="access-gcp-resources-from-aws">Access GCP resources from AWS</h3>
<p>GCP <strong>Workload Identity federation</strong> will allow us to access GCP resources from AWS without the need for service account keys.</p>
<p><strong>How it works</strong></p>
<p>Workload identity federation contains two components: <em>workload identity pools</em> and <em>workload identity providers</em>. 
Workload identity pools is a logical container of external identities (in our case AWS roles), whereas Workload identity providers are the entities that contain the relative metadata about the relationship between the external identity provider (AWS, Azure. etc.) and GCP. For example, providers can contain information like AWS account IDs, IAM role ARNs, etc.</p>
<p>In addition to these components, we need to configure <em>attribute mappings</em>. Attributes are metadata attached to the external identity token that supply information to GCP via attribute mappings. Attributes can be combined with conditions to secure your tokens so that they can only be used by approved external identities during the workload identity federation process. Examples of attributes include name, email, or user ID. </p>
<p><strong>Accessing GCP from AWS</strong></p>
<ul>
<li><p>AWS: Create an <strong>IAM role</strong> for our Lambda functions</p>
</li>
<li><p>GCP: Create a <strong>workload identity pool</strong> - allows us to organize and manage providers</p>
</li>
</ul>
<pre><code>gcloud iam workload-<span class="hljs-keyword">identity</span>-pools <span class="hljs-keyword">create</span> REPLACE_ME_POOL_ID \
    <span class="hljs-comment">--location="global" \</span>
    <span class="hljs-comment">--description="REPLACE_ME_POOL_DESCRIPTION" \</span>
    <span class="hljs-comment">--display-name="REPLACE_ME_POOL_NAME"</span>
</code></pre><ul>
<li>GCP: Create an <strong>Identity pool provider</strong></li>
</ul>
<pre><code>gcloud iam workload-<span class="hljs-keyword">identity</span>-pools providers <span class="hljs-keyword">create</span>-aws REPLACE_ME_PROVIDER_NAME \
    <span class="hljs-comment">--workload-identity-pool="REPLACE_ME_POOL_ID" \</span>
    <span class="hljs-comment">--account-id="REPLACE_ME_AWS_ACCOUNT_ID" \</span>
    <span class="hljs-comment">--location="global"</span>
</code></pre><ul>
<li><p>GCP: Create a <strong>Service Account</strong> - needed to give access to the GCP services that our lambda access, in our case Cloud Firestore:
<em>roles/iam.workloadidentityuser (to impersonate the SA)</em>
<em>roles/datastore.user (read and write on firestore database)</em></p>
</li>
<li><p>GCP: Allow <strong>External Identities to impersonate</strong> a service account - required to allow AWS services (our lambda functions) access the GCP resources with the same roles and permissions as the service account created above</p>
</li>
</ul>
<pre><code>gcloud iam service-accounts <span class="hljs-keyword">add</span>-iam-<span class="hljs-keyword">policy</span>-binding awsrole@datapath.iam.gserviceaccount.com \
    <span class="hljs-comment">--role=roles/iam.workloadIdentityUser \</span>
    <span class="hljs-comment">--member="principalSet://iam.googleapis.com/projects/REPLACE_ME_GCP_PROJECT_ID/locations/global/workloadIdentityPools/REPLACE_ME_POOL_ID/attribute.aws_role/REPLACE_ME_ROLE_ARN"</span>
</code></pre><ul>
<li>GCP: Generate Google credentials - will be used in the lambda functions by exposing the <strong>GOOGLE_APPLICATION_CREDENTIALS</strong> environment variable.</li>
</ul>
<pre><code>gcloud iam workload-<span class="hljs-keyword">identity</span>-pools <span class="hljs-keyword">create</span>-cred-config \
projects/REPLACE_ME_GCP_PROJECT_ID/locations/<span class="hljs-keyword">global</span>/workloadIdentityPools/REPLACE_ME_POOL_ID/providers/REPLACE_ME_PROVIDER_NAME \
    <span class="hljs-comment">--service-account=awsrole@datapath.iam.gserviceaccount.com \</span>
    <span class="hljs-comment">--output-file=configoutput.json \</span>
    <span class="hljs-comment">--aws</span>
</code></pre><blockquote>
<p>Note: All CLI commands are done in the GCP console.</p>
</blockquote>
<p>The diagram bellow describes the exchange process before our resources in AWS can access the Firestore database in GCP.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634330601459/pMjglnklJ.png" alt="workload-identity-federation.png" /></p>
<p>To better understand it, please refer to  <a target="_blank" href="https://blog.scalesec.com/access-gcp-from-aws-using-workload-identity-federation-829113ef0b69">this</a> blog which outlines the different steps involved.</p>
<h2 id="iac-and-deployment-method">IaC and Deployment Method</h2>
<p>The application frontend and backend services hosted on AWS are defined as SAM templates. I have created two different stacks which contain resources from each side.
Although I could have used a solution like Terraform (which I am yet to learn and master) to codify the entire infrastructure, I decided to deploy the Azure Computer Vision API and the GCP Firestore database directly from the console and CLI.</p>
<p>As for automated deployments, I am comfortable using GitHub Actions as I find it easy to understand.
Each service - frontend and backend - is deployed using a separate deployment pipeline as follow:</p>
<p><strong>Frontend</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634162400536/RN1Rybpkq.png" alt="frontend-pipeline.png" /></p>
<p><strong>AWS Backend</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634162409628/_JDZe9Li5.png" alt="backend-pipeline.png" /></p>
<h2 id="takeaways">Takeaways</h2>
<p>Although presenting some benefits for reliability, a multi-cloud architecture presents a lot of concerns when it comes to security, <strong>and it think this is what this cloud guru challenge is all about</strong>.</p>
<p>Communication from AWS resources to GCP services used to be implemented by creating a <em>Service Account</em> - a special type of Google account intended to represent a non-human user that needs to authenticate and be authorized to access data in GCP. This approach represent a big security risk because it relies on <em>service account keys</em> (file containing authentication info) to access GCP APIs.</p>
<p>By using <strong>workload identity federation</strong>, we adopt a more secure method (keyless application authentication mechanism) which allows applications running in AWS (or Azure or on-premises) to federate with an external Identity Provider and call Google Cloud resources without using a service account key. Hopefully I was able to show you how this works in a real environment. </p>
<p>As a next step, I am planning to make access between cloud providers totally private (abstracted from the internet). Don't know how to do it yet, but we'll figure it out!</p>
<p>As always feel free to let me know in the comments section, how you'd have complete this challenge.</p>
]]></content:encoded></item><item><title><![CDATA[Deploying a sample serverless to-do app on AWS]]></title><description><![CDATA[Hi guys! In this post, we'll be building a sample todo app on AWS with Python. We will build a web application which enables logged in visitors to manage their todo list. We will use the AWS Serverless Application Model SAM Framework to deploy the ba...]]></description><link>https://blogs.houessou.com/sample-todo-app-aws</link><guid isPermaLink="true">https://blogs.houessou.com/sample-todo-app-aws</guid><category><![CDATA[serverless]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Pierre-Francois H]]></dc:creator><pubDate>Wed, 04 Aug 2021 12:58:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1628080624073/m90IqbBGa.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi guys! In this post, we'll be building a sample todo app on AWS with Python. We will build a web application which enables logged in visitors to manage their todo list. We will use the AWS Serverless Application Model SAM Framework to deploy the backend services (API, Lambda, DynamoDB and Cognito) and will host the frontend on S3 behind a CloudFront distribution.
The frontend is pretty basic with no fancy visuals (I am no frontend dev :p). We will try to focus on how the resources are created and deployed on AWS.</p>
<h2 id="heading-overview">Overview</h2>
<p>I will go through the overall setup of the app and how I deployed it. Mostly this will be a theoretical post but all the code can be found in the <strong><a target="_blank" href="https://github.com/hpfpv/todo-app-aws-community">GitHub repo</a></strong>.</p>
<p><strong><a target="_blank" href="https://todo.houessou.com">Application web UI</a></strong></p>
<h3 id="heading-about-the-app">About the App</h3>
<p>Before I go into the architecture, let me describe what the app is about and what it does. The app is a todo list manager which helps a user manage and track his/her todo list along with their files or attachments. The user can also find specific todos through the search. </p>
<h3 id="heading-basic-functionality">Basic Functionality</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627647453541/A17idrTi1.png" alt="appflow.png" />
The image above should describe the app's basic functionalities.</p>
<p><strong>User/Login Management</strong></p>
<p><em>Users are able to log in to the app using provided credentials. There is a self-register functionality and once a user is registered, the app provides a capability for the user to log in using those credentials. It also provides a logout option for the user.</em></p>
<p><strong>Search Todo</strong></p>
<p><em>Users are able to perform a keyword search and the app shows a list of todos that contain that keyword in the name. The search only searches on todos that the logged-in user has created. So it has the access boundary and doesn’t show Recipes across users.</em></p>
<p><strong>Add New Todo</strong></p>
<p><em>Users can add new Todos to be stored in the app. There are various details that can be provided for each Todo. Users can also add notes for each Todo.</em></p>
<p><strong>Support for files</strong></p>
<p><em>Users can upload todo files for each Todo. The app provides a capability where users can select and upload a local file or download existing files while adding notes to a Todo. The file can be anything, from a text file to an image file. The app stores it in an S3 bucket and serve it back to the user via CloudFront.</em></p>
<h3 id="heading-application-components">Application Components</h3>
<p>Now that we have a basic functional understanding of the app, let's see how all of these functionalities translate to different technical components. The below image should provide a good overview of each layer of the app and the technical components involved in each layer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628077958693/0vhPB3Gjx.png" alt="app-components.png" /></p>
<p>Let's go through each component:</p>
<p><strong>Frontend</strong></p>
<p><em>The Front end of the app is built of simple HTML and Javascript. All operations and communications with the backend are performed via various REST API endpoints.</em></p>
<p><strong>Backend</strong></p>
<p><em>Backend for the app is built with Lambda Functions triggered by REST APIs. It provides various API endpoints to perform application functionalities such as adding or deleting todos, adding or deleting todo files, etc. The REST APIs are built using API Gateway. The API endpoints perform all operations of connecting with the functions, authenticating, etc. CORS is enabled for the API so it only accepts requests from the frontend.</em></p>
<p><strong>Data Layer</strong></p>
<p><em>DynamoDB Table is used to store all todos and related data. The lambda functions will be performing all Database operations connecting to the Table and getting requests from the frontend. DynamoDB is a serverless service and it provides auto-scaling along with high availability.</em> </p>
<p><strong>Authentication</strong></p>
<p><em>The authentication is handled by AWS Cognito. We use a Cognito user pool to store users' data. When a user logs in and a session is established with the app, the session token and related data are stored at the FrontEnd and sent over the API endpoints. API Gateway then validates the session token against Cognito and allow users to perform application operations.</em></p>
<p><strong>File Service</strong></p>
<p><em>There is a separate service to handle file management for the application. The File service is composed of Javascript function using AWS SDK (for upload files operations), Lambda functions + API Gateway for API calls for various file operations like retrieving file info, deleting file, etc, S3 and DynamoDB to store files and files information. The files are served back to the user through the app using a CDN (Content Delivery Network). The CDN makes serving the static files faster and users can access/download them faster and easier.</em></p>
<h2 id="heading-application-architecture">Application Architecture</h2>
<p>Now that we have some information about the various components and services involved in the app, let's move on to how to place and connect these different components to get the final working application. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628029946699/pOk-oRHg4.png" alt="architecture.png" /></p>
<h3 id="heading-frontend">Frontend</h3>
<p>The static <em>HTML</em>, <em>JavaScript</em>, and <em>CSS</em> files generated for the website will be stored in an S3 bucket. The S3 bucket is configured to host a website and will provide an endpoint through which the app can be accessed. To perform better on the frontend, the S3 bucket is selected as an Origin to a CloudFront distribution. The CloudFront will act as a CDN for the app frontend and provide faster access through the app pages.</p>
<h3 id="heading-lambda-functions-for-backend-services-logic">Lambda Functions for backend services logic</h3>
<p>All the backend logic is deployed as AWS Lambda functions. Lambda functions are totally serverless and our task is to upload our code files to create the Lambda functions along with setting other parameters. Below are the functions which are deployed as part of the backend service:</p>
<p><strong>Todos Service</strong></p>
<ul>
<li>getTodos : <em>retrieve all todos for a userID</em></li>
<li>getTodo : <em>return detailed information about one todo based on the todoID attribute</em> </li>
<li>addTodo : <em>create a todo for a specific user based on the userID</em></li>
<li>completeTodo : <em>update todo record and set completed attribute to TRUE based on todoID </em></li>
<li>addTodoNotes : <em>update todo record and set the notes to attribute to the specified value based on todoID</em></li>
<li>deleteTodo : <em>delete a todo for a specific user based on the userID and todoID
</em></li>
</ul>
<p><strong>Files Service</strong></p>
<ul>
<li>getTodoFiles : <em>retrieve all files which belong to a specified todo</em></li>
<li>addTodoFiles : <em>add files as attachments to a specified todo</em></li>
<li>deleteTodoFiles: <em>delete selected file for specified todo</em></li>
</ul>
<h3 id="heading-api-gateway-to-expose-lambda-functions">API Gateway to expose Lambda Functions</h3>
<p>To expose the Lambda functions and make them accessible by the Frontend, AWS API Gateway is deployed. API Gateway defines all the APIs' endpoints and routes the requests to the proper Lambda function in the backend. These API gateway endpoints are called by the frontend. Each application service has its own API (keeping services as separate as possible for decoupling purpose) with deployed routes as follow:</p>
<p><strong>Todos Service</strong></p>
<ul>
<li>getTodos : /{<strong>userID</strong>}/todos</li>
<li>getTodo : /{<strong>userID</strong>}/todos/{<strong>todoID</strong>}</li>
<li>deleteTodo : /{<strong>userID</strong>}/todos/{<strong>todoID</strong>}/delete</li>
<li>addTodo : /{<strong>userID</strong>}/todos/add</li>
<li>completeTodo : /{<strong>userID</strong>}/todos/{<strong>todoID</strong>}/complete</li>
<li>addTodoNotes : /{<strong>userID</strong>}/todos/{<strong>todoID</strong>}/addnotes</li>
</ul>
<p><strong>Files Service</strong></p>
<ul>
<li>getTodoFiles : /{<strong>todoID</strong>}/files</li>
<li>addTodoFiles : /{<strong>todoID</strong>}/files/upload</li>
<li>deleteTodoFiles : /{<strong>todoID</strong>}/files/{<strong>fileID</strong>}/delete</li>
</ul>
<p>The addTodoFiles API route triggers the addTodoFiles function which only records the file information like fine name and file path/key to a DynamoDB table. The same table is queried by the getTodoFiles function to display returned files information.
The actual operation to upload the files to S3 is performed by a Javascript function in the Frontend code. I found it better to do it that way to prevent a large amount of data going through the lambda functions and thus increasing response time and cost.</p>
<h3 id="heading-database">Database</h3>
<p>DynamoDB tables are used to serve as databases. We have two tables for respectively the Todos Service and the Files Service.
The search functionality of the app is handled by simple DynamoDB query requests. We can deploy a DynamoDB Accelerator in front of the tables to increase performance if needed. Below is the configuration of the tables:</p>
<p><strong>Todos Service</strong>
To keep things simple, each document in DynamoDB will represent one todo with attributes as follow:</p>
<ul>
<li>todoID : <em>unique number identifying todo, will serve as primary key</em></li>
<li>userID : <em>ID of the user who created the todo, will serve as a sort key</em></li>
<li>dateCreated : <em>date todo has been created, today's date</em></li>
<li>dateDue : <em>date the todo is due, user-provided</em></li>
<li>title : <em>todo title, user-provided</em></li>
<li>description : <em>todo description, user-provided</em></li>
<li>notes : <em>additional notes for todo, can be added anytime after todo is created, blank by default</em></li>
<li><em>completed</em> : <em>true or false if todo is marked as completed</em></li>
</ul>
<p><strong>Files Service</strong></p>
<ul>
<li>fileID : <em>unique number identifying file, will serve as primary key</em></li>
<li>todoID : <em>ID of belonging todo item, will serve as sort key</em></li>
<li>fileName : <em>name of the uploaded file</em></li>
<li>filePath : <em>URL of the uploaded file for downloads</em></li>
</ul>
<h3 id="heading-file-storage">File Storage</h3>
<p>To support the file management capability of the application, file storage needs to be deployed. I am using an S3 bucket as the storage for the files which are uploaded from the app. The file service API calls the AWS S3 API to store the files in the bucket. To serve the files back to the user, a CloudFront distribution is created with the S3 bucket as the origin. This will serve as the CDN to distribute the static files faster to the end users.</p>
<h2 id="heading-iac-and-deployment-method">IaC and Deployment Method</h2>
<p>The application backend services are defined as SAM templates. Each service has its own template and resources are configured to be as independent as possible.
I am using automated deployments for the whole application environment - frontend and 2 backend services. Each service is deployed using a separate deployment pipeline to maintain optimal decoupling. 
The components below are used as part of the deployment pipeline:</p>
<ul>
<li>One GitHub Repository for code commits</li>
<li>A separate branch for Prod changes (master branch as Dev)</li>
<li>Various paths, one per service - Frontend, Backend Todos Service, and Backend Files Service</li>
<li>Any commit to a service path in a specified branch (Prod or Dev) automatically tests and deploys changes to the service in the appropriate environment.</li>
<li>GitHub Actions backed by docker containers to build and deploy services</li>
</ul>
<p><strong>FrontEnd</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628077668965/DMv5htTiG.png" alt="frontend-pipeline.png" /></p>
<p><strong>Backend</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1628077651575/cMRfCM8MB.png" alt="backend-pipeline.png" /></p>
<h2 id="heading-takeaways">Takeaways</h2>
<p>Hopefully, I was able to describe in detail the system architecture which I would use for a basic todo-list management app. This application is designed solely for training purposes and there is a lot of room for improvement. I will continue working on making the deployment more secure, highly available, and fault tolerant. 
This post should give you a good idea about how to design a basic full stack and fully serverless architecture for an app using the microservices pattern. </p>
]]></content:encoded></item><item><title><![CDATA[Building A Modern Application using the AWS Serverless Application Model Framework]]></title><description><![CDATA[Hi guys!
In this blog, we'll be building a modern application on AWS with Python following  this tutorial. We will build a sample website called Mythical Mysfits that enables visitors to adopt a fantasy creature (mysfit) as pet.
The main difference w...]]></description><link>https://blogs.houessou.com/aws-modern-application</link><guid isPermaLink="true">https://blogs.houessou.com/aws-modern-application</guid><category><![CDATA[AWS]]></category><category><![CDATA[serverless]]></category><category><![CDATA[Python]]></category><category><![CDATA[lambda]]></category><category><![CDATA[REST API]]></category><dc:creator><![CDATA[Pierre-Francois H]]></dc:creator><pubDate>Mon, 05 Jul 2021 16:06:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1625501102492/a_HWYDRCf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi guys!
In this blog, we'll be building a modern application on AWS with Python following  <a target="_blank" href="https://aws.amazon.com/getting-started/hands-on/build-modern-app-fargate-lambda-dynamodb-python/">this</a> tutorial. We will build a sample website called Mythical Mysfits that enables visitors to adopt a fantasy creature (mysfit) as pet.
The main difference with the tutorial is that we will use the AWS Serverless Application Model SAM Framework to deploy the backend services - API, Lambda (instead of Fargate), DynamoDB and Cognito).</p>
<p>We will use all the Frontend provided code from the tutorial - just working on the backend services here with Python as our programming language. </p>
<p><strong>Application Architecture</strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625486964545/Lbz8vXvyT.png" alt="arch-diagram.png" /></p>
<p>The steps to complete this tutorial are:</p>
<ul>
<li>Build a static website to serve static content - S3 + CloudFront</li>
<li>Enable users to retrieve, filter, like and adopt mysfits - API Gateway + AWS Lambda + DynamoDB - <em>microservice #1</em></li>
<li>Enable users authentication - Cognito</li>
<li>Enable users to contact the Mythical Mysfits staff via a Contact Us button - API Gateway + AWS Lambda + DynamoDB + SNS - <em>microservice #2</em></li>
<li>Capture user behavior with a clickstream analysis - API Gateway + Lambda + Kinesis - <em>microservice #3</em></li>
<li>Use Machine Learning to recommend a Mysfit - API Gateway + Lambda + SageMaker - <em>microservice #4</em></li>
</ul>
<p><strong>GitHub repository: https://github.com/hpfpv/mythicalmysfits-aws</strong></p>
<p><strong>Created web app: https://mythicalmysfits.houessou.com</strong></p>
<p>Alright, let's break this down.</p>
<h3 id="static-website-with-s3-and-cloudfront">Static website with S3 and CloudFront</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625482335139/vNsCxOQm8.png" alt="website.png" />
This one is straight forward. Use the aws cli to create a bucket and copy the <em>xx/web/index</em>.html file. Modify the bucket policy to allow public read and set the bucket to serve static website content: 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623608041286/IR6f9aNJQ.png" alt="s3bucket1.png" />
For this project, we have also implemented a CloudFront distro with a custom domain name.
Accessing the bucket display an HTML page with a list of mysfits stored in a dict variable in the code.
We need to load the mysfits from a DynamoDB Table for dynamic operations like get and update.</p>
<h3 id="backend-microservice-1-operations-on-mysfits-user-authentication">Backend microservice #1: Operations on Mysfits + user authentication</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625498559323/Y-G6HZllM.png" alt="micrservice 1.png" />
This microservice is deployed using the SAM Framework and requires 4 REST APIs coupled with 4 Lambda functions (getmyfits, getmyfit, likemysfit, adoptmysfit) which perform QUERY and UPDATE actions on the Mysfits DynamoDB Table also created in the same stack.
Since <em>adopt</em> and <em>like</em> mysfit operations are allowed for signed in users only, we need to create a Cognito User Pool and Client to be set as authorizer for those functions.</p>
<p>In the SAM template file, we will provisionne below resources:</p>
<p><strong>DynamoDB Table to store mysfits</strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625482141148/FGJp_c9_Y.png" alt="dynamoDB.png" />
Make sure to set the GlobalSecondaryIndex to allow filtering on GoodEvil and LawChaos attributes.</p>
<p><strong>Cognito User Pool and Client</strong></p>
<p>We need to add an authorizer to our API Gateway in other to authenticate and authorize users before they could like or adopt a mysfit. For that, we first need to create a Cognito user pool and client in our stack.</p>
<ul>
<li><p>Cognito User Pool
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625482885429/3sNBT0s2p.png" alt="UserPool.png" /></p>
</li>
<li><p>Cognito User Pool Client
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625482910260/nMnmO8R5qF.png" alt="userpoolclient.png" /></p>
</li>
<li><p>Cognito User Pool Domain</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625482922187/YW6zEkLW8.png" alt="userpooldomain.png" />
We can now reference this cognito user pool and client as authorizer for our main HTTP API.</p>
<p><strong>Main HTTP API</strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625483158085/0uy3mkZ2J.png" alt="httpapi.png" />
The authorizer settings has been added to the HTTP API properties. It uses a JWT configuration with our cognito user pool as issuer and user pool client as audience. I have also added the CORS settings to allow GET and POST requests only from the website.</p>
<p><strong>Lambda functions associated to the main HTTPApi</strong></p>
<p>Lambda code is written in Python and performs CRUD operations on the DynamoDB table containing mysfits items based on the event received - in our case the HTTPApi path and request parameters.</p>
<p> I have set the 4 functions below:</p>
<ul>
<li><strong>getmysfits:</strong>
Retrieve all mysfits for the main page and performs filtering based on GoodEvil or LawChaos value</li>
</ul>
<p><em>SAM ressource</em>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625484019918/2-UBPtRv0.png" alt="getmysfits.png" />
<em>Function code</em></p>
<pre><code><span class="hljs-comment"># Returns a list of filtered mysfits based on queryParameters</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">queryMysfitItems</span>(<span class="hljs-params">filter, value</span>):</span>
    <span class="hljs-comment"># Use the DynamoDB API Query to retrieve mysfits from the table that are</span>
    <span class="hljs-comment"># equal to the selected filter values.</span>
    response = client.query(
        TableName=<span class="hljs-string">'MysfitsTable'</span>,
        IndexName=filter+<span class="hljs-string">'Index'</span>,
        KeyConditions={
            filter: {
                <span class="hljs-string">'AttributeValueList'</span>: [
                    {
                        <span class="hljs-string">'S'</span>: value
                    }
                ],
                <span class="hljs-string">'ComparisonOperator'</span>: <span class="hljs-string">"EQ"</span>
            }
        }
    )
    mysfitList = getMysfitsJson(response[<span class="hljs-string">"Items"</span>])  <span class="hljs-comment"># getMysfitsJson adds mysfits attributes to a dict that matches the JSON response structure</span>
    <span class="hljs-keyword">return</span> json.dumps(mysfitList)

<span class="hljs-comment"># Returns all mysfits list   </span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">getmysfits</span>():</span>
    response = client.scan(TableName=<span class="hljs-string">'MysfitsTable'</span>)
    logging.info(response[<span class="hljs-string">"Items"</span>])
    mysfitList = getMysfitsJson(response[<span class="hljs-string">"Items"</span>]) 
    <span class="hljs-keyword">return</span> json.dumps(mysfitList)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span>(<span class="hljs-params">event, context</span>):</span>
    <span class="hljs-keyword">if</span> (event[<span class="hljs-string">"rawQueryString"</span>] == <span class="hljs-string">""</span>): <span class="hljs-comment"># check the presence of queryParameters in the request</span>
        print(<span class="hljs-string">"Getting all values"</span>)
        items = getmysfits()
    <span class="hljs-keyword">else</span>:
        print(<span class="hljs-string">"Getting filtered values"</span>)
        data = event[<span class="hljs-string">"queryStringParameters"</span>].items()
        <span class="hljs-keyword">for</span> key, value <span class="hljs-keyword">in</span> data:
            items = queryMysfitItems(key, value)
    <span class="hljs-keyword">return</span> {
        <span class="hljs-string">'statusCode'</span>: <span class="hljs-number">200</span>,
        <span class="hljs-string">'headers'</span>: {
            <span class="hljs-string">'Access-Control-Allow-Origin'</span>: <span class="hljs-string">'*'</span>,
            <span class="hljs-string">'Access-Control-Allow-Headers'</span>: <span class="hljs-string">'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'</span>,
            <span class="hljs-string">'Access-Control-Allow-Methods'</span>: <span class="hljs-string">'*'</span>,
            <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>
        },
        <span class="hljs-string">'body'</span>: items
    }
</code></pre><ul>
<li><strong>getmysfit:</strong>
Return one item based on path parameters {MysfitsId}</li>
</ul>
<p><em>SAM resource</em>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625484355098/oq8DO8rI6.png" alt="getmysfit.png" /></p>
<ul>
<li><strong>likemysfit:</strong>
Increment the like value for a specified mysfit</li>
</ul>
<p><em>SAM resource</em>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625484396268/qH8z19k4g.png" alt="likemysfit.png" /></p>
<ul>
<li><strong>adoptmysfit:</strong>
Update the adopt value to TRUE for a specified mysfit</li>
</ul>
<p><em>SAM resource</em>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625484422581/SlE-7vQNp.png" alt="adoptmysfit.png" /></p>
<p>When writing your code, remember to break down things as much as possible to keep your functions simple.
Build your SAM template and test the functions and APIs locally then deploy to AWS (which will create a CloudFormation stack with the resources specified in the template file).
Validate your config by testing the APIs with POSTMAN. You can use the <strong>AWS hosted UI</strong> which provides an OAuth 2.0 authorization server with built-in webpages that can be used to sign up and sign in users (required to like and adopt a mysfit).</p>
<p>At this stage, we have successfully created the microservice needed to serve the frontend (retrieve all mysfits, filter mysfits, like and adopt mysfits). We only need to update the frontend html files by adding the HTTP API url, Cognito user pool and Cognito user pool client.</p>
<h3 id="backend-microservice-2-enable-users-to-contact-the-mythical-mysfits">Backend microservice #2: Enable users to contact the Mythical Mysfits</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625498667351/ptkCnH8Ag.png" alt="questions microservice 2.png" /></p>
<p>This microservice is deployed as a seperate CloudFormation stack and also using the SAM Framework. It requires 1 REST API coupled with 1 Lambda function which writes in a DynamoDB table to allow users to send questions through a form on our website. Once a question is posted in the table, a stream triggers another function which uses SNS SDK to publish the question (received as an event) as a topic message. All resources required are defined in the SAM template file:</p>
<p><strong>DynamoDB Table to store questions</strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625499643309/OLyIITXqf.png" alt="questions dynamotable.png" />
DynamoDB table to store users questions. Stream enabled with NEW_IMAGE view type.</p>
<p><strong>Questions SNS Topic</strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625499697572/1qTT2M0Cx.png" alt="questions sns topic.png" />
SNS Topic which to retrieve questions from the dynamodb table and send notification to topic subscribers (Mythical Mysfits staff).</p>
<p><strong>Questions API</strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625499848674/rJAJ28yNm.png" alt="questions http api.png" />
HTTP Api which will trigger our lambda function to post a question to the questions table. CORS settings has been set to allow GET and POST requests only from our website.</p>
<p><em>*Lambda functions</em></p>
<ul>
<li><strong>postquestion()</strong>: retrieve posted question from the request body and save it to the questions table
<em>SAM resource</em>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625500240234/6p9Hfqcc5.png" alt="post question.png" /></li>
</ul>
<p><em>Function code</em></p>
<pre><code>client = boto3.client(<span class="hljs-string">'dynamodb'</span>)
logger = logging.getLogger()
logger.setLevel(logging.<span class="hljs-keyword">INFO</span>)

def lambda_handler(event, context):
    logger.<span class="hljs-keyword">info</span>(event)
    eventBody = <span class="hljs-type">json</span>.loads(event["body"])
    question = {}
    question["QuestionId"] = {
        "S": str(<span class="hljs-type">uuid</span>.uuid4())
        }
    question["QuestionText"] = {
        "S": eventBody["questionText"]
        }
    question["UserEmailAddress"] = {
        "S": eventBody["email"]
        }

    response = client.put_item(
        TableName=os.environ[<span class="hljs-string">'MYSFITS_QUESTIONS_TABLE'</span>],
        Item=question
        ) 
    logger.<span class="hljs-keyword">info</span>(response)   
    responseBody = {}
    responseBody["status"] = "success"
    <span class="hljs-keyword">return</span> {
        <span class="hljs-string">'statusCode'</span>: <span class="hljs-number">200</span>,
        <span class="hljs-string">'headers'</span>: {
            <span class="hljs-string">'Access-Control-Allow-Origin'</span>: <span class="hljs-string">'https://mythicalmysfits.houessou.com'</span>,
            <span class="hljs-string">'Access-Control-Allow-Headers'</span>: <span class="hljs-string">'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'</span>,
            <span class="hljs-string">'Access-Control-Allow-Methods'</span>: <span class="hljs-string">'GET'</span>,
            <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>
        },
        <span class="hljs-string">'body'</span>: <span class="hljs-type">json</span>.dumps(responseBody)  
    }
</code></pre><ul>
<li><strong>publishquestion()</strong>: publish the newly posted question from the questions table to SNS topic
<em>SAM resource</em>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1625500390776/QjxoixCri.png" alt="publish question.png" />
<em>Function code</em></li>
</ul>
<pre><code>sns = boto3.resource(<span class="hljs-string">'sns'</span>)
topic = sns.Topic(os.environ[<span class="hljs-string">'TOPIC_ARN'</span>])
logger = logging.getLogger()
logger.setLevel(logging.INFO)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span>(<span class="hljs-params">event, context</span>):</span>
    logger.info(event)
    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">for</span> record <span class="hljs-keyword">in</span> event[<span class="hljs-string">'Records'</span>]:
            <span class="hljs-keyword">if</span> record[<span class="hljs-string">'eventName'</span>] == <span class="hljs-string">'INSERT'</span>:
                question = record.get(<span class="hljs-string">'dynamodb'</span>).get(<span class="hljs-string">'NewImage'</span>)
                logger.info(question)
                QuestionText = question[<span class="hljs-string">"QuestionText"</span>]
                UserEmailAddress = question[<span class="hljs-string">"UserEmailAddress"</span>]
            response = topic.publish(
                Message = <span class="hljs-string">'FROM EMAIL: '</span> + UserEmailAddress[<span class="hljs-string">'S'</span>] + <span class="hljs-string">'  QUESTION: '</span> + QuestionText[<span class="hljs-string">'S'</span>] ,
                Subject = <span class="hljs-string">'Question from :'</span> + UserEmailAddress[<span class="hljs-string">'S'</span>] ,
                MessageStructure = <span class="hljs-string">'string'</span>
            )
            print(str(response) + <span class="hljs-string">' has been published!'</span>)
        <span class="hljs-keyword">return</span> response
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> er:
        print(er)
        print(<span class="hljs-string">'Couldn'</span>t publish message to SNS<span class="hljs-string">')</span>
</code></pre><p>Build your SAM template and test the functions and APIs locally then deploy to AWS (which will create a CloudFormation stack with the resources specified in the template file).
At this stage, we have successfully created the microservice needed to allow users to post questions. We only need to update the frontend html files by adding the questions HTTP API url.</p>
<p>Microservices 3 and 4 coming soon...</p>
]]></content:encoded></item><item><title><![CDATA[#CloudGuruChallenge - Improve application performance using Amazon ElastiCache]]></title><description><![CDATA[The purpose of this challenge is to implement a Redis cluster using Amazon ElastiCache to cache database queries in a simple Python application. More details on the challenge here.
I decided to deploy all the resources as part of a CloudFormation sta...]]></description><link>https://blogs.houessou.com/cloudguruchallenge-elasticache</link><guid isPermaLink="true">https://blogs.houessou.com/cloudguruchallenge-elasticache</guid><category><![CDATA[AWS]]></category><category><![CDATA[Python]]></category><category><![CDATA[Redis]]></category><category><![CDATA[PostgreSQL]]></category><dc:creator><![CDATA[Pierre-Francois H]]></dc:creator><pubDate>Sat, 12 Jun 2021 14:36:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1623447719317/G6Z4nSQJA.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The purpose of this challenge is to implement a Redis cluster using Amazon ElastiCache to cache database queries in a simple Python application. More details on the challenge <a target="_blank" href="https://acloudguru.com/blog/engineering/cloudguruchallenge-improve-application-performance-using-amazon-elasticache">here</a>.</p>
<p>I decided to deploy all the resources as part of a CloudFormation stack with an EC2 Instance preloaded with user data to make sure that my application could be replicated easily. When it comes to CF templates, you need to visualize the whole resources engaged for the application as well as the required prerequisites i.e. AZ, network, security groups, ACLs... Also think about all the parameters like database account and password, environment variables for EC2...</p>
<p>I wrote a simple CF template which creates below resources in a stack:</p>
<ul>
<li>Amazon S3 bucket to retrieve and store application code from GitHub repository. Using only a GitHub repo may be an easier and better approach</li>
</ul>
<pre><code>  <span class="hljs-attribute">S3Bucket</span>: #S3 Bucket
    <span class="hljs-attribute">DeletionPolicy</span>: Retain
    <span class="hljs-attribute">Type</span>: <span class="hljs-attribute">AWS</span>::<span class="hljs-attribute">S3</span>::Bucket
    <span class="hljs-attribute">Properties</span>:
      <span class="hljs-attribute">BucketName</span>: <span class="hljs-string">"S3bucketname"</span>
      <span class="hljs-attribute">Tags</span>:
        - <span class="hljs-attribute">Key</span>: key1
          <span class="hljs-attribute">Value</span>: value1
</code></pre><ul>
<li>EC2 Instance profile associated with a role with read and write permissions on the S3 bucket - to be able to pull the code on the EC2 instance from S3</li>
</ul>
<pre><code>  <span class="hljs-attribute">EC2Role</span>: #IAM Role for EC2 instance profile (S3 bucket access)
    <span class="hljs-attribute">Type</span>: <span class="hljs-attribute">AWS</span>::<span class="hljs-attribute">IAM</span>::Role
    <span class="hljs-attribute">DependsOn</span>: S3Bucket
    <span class="hljs-attribute">Properties</span>:
      <span class="hljs-attribute">Tags</span>:
        - <span class="hljs-attribute">Key</span>: key1
          <span class="hljs-attribute">Value</span>: value1
      <span class="hljs-attribute">Description</span>: <span class="hljs-string">"description"</span>
      <span class="hljs-attribute">AssumeRolePolicyDocument</span>: 
        <span class="hljs-attribute">Version</span>: <span class="hljs-string">"2012-10-17"</span>
        <span class="hljs-attribute">Statement</span>:
          - <span class="hljs-attribute">Effect</span>: Allow
            <span class="hljs-attribute">Principal</span>:
              <span class="hljs-attribute">Service</span>:
                - ec2.amazonaws.com
            <span class="hljs-attribute">Action</span>:
              - <span class="hljs-string">'sts:AssumeRole'</span>
      <span class="hljs-attribute">Path</span>: /
      <span class="hljs-attribute">Policies</span>:
        - <span class="hljs-attribute">PolicyName</span>: <span class="hljs-string">"policyname"</span>
          <span class="hljs-attribute">PolicyDocument</span>:
            <span class="hljs-attribute">Version</span>: <span class="hljs-string">"2012-10-17"</span>
            <span class="hljs-attribute">Statement</span>: 
              - <span class="hljs-attribute">Effect</span>: Allow
                <span class="hljs-attribute">Action</span>: <span class="hljs-string">'s3:*'</span>
                <span class="hljs-attribute">Resource</span>: 
                  - <span class="hljs-attribute">arn</span>:<span class="hljs-attribute">aws</span>:<span class="hljs-attribute">s3</span>:::s3bucketname
                  - <span class="hljs-attribute">arn</span>:<span class="hljs-attribute">aws</span>:<span class="hljs-attribute">s3</span>:::s3bucketname<span class="hljs-comment">/*</span>
</code></pre><pre><code>  <span class="hljs-attribute">EC2InstanceProfile</span>: <span class="hljs-number">#EC2</span> Instance profile
    <span class="hljs-attribute">Type</span>: <span class="hljs-attribute">AWS</span>::<span class="hljs-attribute">IAM</span>::InstanceProfile
    <span class="hljs-attribute">DependsOn</span>: EC2Role
    <span class="hljs-attribute">Properties</span>:
      <span class="hljs-attribute">Path</span>: /
      <span class="hljs-attribute">Roles</span>:
        - !Ref EC2Role
</code></pre><ul>
<li>Security Group for EC2 instance</li>
</ul>
<pre><code>  <span class="hljs-attr">EC2SG:</span> <span class="hljs-comment">#SecurityGroup for EC2 Instance</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::EC2::SecurityGroup</span>
    <span class="hljs-attr">Properties:</span> 
      <span class="hljs-attr">GroupDescription:</span> <span class="hljs-string">"SecurityGroup for EC2 Instance - Allow all"</span>
      <span class="hljs-attr">GroupName:</span> <span class="hljs-string">"ec2securitygroupname"</span>
      <span class="hljs-attr">SecurityGroupIngress:</span>
        <span class="hljs-bullet">-</span> 
          <span class="hljs-attr">IpProtocol:</span> <span class="hljs-string">tcp</span>
          <span class="hljs-attr">FromPort:</span> <span class="hljs-number">0</span>
          <span class="hljs-attr">ToPort:</span> <span class="hljs-number">65535</span>
          <span class="hljs-attr">CidrIp:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/0</span>
      <span class="hljs-attr">Tags:</span> 
        <span class="hljs-bullet">-</span> <span class="hljs-attr">Key:</span> <span class="hljs-string">key1</span>
          <span class="hljs-attr">Value:</span> <span class="hljs-string">value1</span>
</code></pre><ul>
<li>Security Group for RDS database and ElastiCache cluster - referencing the EC2 instance SG: Only allow traffic from specified EC2 instance</li>
</ul>
<pre><code>  <span class="hljs-attribute">DatabaseSG</span>: #SecurityGroup for database
    <span class="hljs-attribute">Type</span>: <span class="hljs-attribute">AWS</span>::<span class="hljs-attribute">RDS</span>::DBSecurityGroup
    <span class="hljs-attribute">DependsOn</span>: EC2SG
    <span class="hljs-attribute">Properties</span>:
      <span class="hljs-attribute">GroupDescription</span>: <span class="hljs-string">"description"</span>
      <span class="hljs-attribute">DBSecurityGroupIngress</span>: 
        - <span class="hljs-attribute">EC2SecurityGroupName</span>: !Ref EC2SG #referencing EC2 instance SG as allowed inbound traffic
      <span class="hljs-attribute">Tags</span>: 
        - <span class="hljs-attribute">Key</span>: key1
          <span class="hljs-attribute">Value</span>: value1
</code></pre><pre><code>  <span class="hljs-attr">ElastiCacheSG:</span> <span class="hljs-comment">#SecurityGroup for ElastiCache</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::EC2::SecurityGroup</span>
    <span class="hljs-attr">DependsOn:</span> <span class="hljs-string">EC2SG</span>
    <span class="hljs-attr">Properties:</span> 
      <span class="hljs-attr">GroupDescription:</span> <span class="hljs-string">"description"</span>
      <span class="hljs-attr">GroupName:</span> <span class="hljs-string">"securitygroupname"</span>
      <span class="hljs-attr">SecurityGroupIngress:</span> 
        <span class="hljs-bullet">-</span> <span class="hljs-attr">IpProtocol:</span> <span class="hljs-string">tcp</span>
          <span class="hljs-attr">FromPort:</span> <span class="hljs-number">0</span>
          <span class="hljs-attr">ToPort:</span> <span class="hljs-number">65535</span>
          <span class="hljs-attr">SourceSecurityGroupName:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">EC2SG</span> <span class="hljs-comment">#referencing EC2 instance SG as allowed inbound traffic</span>
      <span class="hljs-attr">Tags:</span> 
        <span class="hljs-bullet">-</span> <span class="hljs-attr">Key:</span> <span class="hljs-string">key1</span>
          <span class="hljs-attr">Value:</span> <span class="hljs-string">value1</span>
</code></pre><ul>
<li>RDS instance with PostgreSQL as engine</li>
</ul>
<pre><code><span class="hljs-attr">DBInstance:</span> <span class="hljs-comment">#RDS database Instance</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::RDS::DBInstance</span>
    <span class="hljs-attr">DependsOn:</span> 
      <span class="hljs-bullet">-</span> <span class="hljs-string">DatabaseSG</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">Engine:</span> <span class="hljs-string">"postgres"</span>
      <span class="hljs-attr">DBInstanceIdentifier:</span> <span class="hljs-string">"identifiername"</span>
      <span class="hljs-attr">DBInstanceClass:</span> <span class="hljs-string">db.t2.micro</span>
      <span class="hljs-attr">AllocatedStorage:</span> <span class="hljs-number">20</span>
      <span class="hljs-attr">DBName:</span> <span class="hljs-string">"dbname"</span>
      <span class="hljs-attr">MasterUsername:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">DatabaseAccount</span> <span class="hljs-comment">#referencing the master user account parameter</span>
      <span class="hljs-attr">MasterUserPassword:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">DatabasePassword</span> <span class="hljs-comment">#referencing the master password parameter</span>
      <span class="hljs-attr">StorageType:</span> <span class="hljs-string">gp2</span>
      <span class="hljs-attr">MaxAllocatedStorage:</span> <span class="hljs-number">20</span>
      <span class="hljs-attr">DBSecurityGroups:</span> 
        <span class="hljs-bullet">-</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">DatabaseSG</span>
      <span class="hljs-attr">Tags:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">Key:</span> <span class="hljs-string">key1</span>
          <span class="hljs-attr">Value:</span> <span class="hljs-string">value1</span>
</code></pre><ul>
<li>ElastiCache for Redis cluster for caching</li>
</ul>
<pre><code>  <span class="hljs-attr">ElastiCache:</span> <span class="hljs-comment"># ElastiCache for Redis Cluster</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ElastiCache::CacheCluster</span>
    <span class="hljs-attr">DependsOn:</span> <span class="hljs-string">ElastiCacheSG</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">ClusterName:</span> <span class="hljs-string">"redisclustername"</span>
      <span class="hljs-attr">CacheNodeType:</span> <span class="hljs-string">cache.t2.micro</span>
      <span class="hljs-attr">Engine:</span> <span class="hljs-string">Redis</span>
      <span class="hljs-attr">VpcSecurityGroupIds:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">ElastiCacheSG.GroupId</span> <span class="hljs-comment">#referencing the ElastiCache SG created above</span>
      <span class="hljs-attr">NumCacheNodes:</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">Tags:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">Key:</span> <span class="hljs-string">key1</span>
          <span class="hljs-attr">Value:</span> <span class="hljs-string">value1</span>
</code></pre><ul>
<li>EC2 Instance with bootstrap code to install prerequisites and run the python application.</li>
</ul>
<pre><code>  EC2: <span class="hljs-comment">#EC2 Instance for app</span>
    Type: AWS::EC2::Instance
    DependsOn: 
      - EC2SG
      - EC2InstanceProfile
      - ElastiCache
      - DBInstance 
    Properties: 
      ImageId: ami-0aeeebd8d2ab47354
      InstanceType: t2.micro
      KeyName: keyname <span class="hljs-comment">#specify an existing key name</span>
      SecurityGroups:
        - !Ref EC2SG
      IamInstanceProfile: !Ref EC2InstanceProfile
      UserData: 
        !Base64 |
        <span class="hljs-comment">#!/bin/bash</span>
        sudo mkdir /home/app
        sudo aws s3 cp s3://hpf-acg-elasticache/app/ /home/app <span class="hljs-comment">--recursive</span>
        sudo yum -y <span class="hljs-keyword">update</span>
        sudo yum -y <span class="hljs-keyword">install</span> python3
        sudo yum -y <span class="hljs-keyword">install</span> postgresql
        sudo cp /home/app/config/.pgpass /home/ec2-<span class="hljs-keyword">user</span>/.pgpass
        sudo chmod <span class="hljs-number">0600</span> /home/ec2-<span class="hljs-keyword">user</span>/.pgpass
        sudo chown ec2-<span class="hljs-keyword">user</span>:ec2-<span class="hljs-keyword">user</span> /home/ec2-<span class="hljs-keyword">user</span>/.pgpass
        <span class="hljs-keyword">export</span> PGPASSFILE=<span class="hljs-string">'/home/ec2-user/.pgpass'</span>
        <span class="hljs-keyword">export</span> REDIS_URL=redis://redisendpoint_URL:<span class="hljs-number">6379</span>
        psql -h rdsendpointURL -U postgres -f /home/app/install.sql databasename
        cd /home/app
        python3 -m venv /home/app
        <span class="hljs-keyword">source</span> /home/app/<span class="hljs-keyword">bin</span>/<span class="hljs-keyword">activate</span>
        python3 -m pip <span class="hljs-keyword">install</span> <span class="hljs-comment">--upgrade pip</span>
        pip <span class="hljs-keyword">install</span> -r requirements.txt 
        python3 /home/app/app.py
      Tags:
        - <span class="hljs-keyword">Key</span>: key1
          <span class="hljs-keyword">Value</span>: value1
        - <span class="hljs-keyword">Key</span>: <span class="hljs-string">"Name"</span>
          <span class="hljs-keyword">Value</span>: <span class="hljs-string">"ec2instancename"</span>
</code></pre><ul>
<li>As parameters, I provided the RDS database account username and password plus Redis endpoint URL as environment variable</li>
</ul>
<pre><code><span class="hljs-attr">Parameters:</span>
  <span class="hljs-attr">DatabaseAccount:</span> <span class="hljs-comment">#DB master account</span>
    <span class="hljs-attr">Description :</span> <span class="hljs-string">"The database admin account. Default is postgres"</span>
    <span class="hljs-attr">Type :</span> <span class="hljs-string">String</span>
    <span class="hljs-attr">Default:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">MinLength :</span> <span class="hljs-number">1</span>
    <span class="hljs-attr">MaxLength :</span> <span class="hljs-number">41</span>
    <span class="hljs-attr">AllowedPattern :</span> <span class="hljs-string">^[a-zA-Z0-9]*$</span>
  <span class="hljs-attr">DatabasePassword:</span> <span class="hljs-comment">#DB master account password</span>
    <span class="hljs-attr">NoEcho:</span> <span class="hljs-literal">True</span>
    <span class="hljs-attr">Description :</span> <span class="hljs-string">"The database admin account password"</span>
    <span class="hljs-attr">Type :</span> <span class="hljs-string">String</span>
    <span class="hljs-attr">MinLength :</span> <span class="hljs-number">1</span>
    <span class="hljs-attr">MaxLength :</span> <span class="hljs-number">41</span>
    <span class="hljs-attr">AllowedPattern :</span> <span class="hljs-string">^[a-zA-Z0-9]*$</span>
</code></pre><p>CloudFormation template diagram
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623454044729/4S3bRTZ2u.jpeg" alt="CF.jpg" /></p>
<p>To be able to access the app via the public IP of the EC2 instance, add the code below to the user data and copy the <em>/app/config/nginx-app.conf</em> file to the <em>/etc/nginx/conf.d/</em> folder of your EC2.</p>
<pre><code>        sudo amazon-linux-extras <span class="hljs-keyword">install</span> nginx1
        sudo chmod -R <span class="hljs-number">755</span> /home/app
        sudo chown -R ec2-<span class="hljs-keyword">user</span>:nginx /home/app
        sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf-orig
        sudo cp /home/app/config/nginx-app.conf /etc/nginx/conf.d/nginx-app.conf
        sudo systemctl <span class="hljs-keyword">start</span> nginx
        sudo systemctl <span class="hljs-keyword">enable</span> nginx
</code></pre><p><em>This CloudFormation template is not perfect and not complete either. A lot more parameters can be added as well as mappings.</em></p>
<p>Now that the app is running, you can see the Elapsed time is always above 5 secondes: 5.09140s, 5.08107s, 5.06325s...</p>
<p><strong>Lets add the caching layer with the following settings:</strong></p>
<ul>
<li>Check the Redis cache before querying the database. </li>
<li>If a cache miss occurs, query the database and update the cache with the results.</li>
</ul>
<p>I updated the app code by first isolating the RDS query process in a separate function with <em>sql code</em> as parameter:</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">query</span>(<span class="hljs-params">sql</span>):</span>
    <span class="hljs-comment"># connect to database listed in database.ini</span>
    conn = connect()
    <span class="hljs-keyword">if</span>(conn != <span class="hljs-literal">None</span>):
        cur = conn.cursor()
        cur.execute(sql)
        <span class="hljs-comment"># fetch one row</span>
        retval = cur.fetchone()
        <span class="hljs-comment"># close db connection</span>
        cur.close() 
        conn.close()
        print(<span class="hljs-string">"PostgreSQL connection is now closed"</span>)
        <span class="hljs-keyword">return</span> retval
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>
</code></pre><p>Next step is the Redis cache initialization:</p>
<pre><code><span class="hljs-comment"># Read the Redis credentials from the REDIS_URL environment variable.</span>
<span class="hljs-attr">REDIS_URL</span> = os.environ.get(<span class="hljs-string">'REDIS_URL'</span>)

<span class="hljs-comment"># Initialize the cache</span>
<span class="hljs-attr">cache</span> = redis.Redis.from_url(REDIS_URL)

<span class="hljs-comment"># Time to live for cached data - 10 seconds for this example</span>
<span class="hljs-attr">TTL</span> = <span class="hljs-number">10</span>
</code></pre><p>Finally, I added a fetch function to check Redis cache first and update Redis in case of a <em>miss</em>:</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fetch</span>(<span class="hljs-params">sql</span>):</span>
    result = cache.get(sql)
    <span class="hljs-keyword">if</span> result:
        <span class="hljs-keyword">return</span> json.loads(result)
    result = query(sql)
    cache.setex(sql,TTL, json.dumps(result)) <span class="hljs-comment"># if result not found in redis, update redis cache</span>
    <span class="hljs-keyword">return</span> result
</code></pre><p> Let's run the app:</p>
<ul>
<li>The first request also take above 5 seconds to complete.</li>
<li>From the fourth request we notice an elapsed time of 0.00150 second and lower</li>
<li>After waiting 10 seconds (which is the value of the cache TTL), the time elapsed is back to 5+ seconds </li>
</ul>
<p><strong>Before Redis</strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623453319535/hBOSQZOFT.png" alt="before redis.png" /></p>
<p><strong>After Redis</strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623453357791/u3lLxBA3D.png" alt="after redis.png" />
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1623508345824/53TYijH8C.jpeg" alt="after redis2.jpg" /></p>
<p>You can find more information on improving application performance with ElastiCache on  <a target="_blank" href="https://aws.amazon.com/getting-started/hands-on/boosting-mysql-database-performance-with-amazon-elasticache-for-redis/">AWS docs</a>.</p>
<p>This challenge was very fun to work on. I had a good time practicing on CF templates to deploy the app. I will further my learnings by trying to implement ElastiCache for session-store.</p>
]]></content:encoded></item><item><title><![CDATA[#CloudGuruChallenge – Event-Driven Python on AWS]]></title><description><![CDATA[I started this challenge as part of my training after completing last month's Azure Resume challenge. 
This one was particularly challenging for me because I had no such experience in Python when starting the project. I learnt on the go with a lot of...]]></description><link>https://blogs.houessou.com/cloudguruchallenge-event-driven-python-on-aws</link><guid isPermaLink="true">https://blogs.houessou.com/cloudguruchallenge-event-driven-python-on-aws</guid><category><![CDATA[Cloud]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Python]]></category><category><![CDATA[serverless]]></category><category><![CDATA[ci-cd]]></category><dc:creator><![CDATA[Pierre-Francois H]]></dc:creator><pubDate>Tue, 01 Jun 2021 21:56:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1622585297864/p-OYLeocQ.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I started this challenge as part of my training after completing last month's <a target="_blank" href="https://acloudguru.com/blog/engineering/cloudguruchallenge-your-resume-in-azure?utm_source=instagram&amp;utm_medium=social&amp;utm_campaign=cloudguruchallenge">Azure Resume challenge</a>. 
This one was particularly challenging for me because I had no such experience in Python when starting the project. I learnt on the go with a lot of resources from  <a target="_blank" href="https://stackoverflow.com/">stack overflow</a>  and  <a target="_blank" href="https://docs.aws.amazon.com/">AWS docs</a>.
The goal of this challenge is to automate an ETL processing pipeline for COVID-19 data using Python and cloud services (AWS here).
You can find the steps for this #CloudGuruChallenge  <a target="_blank" href="https://acloudguru.com/blog/engineering/cloudguruchallenge-python-aws-etl">here</a>.</p>
<p>Let's begin!</p>
<h3 id="extract-and-transform">Extract and Transform</h3>
<p>The first part of this challenge is all about importing and manipulating data from 2 csv files and importing selected data to a DynamoDB Table.
After a lot of googling, I decided to use <em>pandas dataframes</em> to store the csv data and perfom the required transformation (conversion of the <em>date</em> field in a date object, joining data from 2 dataframes, removing non-US data...).
The csv import and data transformation were handled separately by 2 different modules.</p>
<ul>
<li>import_csv</li>
</ul>
<pre><code><span class="hljs-keyword">import</span> ssl
<span class="hljs-keyword">import</span> pandas
ssl._create_default_https_context = ssl._create_unverified_context

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">usdata</span>():</span>
    uscovid19 = []
    csvurl = <span class="hljs-string">'https://raw.githubusercontent.com/nytimes/covid-19-data/master/us.csv'</span>
    uscovid19 = pandas.read_csv(csvurl, delimiter=<span class="hljs-string">','</span>)
    <span class="hljs-comment">#print (uscovid19)</span>
    <span class="hljs-keyword">return</span> uscovid19

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">recovered</span>():</span>
    recovered = []
    recurl = <span class="hljs-string">'https://raw.githubusercontent.com/datasets/covid-19/master/data/time-series-19-covid-combined.csv'</span>
    recovered = pandas.read_csv(recurl, delimiter=<span class="hljs-string">','</span>, usecols=[<span class="hljs-number">0</span>,<span class="hljs-number">1</span>,<span class="hljs-number">4</span>])
    recovered = recovered[recovered[<span class="hljs-string">'Country/Region'</span>] == <span class="hljs-string">'US'</span>]
    recovered = recovered.drop(<span class="hljs-string">'Country/Region'</span>, axis = <span class="hljs-string">'columns'</span>)
    <span class="hljs-comment">#print (recovered)</span>
    <span class="hljs-keyword">return</span> recovered
</code></pre><ul>
<li>data transformation</li>
</ul>
<pre><code><span class="hljs-keyword">import</span> import_csv
<span class="hljs-keyword">import</span> pandas

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    <span class="hljs-keyword">try</span>:
        uscovid19 = import_csv.usdata()
        recovered = import_csv.recovered()

        uscovid19[<span class="hljs-string">'date'</span>] = pandas.to_datetime(uscovid19[<span class="hljs-string">'date'</span>])
        recovered[<span class="hljs-string">'Date'</span>] = pandas.to_datetime(recovered[<span class="hljs-string">'Date'</span>])

        alldata = pandas.merge(uscovid19, recovered, left_on=<span class="hljs-string">'date'</span>, right_on=<span class="hljs-string">'Date'</span>)
        alldata = alldata.drop(<span class="hljs-string">'Date'</span>, axis=<span class="hljs-string">'columns'</span>)

        <span class="hljs-keyword">return</span> alldata
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> er:
        print(er)
</code></pre><p>The data transformation module calls import_csv and returns the combined csv data as a <strong>pandas.dataframe</strong></p>
<h3 id="load-data-into-dynamodb">Load data into DynamoDB</h3>
<p>First I created a DynamoDB table with <em>date (String)</em> as the partition key and enabled DynamoDB Streams.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1622584086321/C7poa2leD.png" alt="Screen Shot 2021-06-01 at 8.58.29 PM.png" /></p>
<p>Since this code will be triggered by a CloudWatch Rule, the data load process has to be conditional depending on the count of items in the table compared to the count of items in the dataframe.</p>
<ul>
<li>Initial data load (no items in DynamoDB table)</li>
</ul>
<p>I got the count of items by scanning the table <strong><em>dynamotable.scan(Select = 'COUNT')['Count']</em></strong>. 
When the count returns 0, the code bellow is triggered:</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initial_load</span>(<span class="hljs-params">dataframe, dynamotable</span>):</span>
    <span class="hljs-keyword">try</span>:
        datacount = int(len(dataframe.index)) 
        <span class="hljs-keyword">for</span> row <span class="hljs-keyword">in</span> range(<span class="hljs-number">0</span>, datacount):
            date = str(dataframe.loc[row, <span class="hljs-string">'date'</span>])
            cases = str(dataframe.loc[row, <span class="hljs-string">'cases'</span>])
            deaths = str(dataframe.loc[row, <span class="hljs-string">'deaths'</span>])
            Recovered = str(dataframe.loc[row, <span class="hljs-string">'Recovered'</span>])
            dynamotable.put_item(
                Item={
                    <span class="hljs-string">'date'</span>: date,
                    <span class="hljs-string">'cases'</span>: cases,
                    <span class="hljs-string">'deaths'</span>: deaths,
                    <span class="hljs-string">'Recovered'</span>: Recovered
                }
            )
        <span class="hljs-keyword">print</span> (<span class="hljs-string">'First load of Items completed successfully'</span>)
        finalcount = dynamotable.scan(Select = <span class="hljs-string">'COUNT'</span>)[<span class="hljs-string">'Count'</span>] 
        response = finalcount 
        <span class="hljs-keyword">return</span> response
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> er:
        print(er)
</code></pre><ul>
<li>Appending the table with new items</li>
</ul>
<p>When the count of items in the dataframe is greater than the count of items in the table, the code bellow is triggered to add only new items (new days).
This code is not complete for me, as it only add new items after the last item in the table. No control has been implemented to check and add new items at any other row than the last one.</p>
<pre><code>def append_data(dataframe, dynamotable):
    try:
        dynamocount = int(dynamotable.scan(<span class="hljs-keyword">Select</span> = <span class="hljs-string">'COUNT'</span>)[<span class="hljs-string">'Count'</span>])
        datacount = <span class="hljs-built_in">int</span>(<span class="hljs-keyword">len</span>(dataframe.index)) 
        <span class="hljs-keyword">for</span> <span class="hljs-keyword">row</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">range</span>(dynamocount, datacount):
            <span class="hljs-built_in">date</span> = <span class="hljs-keyword">str</span>(dataframe.loc[<span class="hljs-keyword">row</span>, <span class="hljs-string">'date'</span>])
            cases = <span class="hljs-keyword">str</span>(dataframe.loc[<span class="hljs-keyword">row</span>, <span class="hljs-string">'cases'</span>])
            deaths = <span class="hljs-keyword">str</span>(dataframe.loc[<span class="hljs-keyword">row</span>, <span class="hljs-string">'deaths'</span>])
            Recovered = <span class="hljs-keyword">str</span>(dataframe.loc[<span class="hljs-keyword">row</span>, <span class="hljs-string">'Recovered'</span>])
            dynamotable.put_item(
                    Item={
                        <span class="hljs-string">'date'</span>: <span class="hljs-built_in">date</span>,
                        <span class="hljs-string">'cases'</span>: cases,
                        <span class="hljs-string">'deaths'</span>: deaths,
                        <span class="hljs-string">'Recovered'</span>: Recovered
                    }
                )       
        dynamotable.scan()
        print (<span class="hljs-string">'DynamoDB table updated successfully'</span>)
        finalcount = dynamotable.scan(<span class="hljs-keyword">Select</span> = <span class="hljs-string">'COUNT'</span>)[<span class="hljs-string">'Count'</span>] 
        response = finalcount - dynamocount
        <span class="hljs-keyword">return</span> response
    <span class="hljs-keyword">except</span> <span class="hljs-keyword">Exception</span> <span class="hljs-keyword">as</span> er:
        print(er)
</code></pre><p>Both codes return the number of added items to the table (just to be able to log it).
Below is the main code which calls the initial load or the append data modules based on the items count. I also added a part for no new items in dataframe vs table.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">load_data</span>(<span class="hljs-params">dataframe, dynamotable</span>):</span>
    print(<span class="hljs-string">'---------------------'</span>)
    print(<span class="hljs-string">'Begining data Laod'</span>)
    print(<span class="hljs-string">'---------------------'</span>)
    dynamocount = int(dynamotable.scan(Select = <span class="hljs-string">'COUNT'</span>)[<span class="hljs-string">'Count'</span>])
    datacount = int(len(dataframe.index))
    count = datacount - dynamocount
    print(<span class="hljs-string">'Items in DynamoDB table: '</span> + str(dynamocount))
    print(<span class="hljs-string">'Items in dataframe: '</span> + str(datacount))
    <span class="hljs-keyword">if</span> dynamocount == <span class="hljs-number">0</span>:
        print(<span class="hljs-string">'Table first load of items'</span>)
        response = initial_load(dataframe, dynamotable)
        print( str(response) + <span class="hljs-string">' ITEMS CREATED'</span>)
    <span class="hljs-keyword">elif</span> dynamocount == datacount:
        print(<span class="hljs-string">'NO NEW ITEMS'</span>)
    <span class="hljs-keyword">elif</span> dynamocount != datacount:
        print(<span class="hljs-string">'Appending table with new items'</span>)
        response = append_data(dataframe, dynamotable)
        print( str(response) + <span class="hljs-string">' ITEMS ADDED'</span>)
    print(<span class="hljs-string">'---------------------'</span>)
    <span class="hljs-keyword">print</span> (<span class="hljs-string">'Data load completed!!'</span>)
    print(<span class="hljs-string">'---------------------'</span>)
</code></pre><p>Those modules are part of the same package which has been deployed to AWS Lambda as a function. The handler calls the <em>load_data</em> module with the DynamoDB table and the dataframe as arguments.
Running the function in Lambda resulted in several errors due to missing <em>numpy</em> dependencies even-though <em>pandas</em> and <em>numpy</em> lib were added to the function package . <em>Numpy</em> is required when using pandas in Python. I eventually came  <a target="_blank" href="https://github.com/pbegle/aws-lambda-py3.6-pandas-numpy">this post</a>  with the required python deployment package to run <em>pandas</em> and <em>numpy</em> in AWS Lambda with <strong>python 3.6 runtime</strong>. I just had to add my modules and it worked just fine.
Since this function update the DynamoDB table, don't forget to set appropriate permission to the execution role.</p>
<h3 id="cloudwatch-or-eventbridge-rule-to-trigger-etl-function">CloudWatch or EventBridge rule to trigger ETL function</h3>
<p>Here I simply created a CloudWatch rule to trigger my function on a schedule. You will need to add a resource based policy to the function to allow lambda:InvokeFunction permission.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1622579183057/w1wLGQKHO.png" alt="Screen Shot 2021-06-01 at 9.25.43 PM.png" /></p>
<h3 id="notification">Notification</h3>
<p>When the database has been updated, the code should trigger an SNS message to notify any interested consumers that the ETL job has completed. The message should include the number of rows updated in the database.</p>
<ul>
<li>Lambda function to publish message to SNS topic</li>
</ul>
<p>I created another function triggered by DynamoDB streams with below configuration:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1622580394353/ZGqRJPC0y.png" alt="Screen Shot 2021-06-01 at 9.36.16 PM.png" /></p>
<p>The code run through the events (DynamoDB stream) and get the total number of items added or deleted based on event name and then publish a message to SNS topic.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_stream_insert</span>(<span class="hljs-params">event, context</span>):</span>
    <span class="hljs-keyword">try</span>:
        new_rows_count = <span class="hljs-number">0</span>
        <span class="hljs-keyword">for</span> record <span class="hljs-keyword">in</span> event [<span class="hljs-string">'Records'</span>]:
            <span class="hljs-keyword">if</span> record[<span class="hljs-string">'eventName'</span>] == <span class="hljs-string">'INSERT'</span>:
                new_rows_count += <span class="hljs-number">1</span>
        <span class="hljs-keyword">return</span> new_rows_count
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> er:
        print(er)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_stream_remove</span>(<span class="hljs-params">event, context</span>):</span>
    <span class="hljs-keyword">try</span>:
        del_rows_count = <span class="hljs-number">0</span>
        <span class="hljs-keyword">for</span> record <span class="hljs-keyword">in</span> event [<span class="hljs-string">'Records'</span>]:
            <span class="hljs-keyword">if</span> record[<span class="hljs-string">'eventName'</span>] == <span class="hljs-string">'REMOVE'</span>:
                del_rows_count += <span class="hljs-number">1</span>
        <span class="hljs-keyword">return</span> del_rows_count
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> er:
        print(er)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>(<span class="hljs-params">event, context</span>):</span>
    <span class="hljs-keyword">try</span>:
        sns = boto3.resource(<span class="hljs-string">'sns'</span>)
        topic = sns.Topic(<span class="hljs-string">'arn:aws:sns:us-east-1:XXXXXXX:xxxxxxx'</span>)
        newItems = str(get_stream_insert(event, context))
        delItems = str(get_stream_remove(event, context))
        response = topic.publish(
            Message = <span class="hljs-string">'The ETL job is completed. The table has '</span> + newItems + <span class="hljs-string">' new items and '</span> + delItems + <span class="hljs-string">' deleted items.'</span> ,
            Subject = <span class="hljs-string">'ETLCovid19 job status'</span>,
            MessageStructure = <span class="hljs-string">'string'</span>
        )
        print(str(response) + <span class="hljs-string">' has been published!'</span>)
        <span class="hljs-keyword">return</span> response
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> er:
        print(er)
        print(<span class="hljs-string">'Couldnt publish message to SNS'</span>)
</code></pre><h3 id="iac">IaC</h3>
<p>This part is about defining the created resources in code using CloudFront. I created a yaml template to import all the resources in a CloudFormation stack. Very useful guide on CF  <a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html">here</a>.</p>
<pre><code><span class="hljs-attribute">Resources</span>:
#Import S3 bucket
  <span class="hljs-attribute">ETLCovid19Bucket</span>:
    <span class="hljs-attribute">Type</span>: <span class="hljs-attribute">AWS</span>::<span class="hljs-attribute">S3</span>::Bucket
    <span class="hljs-attribute">DeletionPolicy</span>: Retain
    <span class="hljs-attribute">Properties</span>:
      <span class="hljs-attribute">BucketName</span>: <span class="hljs-string">"BucketName"</span>
      <span class="hljs-attribute">Tags</span>:
        - <span class="hljs-attribute">Key</span>: <span class="hljs-string">"xxxxxxxx"</span>
          <span class="hljs-attribute">Value</span>: <span class="hljs-string">"xxxxxxxx"</span>

#Import DynamoDB Table to store data
  <span class="hljs-attribute">ETLCovid19Table</span>:
    <span class="hljs-attribute">Type</span>: <span class="hljs-attribute">AWS</span>::<span class="hljs-attribute">DynamoDB</span>::Table
    <span class="hljs-attribute">DeletionPolicy</span>: Retain
    <span class="hljs-attribute">Properties</span>:
      <span class="hljs-attribute">TableName</span>: <span class="hljs-string">"TableName"</span>
      <span class="hljs-attribute">BillingMode</span>: PROVISIONED
      <span class="hljs-attribute">AttributeDefinitions</span>: 
        -
          <span class="hljs-attribute">AttributeName</span>: <span class="hljs-string">"date"</span>
          <span class="hljs-attribute">AttributeType</span>: <span class="hljs-string">"S"</span>
        -
          <span class="hljs-attribute">AttributeName</span>: <span class="hljs-string">"cases"</span>
          <span class="hljs-attribute">AttributeType</span>: <span class="hljs-string">"S"</span>
        - 
          <span class="hljs-attribute">AttributeName</span>: <span class="hljs-string">"deaths"</span>
          <span class="hljs-attribute">AttributeType</span>: <span class="hljs-string">"S"</span>
        - 
          <span class="hljs-attribute">AttributeName</span>: <span class="hljs-string">"recovered"</span>
          <span class="hljs-attribute">AttributeType</span>: <span class="hljs-string">"S"</span>
      <span class="hljs-attribute">KeySchema</span>: 
        - <span class="hljs-attribute">AttributeName</span>: <span class="hljs-string">"date"</span>
          <span class="hljs-attribute">KeyType</span>: <span class="hljs-string">"HASH"</span>
      <span class="hljs-attribute">ProvisionedThroughput</span>:
        <span class="hljs-attribute">ReadCapacityUnits</span>: <span class="hljs-number">5</span>
        <span class="hljs-attribute">WriteCapacityUnits</span>: <span class="hljs-number">5</span>
      <span class="hljs-attribute">StreamSpecification</span>:
        <span class="hljs-attribute">StreamViewType</span>: NEW_AND_OLD_IMAGES
      <span class="hljs-attribute">Tags</span>:
        - <span class="hljs-attribute">Key</span>: <span class="hljs-string">"xxxxxxxx"</span>
          <span class="hljs-attribute">Value</span>: <span class="hljs-string">"xxxxxxxxxxx"</span>

#Import ETLCovid19 Lambda Function
  <span class="hljs-attribute">ETLCovid19Function</span>:
    <span class="hljs-attribute">DependsOn</span>:
      - ETLCovid19Bucket
      - ETLCovid19Table
    <span class="hljs-attribute">Type</span>: <span class="hljs-attribute">AWS</span>::<span class="hljs-attribute">Lambda</span>::Function
    <span class="hljs-attribute">DeletionPolicy</span>: Retain
    <span class="hljs-attribute">Properties</span>:
      <span class="hljs-attribute">FunctionName</span>: <span class="hljs-string">"FunctionName"</span>
      <span class="hljs-attribute">Handler</span>: <span class="hljs-string">"ETLCovid19.main"</span>
      <span class="hljs-attribute">Role</span>: <span class="hljs-string">"arn:aws:iam::XXXXXXXXXXXXXX:role/service-role/xxxxxxxxx"</span>
      <span class="hljs-attribute">Code</span>:
        <span class="hljs-attribute">S3Bucket</span>: <span class="hljs-string">"xxxxxxxxxxxxxx"</span>
        <span class="hljs-attribute">ZipFile</span>: <span class="hljs-string">"xxxxxx.zip"</span>
      <span class="hljs-attribute">Runtime</span>: <span class="hljs-string">"python3.6"</span>
      <span class="hljs-attribute">Tags</span>:
        - <span class="hljs-attribute">Key</span>: <span class="hljs-string">"xxxxxxxxxx"</span>
          <span class="hljs-attribute">Value</span>: <span class="hljs-string">"xxxxxxxxxxx"</span>

#Import ETLTriggerSNS Lambda Function
  <span class="hljs-attribute">ETLCovid19SNSFunction</span>:
    <span class="hljs-attribute">DependsOn</span>:
      - ETLCovid19Bucket
      - ETLCovid19Table
    <span class="hljs-attribute">Type</span>: <span class="hljs-attribute">AWS</span>::<span class="hljs-attribute">Lambda</span>::Function
    <span class="hljs-attribute">DeletionPolicy</span>: Retain
    <span class="hljs-attribute">Properties</span>:
      <span class="hljs-attribute">FunctionName</span>: <span class="hljs-string">"FunctionName"</span>
      <span class="hljs-attribute">Handler</span>: <span class="hljs-string">"trigger_sns.main"</span>
      <span class="hljs-attribute">Role</span>: <span class="hljs-string">"arn:aws:iam::XXXXXXXXXXXXXX:role/service-role/xxxxxxxxxxxxxxxx"</span>
      <span class="hljs-attribute">Code</span>:
        <span class="hljs-attribute">S3Bucket</span>: <span class="hljs-string">"xxxxxxxxxx"</span>
        <span class="hljs-attribute">ZipFile</span>: <span class="hljs-string">"xxxxxxx.zip"</span>
      <span class="hljs-attribute">Runtime</span>: <span class="hljs-string">"python3.8"</span>
      <span class="hljs-attribute">Tags</span>:
        - <span class="hljs-attribute">Key</span>: <span class="hljs-string">"xxxxxxxx"</span>
          <span class="hljs-attribute">Value</span>: <span class="hljs-string">"xxxxxxxxxxx"</span>

#Import SNS Topic for notification
  <span class="hljs-attribute">ETLCovid19Topic</span>:
    <span class="hljs-attribute">DependsOn</span>:
     - ETLCovid19SNSFunction
    <span class="hljs-attribute">Type</span>: <span class="hljs-attribute">AWS</span>::<span class="hljs-attribute">SNS</span>::Topic
    <span class="hljs-attribute">DeletionPolicy</span>: Retain
    <span class="hljs-attribute">Properties</span>:
      <span class="hljs-attribute">TopicName</span>: <span class="hljs-string">"TopicName"</span>
      <span class="hljs-attribute">Subscription</span>:
        - <span class="hljs-attribute">Endpoint</span>: <span class="hljs-string">"xxx@xxxxx.xx"</span>
          <span class="hljs-attribute">Protocol</span>: <span class="hljs-string">"email"</span>
      <span class="hljs-attribute">Tags</span>:
        - <span class="hljs-attribute">Key</span>: <span class="hljs-string">"xxxxxxxxx"</span>
          <span class="hljs-attribute">Value</span>: <span class="hljs-string">"xxxxxxxxx"</span>

#Import SQS queue for retries
  <span class="hljs-attribute">ETLCovid19Queue</span>:
    <span class="hljs-attribute">DependsOn</span>:
      - ETLCovid19Topic
    <span class="hljs-attribute">Type</span>: <span class="hljs-attribute">AWS</span>::<span class="hljs-attribute">SQS</span>::Queue
    <span class="hljs-attribute">DeletionPolicy</span>: Retain
    <span class="hljs-attribute">Properties</span>:
      <span class="hljs-attribute">QueueName</span>: <span class="hljs-string">"QueueName"</span>
      <span class="hljs-attribute">VisibilityTimeout</span>: <span class="hljs-number">30</span>
      <span class="hljs-attribute">Tags</span>:
        - <span class="hljs-attribute">Key</span>: <span class="hljs-string">"xxxxxxxx"</span>
          <span class="hljs-attribute">Value</span>: <span class="hljs-string">"xxxxxxxxxx"</span>

#Import CloudWatch Rule
  <span class="hljs-attribute">ETLCovid19CloudWatchRule</span>:
    <span class="hljs-attribute">DependsOn</span>: ETLCovid19Function 
    <span class="hljs-attribute">Type</span>: <span class="hljs-attribute">AWS</span>::<span class="hljs-attribute">Events</span>::Rule
    <span class="hljs-attribute">DeletionPolicy</span>: Retain
    <span class="hljs-attribute">Properties</span>: 
      <span class="hljs-attribute">Description</span>: <span class="hljs-string">"Rule to trigger Function ACG_ETLCovid19"</span>
      <span class="hljs-attribute">Name</span>: <span class="hljs-string">"Name"</span>
      <span class="hljs-attribute">ScheduleExpression</span>: <span class="hljs-string">"rate(5 days)"</span>
      <span class="hljs-attribute">State</span>: ENABLED
      <span class="hljs-attribute">Targets</span>: 
        - <span class="hljs-attribute">Arn</span>: <span class="hljs-string">"arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:xxxxxxxxxx"</span>
          <span class="hljs-attribute">Id</span>: <span class="hljs-string">"xxxxxxxxxxxx"</span>
</code></pre><p>Here's a template view in the Designer
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1622582975860/Un5ydLgPP.png" alt="Screen Shot 2021-06-01 at 5.53.29 PM.png" /></p>
<p>Imported resources
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1622583066275/0nHU64Wgk.png" alt="Screen Shot 2021-06-01 at 5.32.09 PM.png" /></p>
<h3 id="cicd">CI/CD</h3>
<p>Deployed my functions to GitHub repository for change control and automatic deployment to AWS Lambda on test pass with GitHub actions. The repo structure is as below:</p>
<pre><code><span class="hljs-bullet">-</span> Repository folder
<span class="hljs-bullet">  -</span> backend
<span class="hljs-bullet">    -</span> Function-1-folder
<span class="hljs-bullet">    -</span> Function-2-folder
<span class="hljs-bullet">    -</span> CloudFormation
</code></pre><p>Test files for each function are located in the function folder. Running python test using Nose2.
Below are the code for the workflows.</p>
<ul>
<li><p>Update on function 1</p>
<pre><code><span class="hljs-attr">name:</span> <span class="hljs-string">Test</span> <span class="hljs-string">and</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">Function</span> <span class="hljs-number">1</span>
<span class="hljs-attr">on:</span> 
<span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">main</span> ]
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'backend/Function-1-folder/**'</span>          
<span class="hljs-attr">jobs:</span>
<span class="hljs-attr">deploy_source:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">Test</span> <span class="hljs-string">and</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">Lambda</span>
  <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
  <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">checkout</span> <span class="hljs-string">source</span> <span class="hljs-string">code</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v1</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Configure</span> <span class="hljs-string">AWS</span> <span class="hljs-string">credentials</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">aws-actions/configure-aws-credentials@v1</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">aws-access-key-id:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_ACCESS_KEY_ID</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">aws-secret-access-key:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_SECRET_ACCESS_KEY</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">aws-region:</span> <span class="hljs-string">'us-east-1'</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">Python</span> <span class="hljs-number">3.6</span> <span class="hljs-string">Environment</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-python@v1</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">python-version:</span> <span class="hljs-number">3.6</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">'Test functions with Nose2'</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">|
        pushd './backend/Function-1-folder/'
        pip install awscli
        python -m pip install --upgrade pip
        pip3 install pandas
        pip3 install numpy
        pip3 install nose2
        pip3 install boto3    
        python -m nose2 test_import_csv.test_import_csv test_transformation.test_transformation
</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">zip</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">montudor/action-zip@v1</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Zip</span> <span class="hljs-string">Package</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">zip</span> <span class="hljs-string">-qq</span> <span class="hljs-string">-r</span> <span class="hljs-string">Function1.zip</span> <span class="hljs-string">.</span>
      <span class="hljs-attr">working-directory:</span> <span class="hljs-string">./backend/Function-1-folder</span>   

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">Lambda</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">appleboy/lambda-action@master</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">aws-access-key-id:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_ACCESS_KEY_ID</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">aws-secret-access-key:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_SECRET_ACCESS_KEY</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">aws-region:</span> <span class="hljs-string">'us-east-1'</span>
        <span class="hljs-attr">function_name:</span> <span class="hljs-string">Function1</span>
        <span class="hljs-attr">zip_file:</span> <span class="hljs-string">./backend/Function-1-folder/Function1.zip</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">package</span> <span class="hljs-string">to</span> <span class="hljs-string">S3</span> <span class="hljs-string">bucket</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">qoqa/action-s3-cp@v1.1</span>
      <span class="hljs-attr">env:</span>
        <span class="hljs-attr">AWS_REGION:</span> <span class="hljs-string">'us-east-1'</span>
        <span class="hljs-attr">AWS_S3_BUCKET:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_S3_BUCKET</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">AWS_ACCESS_KEY_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_ACCESS_KEY_ID</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">AWS_SECRET_ACCESS_KEY:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_SECRET_ACCESS_KEY</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">AWS_S3_PATH:</span> <span class="hljs-string">'/backend/Fucntion1.zip'</span>
        <span class="hljs-attr">FILE:</span> <span class="hljs-string">'./backend/Function-1-folder/Function1.zip'</span>
</code></pre></li>
<li><p>Update on function 2</p>
</li>
</ul>
<p>Paste the same code as above and modify as appropriate.</p>
<h3 id="results">Results</h3>
<p>Below is a dashboard created in QuickSight using the data in the DynamoDB table.
This  <a target="_blank" href="https://dev.to/jdonboch/finally-dynamodb-support-in-aws-quicksight-sort-of-2lbl">article</a>  explains how to visualize DynamoDB data in QuickSight using Athena data connectors.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1622583331722/aA2gvMwrn.png" alt="Screen Shot 2021-06-01 at 10.35.04 PM.png" /></p>
]]></content:encoded></item><item><title><![CDATA[#CloudGuruChallenge – Cloud Resume]]></title><description><![CDATA[The goal of this challenge is to create your resume hosted on Azure or AWS.
I recently achieved the AWS Solutions Architect certification and I was so excited about it that I decided to go for the SysOps exam. Learning for the exam, I came across thi...]]></description><link>https://blogs.houessou.com/cloudguruchallenge-cloud-resume</link><guid isPermaLink="true">https://blogs.houessou.com/cloudguruchallenge-cloud-resume</guid><category><![CDATA[AWS]]></category><category><![CDATA[Azure]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[Python]]></category><category><![CDATA[ci-cd]]></category><dc:creator><![CDATA[Pierre-Francois H]]></dc:creator><pubDate>Mon, 24 May 2021 00:20:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1621821325524/ASSyo932F.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The goal of this challenge is to create your resume hosted on Azure or AWS.
I recently achieved the AWS Solutions Architect certification and I was so excited about it that I decided to go for the SysOps exam. Learning for the exam, I came across this  <a target="_blank" href="https://www.youtube.com/watch?v=DNODbaDQRc0">video</a>  on youtube talking about How many certs were needed to get a cloud job. You may have guessed it:  <strong>hands-on experience and aptitude is your most important asset</strong>.
That's the reason why I decided to take this challenge I first saw on <a target="_blank" href="https://acloudguru.com/blog/engineering/cloudguruchallenge-your-resume-in-azure?utm_source=instagram&amp;utm_medium=social&amp;utm_campaign=cloudguruchallenge">A Cloud Guru</a>. </p>
<p>I completed the challenge both on AWS and Azure.</p>
<h3 id="azure">Azure</h3>
<p>1-
First created a CosmosDB account (SQL Core) with a container to store the site visits count</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1621804944614/oJD4FJSSW.png" alt="CosmosDB.png" /></p>
<p>2-
The second part was to create a Function to retrieve and update the value of the visits count.
Decided to go with Python but I can say that this language is note really appropriate to use with Azure Functions. You have to create you package outside of Azure + not so many sample for CRUD operations on CosmosDB. Definitely had some difficulties here but eventually sort them out with this  <a target="_blank" href="https://docs.microsoft.com/en-us/azure/cosmos-db/create-sql-api-python">article</a>. 
Bellow is a part of the code of the function I deployed to Azure using vscode.</p>
<pre><code>def main(req: <span class="hljs-keyword">func</span>.HttpRequest) -&gt; <span class="hljs-keyword">func</span>.HttpResponse:
    logging.info(<span class="hljs-string">'Python HTTP trigger function processed a request.'</span>)
    getcount = container.read_item(item=<span class="hljs-string">'visits'</span>, partition_key=<span class="hljs-string">'visits'</span>)
    getcount[<span class="hljs-string">'count'</span>] = getcount[<span class="hljs-string">'count'</span>] + <span class="hljs-number">1</span>
    siteVisited = container.upsert_item(body=getcount)

    #<span class="hljs-built_in">print</span>(<span class="hljs-string">'count: {0}'</span>.format(response.get(<span class="hljs-string">'count'</span>)))
    counts = <span class="hljs-string">'{0}'</span>.format(siteVisited.get(<span class="hljs-string">'count'</span>))

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">func</span>.HttpResponse ( 
        status_code=<span class="hljs-number">200</span>,
        headers = {
            <span class="hljs-string">'Access-Control-Allow-Origin'</span>: <span class="hljs-string">'*'</span>,
            <span class="hljs-string">'Access-Control-Allow-Headers'</span>: <span class="hljs-string">'Content-Type,Date,x-ms-session-token'</span>,
            <span class="hljs-string">'Access-Control-Allow-Credentials'</span>: <span class="hljs-string">'false'</span>,
            <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>
        },
        body = counts
    )
</code></pre><p>This function returns only the count value. Don't forget to add the response headers in the return statement.
One thing I like on Azure is the fact that your HTTP trigger functions come with a publicly accessible URL directly (as opposed to AWS where you have to separately create the API as a trigger for the function). Now you have to properly configure CORS for the function to be triggered by the API.</p>
<p>3- 
When I was sure that my function was working and returning the visits count value through GET method, now was the part for the frontend website.
Downloaded a template from  <a target="_blank" href="https://www.themezy.com/">themezy</a> and updated it with my resume information.
At this point, the challenge was to write the JavaScript code to retrieve the count value.
I did a lot of googling here. I knew I had to use the <em>fetch</em> API but I wasn't getting it to work.
Finally came out with this code:</p>
<pre><code><span class="hljs-keyword">const</span> url = <span class="hljs-string">'your function URL'</span>;
fetch(url, {
    <span class="hljs-attr">method</span>: <span class="hljs-string">"GET"</span>,
    <span class="hljs-attr">headers</span>: {
        <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
        <span class="hljs-string">"Accept"</span>: <span class="hljs-string">"application/json"</span>
    },
})
.then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> response.json()
})
.then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Calling API to get visits counts"</span>);
    nbvisited = response;
    <span class="hljs-built_in">console</span>.log(nbvisited);
    <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'nbvisited'</span>).innerText = nbvisited;
})
.catch(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">error</span>) </span>{
  <span class="hljs-built_in">console</span>.log(error);
});
</code></pre><p>4- 
The next step was to create an Azure Blob storage setup as a static website. 
Then create a CDN with the blob storage as the origin and configure HTTPS with your custom domain.
You need to look at the CORS settings of the blob storage as well.</p>
<p>5-
CI/CD with GitHub</p>
<p>I have deployed my project to a GitHub repository and created workflows to update the frontend (update blob storage and purge Azure CDN) on push.
From Azure functions App, you can configure CI/CD with GitHub as deployment center. You function will be updated on Azure when modified on GitHub. However, the created workflow doesn't include code testing (because I use Python). So I added a testing step in the workflow file: testing using <em>nose2</em>. If you use Python as well, make sure that your functions are importable as module otherwise nose2 will not pick them.</p>
<p>Workflow for backend</p>
<pre><code><span class="hljs-attribute">name</span>: Test and Deploy function to Azure Function App

<span class="yaml"><span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
     <span class="hljs-attr">branches:</span> [ <span class="hljs-string">main</span> ]
     <span class="hljs-attr">paths:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">'backend/**'</span>
  <span class="hljs-attr">workflow_dispatch:</span>

<span class="hljs-attr">env:</span>
  <span class="hljs-attr">AZURE_FUNCTIONAPP_PACKAGE_PATH:</span> <span class="hljs-string">'backend'</span> <span class="hljs-comment"># set this to the path to your web app project, defaults to the repository root</span>
  <span class="hljs-attr">PYTHON_VERSION:</span> <span class="hljs-string">'3.8'</span> <span class="hljs-comment"># set this to the python version to use (supports 3.6, 3.7, 3.8)</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build-and-deploy:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">'Checkout GitHub Action'</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">Python</span> <span class="hljs-string">${{</span> <span class="hljs-string">env.PYTHON_VERSION</span> <span class="hljs-string">}}</span> <span class="hljs-string">Environment</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-python@v1</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">python-version:</span> <span class="hljs-string">${{</span> <span class="hljs-string">env.PYTHON_VERSION</span> <span class="hljs-string">}}</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">'Resolve Project Dependencies Using Pip'</span>
      <span class="hljs-attr">shell:</span> <span class="hljs-string">bash</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">|
        pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}'
        python -m pip install --upgrade pip
        pip install -r requirements.txt --target=".python_packages/lib/site-packages"
        popd
</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">'Test functions with Nose2'</span>
      <span class="hljs-attr">shell:</span> <span class="hljs-string">bash</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">|
        pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}'
        python -m pip install --upgrade pip
        pip install nose2
        pip install azure-functions
        pip install azure-cosmos
        python -m nose2
</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">'Run Azure Functions Action'</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">Azure/functions-action@v1</span>
      <span class="hljs-attr">id:</span> <span class="hljs-string">fa</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">app-name:</span> <span class="hljs-string">'hpfgetresumecounter'</span>
        <span class="hljs-attr">slot-name:</span> <span class="hljs-string">'production'</span>
        <span class="hljs-attr">package:</span> <span class="hljs-string">${{</span> <span class="hljs-string">env.AZURE_FUNCTIONAPP_PACKAGE_PATH</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">publish-profile:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AzureAppService_PublishProfile_a7603b82524748699547273836b49631</span> <span class="hljs-string">}}</span></span>
</code></pre><p>6-
Results</p>
<p>Visit my cloud resume on Azure  <a target="_blank" href="https://pierrefrancois1.houessou.com">here</a>.</p>
<p>You can checkout my  <a target="_blank" href="https://github.com/hpfpv/cloudguru-resumecounter-az">GitHub</a>  repository for this project.</p>
<h3 id="aws">AWS</h3>
<p>I actually completed this challenge on AWS before doing it is Azure. I tend to prefer AWS as for me it more easily understandable. The steps are pretty much identical except for the better Python integration on AWS</p>
<p>1-
 Created a DynamoBD table to store my website visits count as item. Straight forward here, just give your table a name and specify the primary key. No need to set API and data model.</p>
<p>2-
 Created a Lambda function to update the visits count in DynamoDB table and return the value of the count. Also make sure to mention the appropriate headers in the return statement.
You need to set an execution role for the function which has read and write access to your DynamoDB table.</p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-type">json</span>
<span class="hljs-keyword">import</span> boto3

dynamodb = boto3.resource(<span class="hljs-string">'dynamodb'</span>)

def lambda_handler(event, context):
    table = dynamodb.<span class="hljs-keyword">Table</span>(<span class="hljs-string">'NameofTable'</span>)
    siteVisited = <span class="hljs-keyword">table</span>.update_item(
        Key={
            <span class="hljs-string">'id'</span>: <span class="hljs-string">'visits'</span>
        },
        UpdateExpression=<span class="hljs-string">'SET counts = counts + :val'</span>,
        ExpressionAttributeValues={
            <span class="hljs-string">':val'</span>: <span class="hljs-number">1</span>
        },
        ReturnValues="UPDATED_NEW"
    )
    getCount = <span class="hljs-keyword">table</span>.scan()
    counts = str(siteVisited[<span class="hljs-string">'Attributes'</span>][<span class="hljs-string">'counts'</span>])
    <span class="hljs-keyword">return</span> {
        <span class="hljs-string">'statusCode'</span>: <span class="hljs-number">200</span>,
        <span class="hljs-string">'headers'</span>: {
            <span class="hljs-string">'Access-Control-Allow-Origin'</span>: <span class="hljs-string">'*'</span>,
            <span class="hljs-string">'Access-Control-Allow-Headers'</span>: <span class="hljs-string">'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'</span>,
            <span class="hljs-string">'Access-Control-Allow-Credentials'</span>: <span class="hljs-string">'false'</span>,
            <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>
        },
        <span class="hljs-string">'body'</span>: <span class="hljs-type">json</span>.dumps(counts)
    }
</code></pre><p>This function also returns only the count value.</p>
<p>3-
Created an API Gateway trigger for the function. Make sure to give <em>lambda:InvokeFunction</em> permission for the function to the API Gateway. Also check CORS settings in case of issue.</p>
<p>4-
From there, it is almost the same with Azure: </p>
<ul>
<li><p>JavaScript code to fetch the API is identical. Just put the URL of the AWS API Gateway</p>
</li>
<li><p>Created an S3 bucket to store the website files. No need to set it as static website or publicly available</p>
</li>
<li><p>Created a CloudFront distribution with the S3 bucket as origin. Configure OAI to allow access to the bucket only from the CloudFront distribution (you have to create an identity)</p>
</li>
<li><p>Configured a custom domain with custom domain AWS managed SSL certificate for HTTPS</p>
</li>
</ul>
<p>5-
CI/CD with GitHub</p>
<p>I have uploaded my project to a GitHub repository and created workflows to update the frontend (update S3 bucket and invalidate CloudFront distribution) on push.
Uploaded the Python function package to the backend folder of the repository and created a workflow file to test and deploy it to Lambda. Testing using <em>nose2</em>. If you use Python as well, make sure that your functions are importable as module otherwise nose2 will not pick them.</p>
<p>Workflow for backend</p>
<pre><code><span class="hljs-attr">name:</span> <span class="hljs-string">Test</span> <span class="hljs-string">and</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">Lambda</span>
<span class="hljs-attr">on:</span> 
  <span class="hljs-attr">push:</span>
      <span class="hljs-attr">branches:</span> [ <span class="hljs-string">main</span> ]
      <span class="hljs-attr">paths:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">'backend/**'</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">deploy_source:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Test</span> <span class="hljs-string">and</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">Lambda</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">checkout</span> <span class="hljs-string">source</span> <span class="hljs-string">code</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v1</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">'Configure AWS credentials'</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">aws-actions/configure-aws-credentials@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">aws-access-key-id:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_ACCESS_KEY_ID</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">aws-secret-access-key:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_SECRET_ACCESS_KEY</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">aws-region:</span> <span class="hljs-string">'us-east-1'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">'Test functions with Nose2'</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          pushd './backend'
          pip install awscli
          python -m pip install --upgrade pip
          pip install nose2
          pip install boto3
          python -m nose2
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">Lambda</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">appleboy/lambda-action@master</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">aws-access-key-id:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_ACCESS_KEY_ID</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">aws-secret-access-key:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_SECRET_ACCESS_KEY</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">aws-region:</span> <span class="hljs-string">'us-east-1'</span>
          <span class="hljs-attr">function_name:</span> <span class="hljs-string">HPFResumeVisitCounter</span>
          <span class="hljs-attr">source:</span> <span class="hljs-string">backend/HPFResumeVisitCounter/GetResumeCounter.py</span>
</code></pre><p>6-
Results</p>
<p>Visit my cloud resume on AWS  <a target="_blank" href="https://pierrefrancois.houessou.com">here</a>.</p>
<p>You can checkout my  <a target="_blank" href="https://github.com/hpfpv/cloudguru-resumecounter-aws">GitHub</a>  repository for this project.</p>
]]></content:encoded></item></channel></rss>