<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Danny Kay</title>
    <description>The latest articles on Forem by Danny Kay (@danieljameskay).</description>
    <link>https://forem.com/danieljameskay</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F308835%2F09bed771-d7d7-498f-ab24-635dfa221fbc.jpg</url>
      <title>Forem: Danny Kay</title>
      <link>https://forem.com/danieljameskay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/danieljameskay"/>
    <language>en</language>
    <item>
      <title>Getting up and running on AWS EKS Fargate</title>
      <dc:creator>Danny Kay</dc:creator>
      <pubDate>Wed, 11 May 2022 16:09:08 +0000</pubDate>
      <link>https://forem.com/danieljameskay/getting-something-super-simple-up-and-running-on-aws-eks-fargate-1ck1</link>
      <guid>https://forem.com/danieljameskay/getting-something-super-simple-up-and-running-on-aws-eks-fargate-1ck1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;EKS Fargate has been on my "investigate" list in Notion for quite some time now. With some spare time the other weekend I thought I'd do some digging and see if I could get something mega basic but repeatable up and running for if I need to use EKS Fargate for a proof of concept or something similar.&lt;/p&gt;

&lt;p&gt;This post walks through how to get a basic application running on EKS Fargate complete with logging in CloudWatch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to follow along?
&lt;/h2&gt;

&lt;p&gt;Alrighty, so before we do anything constructive the repository for all the bits and bobs I'm showing you below can be found &lt;a href="https://github.com/danieljameskay/eks-fargate-example"&gt;here&lt;/a&gt;. Some of the code in the snippets has been left out for brevity.&lt;/p&gt;

&lt;p&gt;Be wary, AWS is fun but that fun comes at a cost. Following this will cost you some dough so keep an eye on the Billing &amp;amp; Cost Management Dashboard people!&lt;/p&gt;

&lt;p&gt;This post assumes you understand the basics of AWS, Kubernetes and Terraform as I won't be going in to deep on these subjects.&lt;/p&gt;

&lt;p&gt;I'll admit, my Terraform could be better, this isn't production worthy Terraform code in anyway or form, just me hacking some things together :) &lt;/p&gt;

&lt;p&gt;Some of my examples are based off the official Terraform EKS Examples Github repository which can be viewed &lt;a href="https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/examples"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Get Started
&lt;/h2&gt;

&lt;p&gt;So before we can even think about doing anything we need somewhere for the Fargate nodes to run i.e. Subnets within a VPC....&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~&amp;gt; 3.0"

  name = local.name
  cidr = "10.0.0.0/16"

  azs             = ["${local.region}a", "${local.region}b", "${local.region}c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"]

  enable_nat_gateway   = true
  single_nat_gateway   = true

  enable_dns_hostnames = true

  public_subnet_tags = {
    "kubernetes.io/cluster/${local.name}" = "shared"
    "kubernetes.io/role/elb"              = 1
  }

  private_subnet_tags = {
    "kubernetes.io/cluster/${local.name}" = "shared"
    "kubernetes.io/role/internal-elb"     = 1
  }

  tags = local.tags
}

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

&lt;/div&gt;



&lt;p&gt;So here we are utilising the VPC Terraform module which allows us to spin up a basic network structure for our EKS Cluster.&lt;/p&gt;

&lt;p&gt;Next we can use the EKS Terraform module for the EKS Cluster...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module "eks" {
  source                          = "terraform-aws-modules/eks/aws"
  version                         = "~&amp;gt; 18.0"

  cluster_name                    = local.name
  cluster_version                 = local.cluster_version
  cluster_endpoint_private_access = true
  cluster_endpoint_public_access  = true

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets
}

resource "null_resource" "kubectl" {
  depends_on = [module.eks]
  provisioner "local-exec" {
    command = "aws eks --region ${local.region} update-kubeconfig --name ${local.name}"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Utilising the EKS Terraform Module means we can get a EKS Cluster up and running super quick without having to get bogged down in Terraform code. &lt;/p&gt;

&lt;p&gt;In the above code snippet we are also using the &lt;code&gt;null_resource&lt;/code&gt; to be able to run a command which updates the local kubeconfig so we can use &lt;code&gt;kubectl&lt;/code&gt; commands against the EKS Cluster as soon as the Terraform &lt;code&gt;apply&lt;/code&gt; command has finished.&lt;/p&gt;

&lt;p&gt;The outputs and providers file haven't been shown in this post but are available in Github Repository.&lt;/p&gt;

&lt;p&gt;Right, now we can go ahead and deploy our VPC and EKS Cluster by running the below in a Terminal...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform init
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploying the EKS Cluster can take some time so feel free to grab a Coffee or alternative beverage whilst it does it's thing.&lt;/p&gt;

&lt;p&gt;Once Terraform has finished deploying everything you should see a success message followed by the ARN of the Cluster, this specific output has been specified in the outputs file...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

cluster_arn = "arn:aws:eks:eu-west-2:012345678901:cluster/ex-eks-fargate"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To verify everything has gone according to plan we can use &lt;code&gt;kubectl&lt;/code&gt; to interact with out newly created Cluster...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ kubectl get ns
NAME              STATUS   AGE
default           Active   3m
kube-node-lease   Active   3m
kube-public       Active   3m
kube-system       Active   3m

➜ kubectl get pods -n kube-system
NAME                       READY   STATUS    RESTARTS   AGE
coredns-679c9bd45b-f7x8b   0/1     Pending   0          3m
coredns-679c9bd45b-zw52k   0/1     Pending   0          3m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hmm, that's interesting, the CoreDNS Pods are stuck in Pending...let's dig into that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Look Ma, No Nodes
&lt;/h2&gt;

&lt;p&gt;So for those of you have worked with the EKS Terraform Module before might have realised that the Worker Groups are missing from the EKS Terraform code.&lt;/p&gt;

&lt;p&gt;Instead of using Worker Groups we utilise Fargate Profiles instead. &lt;/p&gt;

&lt;p&gt;A Fargate Profile is used to determine which pods use Fargate when launched. The beauty of this is that you could have a EKS Cluster which comprises of Self Managed/AWS Managed nodes and Fargate Profiles, using the Fargate Profiles to run a specific workload.&lt;/p&gt;

&lt;p&gt;So how does the Fargate Profile work? &lt;/p&gt;

&lt;p&gt;The Profile contains selectors, if a Pod Deployment contains a selector that matches those in the Profile, it's deployed to Fargate.&lt;/p&gt;

&lt;p&gt;A basic Fargate Profile and IAM configuration is shown here...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_eks_fargate_profile" "default" {
  depends_on             = [module.eks]
  cluster_name           = local.name
  fargate_profile_name   = "default"
  pod_execution_role_arn = aws_iam_role.eks_fargate_profile_role.arn
  subnet_ids             = flatten([module.vpc.private_subnets])

  selector {
    namespace = "kube-system"
  }

  selector {
    namespace = "default"
  }
}

resource "aws_iam_role" "eks_fargate_profile_role" {
  name = "eks-fargate-profile-role"

  assume_role_policy = jsonencode({
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = {
        Service = "eks-fargate-pods.amazonaws.com"
      }
    }]
    Version = "2012-10-17"
  })
}

resource "aws_iam_role_policy_attachment" "AmazonEKSFargatePodExecutionRolePolicy" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSFargatePodExecutionRolePolicy"
  role       = aws_iam_role.eks_fargate_profile_role.name
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We give the Profile a name, specify an Execution Role and specify the Subnets we want our Pods deployed into amongst other attributes.&lt;/p&gt;

&lt;p&gt;The Selector is responsible for selecting Pods which are deployed to the kube-system and default namespace and running them on Fargate Nodes.&lt;/p&gt;

&lt;p&gt;On the IAM side of things, we create a IAM Role and attach the &lt;code&gt;AmazonEKSFargatePodExecutionRolePolicy&lt;/code&gt; to it so it can download container images from ECR and attach the IAM Role to the Fargate Profile as shown in the above code. More information regarding the IAM side of things can be viewed &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/pod-execution-role.html"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Right, now we can go ahead and deploy our Fargate Profile and associated IAM configuration by running &lt;code&gt;terraform apply&lt;/code&gt; as we did in the last section and verifying that the operation was successful.&lt;/p&gt;

&lt;p&gt;But, as you can see below, the CorsDNS Pods are still Pending...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ kubectl get pods -n kube-system
NAME                       READY   STATUS    RESTARTS   AGE
coredns-679c9bd45b-m948h   0/1     Pending   0          2m
coredns-679c9bd45b-wwlfn   0/1     Pending   0          2m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lets dig into why that's happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  So Close, but we have something to "Patch" up
&lt;/h2&gt;

&lt;p&gt;So we have our EKS Cluster, a Fargate Profile which is setup to run Pods in the kube-system namespace...what else do we need to do?&lt;/p&gt;

&lt;p&gt;By default, the CoreDNS deployment is configured to run on Amazon EC2 instances, we need to change that.&lt;/p&gt;

&lt;p&gt;If we run the describe kubectl command against one of the CoreDNS Pods we'll see something interesting...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  eks-fargate kubectl describe pod/coredns-6f6f94db9c-fjhjn -n kube-system
Name:                 coredns-6f6f94db9c-fjhjn
Namespace:            kube-system
Priority:             2000000000
Priority Class Name:  system-cluster-critical
Node:                 &amp;lt;none&amp;gt;
Labels:               eks.amazonaws.com/component=coredns
                      k8s-app=kube-dns
                      pod-template-hash=6f6f94db9c
Annotations:          eks.amazonaws.com/compute-type: ec2
                      kubectl.kubernetes.io/restartedAt: 2022-05-12T15:11:14+01:00
                      kubernetes.io/psp: eks.privileged
Status:               Pending
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that there is an &lt;code&gt;eks.amazonaws.com/compute-type&lt;/code&gt; annotation with the value of &lt;code&gt;ec2&lt;/code&gt;, we need to remove that annotation, but as we have no control over the Deployment files we'll have to run a patch command against the deployment followed by a rollout restart...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl patch deployment coredns \
      --namespace kube-system \
      --type=json -p='[{"op": "remove", "path": "/spec/template/metadata/annotations", "value": "eks.amazonaws.com/compute-type"}]' \

kubectl rollout restart -n kube-system deployment coredns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;rollout restart&lt;/code&gt; command is needed to delete and re-create the existing CoreDNS pods so that they are scheduled on Fargate.&lt;/p&gt;

&lt;p&gt;To make life slightly easier, we can place the above code in a script that is ran once the Fargate Profile has been created...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "null_resource" "fargate_patch_coredns" {
  depends_on = [aws_eks_fargate_profile.default]
  provisioner "local-exec" {
    command = "/bin/bash ./scripts/patch_coredns_deployment.sh"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As in previous steps we can run &lt;code&gt;terraform apply&lt;/code&gt; and wait for Terraform to do it's thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fargate Finale
&lt;/h2&gt;

&lt;p&gt;It will take around a minute for the CoreDNS deployment to be deployed onto Fargate after Terraform has finished applying...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  kubectl get pods -n kube-system
NAME                       READY   STATUS    RESTARTS   AGE
coredns-86dd9db845-m968g   1/1     Running   0          83s
coredns-86dd9db845-pk27d   1/1     Running   0          83s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Happy days! Let's dig a little deeper...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  kubectl get nodes
NAME                                               STATUS   ROLES    AGE   VERSION
fargate-ip-10-0-2-184.eu-west-2.compute.internal   Ready    &amp;lt;none&amp;gt;   89s   v1.22.6-eks-7d68063
fargate-ip-10-0-2-36.eu-west-2.compute.internal    Ready    &amp;lt;none&amp;gt;   84s   v1.22.6-eks-7d68063
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we can see that two Fargate nodes have been fired up, one for each CoreDNS Pod. How exciting!&lt;/p&gt;

&lt;p&gt;If we dig into the logs of one of the Pods we can see the events that took place...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Events:
  Type     Reason           Age    From               Message
  ----     ------           ----   ----               -------
  Warning  LoggingDisabled  3m19s  fargate-scheduler  Disabled logging because aws-logging configmap was not found. configmap "aws-logging" not found
  Normal   Scheduled        2m28s  fargate-scheduler  Successfully assigned kube-system/coredns-86dd9db845-m968g to fargate-ip-10-0-3-190.eu-west-2.compute.internal
  Normal   Pulling          2m27s  kubelet            Pulling image "602401143452.dkr.ecr.eu-west-2.amazonaws.com/eks/coredns:v1.8.7-eksbuild.1"
  Normal   Pulled           2m25s  kubelet            Successfully pulled image "602401143452.dkr.ecr.eu-west-2.amazonaws.com/eks/coredns:v1.8.7-eksbuild.1" in 1.992500087s
  Normal   Created          2m25s  kubelet            Created container coredns
  Normal   Started          2m24s  kubelet            Started container coredns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright, great job!&lt;/p&gt;

&lt;p&gt;Let's move onto to the next step which is getting the logs from the Pods into CloudWatch, so we can get rid of the &lt;code&gt;Disabled logging because aws-logging configmap was not found&lt;/code&gt; error event.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's going on in there?
&lt;/h2&gt;

&lt;p&gt;In order for the logs of our Pods to be transported to CloudWatch, there are two things that need setting up on the Kubernetes side of things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Namespace&lt;/li&gt;
&lt;li&gt;A ConfigMap&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I won't go into what the two artifacts above look like as they can be viewed &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/fargate-logging.html"&gt;here&lt;/a&gt;, but rest assured these are also in the Github repository which can be viewed &lt;a href="https://github.com/danieljameskay/eks-fargate-example"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Utilising a &lt;code&gt;null_resource&lt;/code&gt; and a shell script the above mentioned files are deployed once the Fargate Profile has been configured...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "null_resource" "deploy_logging_artifacts" {
  depends_on = [null_resource.fargate_patch_coredns]
  provisioner "local-exec" {
    command = "/bin/bash ./logging/deploy.sh"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One other thing that we need to do is assign a new policy to our Fargate Profile Role to grant permission to communicate with CloudWatch...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_iam_role_policy_attachment" "cloudwatch_transport_policy_attachment" {
  policy_arn = aws_iam_policy.cloudwatch_transport_iam_policy.arn
  role       = aws_iam_role.eks_fargate_profile_role.name
}

resource "aws_iam_policy" "cloudwatch_transport_iam_policy" {
  name = "cloudwatch-transport-iam-policy"
  path = "/"
  policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [{
      "Effect" : "Allow",
      "Action" : [
        "logs:CreateLogStream",
        "logs:CreateLogGroup",
        "logs:DescribeLogStreams",
        "logs:PutLogEvents",
      ],
      "Resource" : "*"
    }]
  })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As in previous steps, we can run &lt;code&gt;terraform apply&lt;/code&gt; and wait for Terraform to do it's thing.&lt;/p&gt;

&lt;p&gt;As the CoreDNS Pods aren't the most chattiest of things, to verify the logging is working we can just delete a Pod...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl delete pod/coredns-86dd9db845-pk27d -n kube-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then jump into CloudWatch or use the amazing &lt;a href="https://github.com/jorgebastida/awslogs"&gt;awslogs&lt;/a&gt; to view the output...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ awslogs get fluent-bit-cloudwatch --watch --aws-region eu-west-2

fluent-bit-cloudwatch from-fluent-bit-kube.var.log.containers.coredns-5679cff8b6-cmmw8_kube-system_coredns-6e7399229b358343d0469c28a9ca3beaf5646d066250e8c2b76c3b1208f54459.log .:53
fluent-bit-cloudwatch from-fluent-bit-kube.var.log.containers.coredns-5679cff8b6-cmmw8_kube-system_coredns-6e7399229b358343d0469c28a9ca3beaf5646d066250e8c2b76c3b1208f54459.log [INFO] plugin/reload: Running configuration MD5 = 47d57903c0f0ba4ee0626a17181e5d94
fluent-bit-cloudwatch from-fluent-bit-kube.var.log.containers.coredns-5679cff8b6-cmmw8_kube-system_coredns-6e7399229b358343d0469c28a9ca3beaf5646d066250e8c2b76c3b1208f54459.log CoreDNS-1.8.7
fluent-bit-cloudwatch from-fluent-bit-kube.var.log.containers.coredns-5679cff8b6-cmmw8_kube-system_coredns-6e7399229b358343d0469c28a9ca3beaf5646d066250e8c2b76c3b1208f54459.log linux/amd64, go1.17.7, a9adfd56
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The log group name is set to &lt;code&gt;fluent-bit-cloudwatch&lt;/code&gt;, this is defined in the ConfigMap we deployed at the start of this section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Application Up &amp;amp; Running
&lt;/h2&gt;

&lt;p&gt;The official Kubernetes site has a basic application that we can try out on EKS Fargate, it can be viewed &lt;a href="https://kubernetes.io/docs/tutorials/stateless-application/guestbook/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the purpose of this post I've created a small script which deploys the application once the EKS Fargate Terraform code and scripts have finished running. So when the application is deployed the Fargate Nodes will have been patched and the logging configuration will have been deployed...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

cd guestbook-app

kubectl apply -f https://k8s.io/examples/application/guestbook/redis-leader-deployment.yaml
kubectl apply -f https://k8s.io/examples/application/guestbook/redis-leader-service.yaml
kubectl apply -f https://k8s.io/examples/application/guestbook/redis-follower-deployment.yaml
kubectl apply -f https://k8s.io/examples/application/guestbook/redis-follower-service.yaml
kubectl apply -f https://k8s.io/examples/application/guestbook/frontend-deployment.yaml
kubectl apply -f https://k8s.io/examples/application/guestbook/frontend-service.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the Guestbook deployment has no namespace specified it will be deploy into the default one, as we specified the default namespace as a selector earlier on, we don't have to make any changes to accommodate the Guestbook.&lt;/p&gt;

&lt;p&gt;Right, to verify everything works correctly I've destroyed the existing setup by running &lt;code&gt;terraform destroy&lt;/code&gt; then running &lt;code&gt;terraform apply&lt;/code&gt; as we did earlier.&lt;/p&gt;

&lt;p&gt;When Terraform has finished applying we should be able to verify that the Guestbook has been deployed successfully...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ kubectl get pods                         
NAME                             READY   STATUS    RESTARTS   AGE
frontend-85595f5bf9-9nc27        1/1     Running   0          5m19s
frontend-85595f5bf9-hkj5s        1/1     Running   0          5m23s
frontend-85595f5bf9-jnnpt        1/1     Running   0          5m21s
redis-follower-dddfbdcc9-88cr9   1/1     Running   0          5m25s
redis-follower-dddfbdcc9-j95b9   1/1     Running   0          5m24s
redis-leader-fb76b4755-b4zpx     1/1     Running   0          5m27s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lets connect to the frontend service we created using &lt;code&gt;port-forward&lt;/code&gt;...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  kubectl port-forward svc/frontend 8080:80
Forwarding from 127.0.0.1:8080 -&amp;gt; 80
Forwarding from [::1]:8080 -&amp;gt; 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is working correctly, you should be able to navigate to &lt;a href="http://localhost:8080"&gt;http://localhost:8080&lt;/a&gt; and interact with the Guestbook application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;So there we are, we've run through how to get a basic EKS Fargate Cluster up and running, how to schedule Pods on to Fargate and how to get basic logging functioning.&lt;/p&gt;

&lt;p&gt;I hope everyone who has read this post has enjoyed it and if anyone has any questions drop me a comment or a tweet!&lt;/p&gt;

&lt;p&gt;Cover Photo by Chuttersnap on &lt;a href="https://unsplash.com/@chuttersnap"&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I've dropped a few links in the post but there are a few more below which you may find beneficial.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/fargate-logging.html"&gt;https://docs.aws.amazon.com/eks/latest/userguide/fargate-logging.html&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/fargate-profile.html"&gt;https://docs.aws.amazon.com/eks/latest/userguide/fargate-profile.html&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/fargate-getting-started.html"&gt;https://docs.aws.amazon.com/eks/latest/userguide/fargate-getting-started.html&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/hashicorp/terraform-provider-kubernetes/issues/723"&gt;https://github.com/hashicorp/terraform-provider-kubernetes/issues/723&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/aws-ia/terraform-aws-eks-blueprints/issues/394"&gt;https://github.com/aws-ia/terraform-aws-eks-blueprints/issues/394&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/terraform-aws-modules/terraform-aws-eks/issues/1286"&gt;https://github.com/terraform-aws-modules/terraform-aws-eks/issues/1286&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/vmware-tanzu/octant"&gt;https://github.com/vmware-tanzu/octant&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stay safe!&lt;/p&gt;

&lt;p&gt;Cheers&lt;br&gt;
DK&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>serverless</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Triggering Lambda Functions From Amazon MSK</title>
      <dc:creator>Danny Kay</dc:creator>
      <pubDate>Mon, 21 Sep 2020 15:52:27 +0000</pubDate>
      <link>https://forem.com/danieljameskay/triggering-lambda-functions-from-amazon-msk-316o</link>
      <guid>https://forem.com/danieljameskay/triggering-lambda-functions-from-amazon-msk-316o</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;I was on Twitter last month and came across the below tweet.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1294178133164339200-758" src="https://platform.twitter.com/embed/Tweet.html?id=1294178133164339200"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1294178133164339200-758');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1294178133164339200&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;This announcement was pretty big in my opinion.&lt;/p&gt;

&lt;p&gt;Having the ability to trigger AWS Lambda functions from Amazon MSK records allows us to utilize many benefits of Serverless computing.&lt;/p&gt;

&lt;p&gt;I had a bit of spare time on my hands so thought I'd have a little dig into this new feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to follow along?
&lt;/h2&gt;

&lt;p&gt;Alrighty, so before we do anything constructive the repository for all the bits and bobs I'm showing you below can be found &lt;a href="https://github.com/danieljameskay/Dev-Article-MSK-Lambda-Examples" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Be wary, AWS is fun but that fun comes at a cost. Following this will cost you some dough so keep an eye on the Billing &amp;amp; Cost Management Dashboard people!&lt;/p&gt;

&lt;p&gt;This post assumes you know your way around AWS and understand how Kafka works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the Infrastructure
&lt;/h2&gt;

&lt;p&gt;Before writing our Lambda functions we need some infrastructure, I'm using CloudFormation in this example. AWS has kindly provided a template that provides a basic architecture of a 3 node Kafka Cluster, VPC, Subnet setup, and an EC2 bastion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-msk-cluster.html#aws-resource-msk-cluster--examples" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-msk-cluster.html#aws-resource-msk-cluster--examples&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've tweaked this ever so slightly so that we can communicate with the broker using &lt;code&gt;TLS_PLAINTEXT&lt;/code&gt; instead of &lt;code&gt;TLS&lt;/code&gt;, I've modified the MSK Security group to allow MSK to communicate with itself and I'm running 2 Brokers instead of 3.&lt;/p&gt;

&lt;p&gt;You will also need an EC2 Key Pair so go ahead into the EC2 Console and create one, making sure you download the Key and keep it somewhere safe and out of harm's way.&lt;/p&gt;

&lt;p&gt;With the two pieces of pivotal information, we can go ahead and run the script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ deploy-msk-infrastructure.sh IP_ADDRESS KEY_PAIR

➜ deploy-msk-infrastructure.sh 1.2.3.4/32 demo-keypair.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It takes around 20–30 minutes for everything to be up, running, and functional. &lt;/p&gt;

&lt;p&gt;Once we are cooking with gas we should be able to SSH into our Bastion, create a Topic, and produce a record to it.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ chmod 400 demo-keypair.pem

➜ ssh -i "demo-keypair.pem" ec2-user@ec2-1-2-3-4.eu-west-2.compute.amazonaws.com


__|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|
https://aws.amazon.com/amazon-linux-2/


[ec2-user@ip-1-2-3-4 ~]➜ ./kafka/kafka_2.12-2.2.1/bin/kafka-topics.sh --create --topic example \
--bootstrap-server b-1.mskcluster.vxbd2z.c4.kafka.eu-west-2.amazonaws.com:9092 --partitions 1 --replication-factor 1

[ec2-user@ip-1-2-3-4 ~]➜ ./kafka/kafka_2.12-2.2.1/bin/kafka-console-producer.sh --topic example --broker-list b-1.mskcluster.vxbd2z.c4.kafka.eu-west-2.amazonaws.com:9092

&amp;gt;Hello!

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

&lt;/div&gt;


&lt;p&gt;And then consume it.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ec2-user@ip-1-2-3-4 ~]➜ ./kafka/kafka_2.12-2.2.1/bin/kafka-console-consumer.sh --topic example --bootstrap-server b-1.mskcluster.vxbd2z.c4.kafka.eu-west-2.amazonaws.com:9092 --from-beginning

Hello!

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

&lt;/div&gt;


&lt;p&gt;Alrighty, we have verified that our Cluster is alive and kicking. Now we can go ahead and create a Lambda function to verify the connectivity.&lt;/p&gt;
&lt;h2&gt;
  
  
  Simple Lambda Function
&lt;/h2&gt;

&lt;p&gt;Before we get into the code lets just go over how a Lambda function is actually invoked by Amazon MSK.&lt;/p&gt;

&lt;p&gt;A Lambda function is responsible for handling our records. But there is another process that happens before this which is an Event Source Mapping.&lt;/p&gt;

&lt;p&gt;An Event Source Mapping is a Lambda process that reads data from a source system and invokes a function with the data it has received from the source system.&lt;/p&gt;

&lt;p&gt;The Event Source Mapping that we create in the two examples below is responsible for reading the records from the Topics and invoking the functions.&lt;/p&gt;

&lt;p&gt;The Event Source Mapping can be created from the AWS Lambda Console but it can also be created from a CloudFormation Template which we'll be doing in this example.&lt;/p&gt;

&lt;p&gt;Right let's create this basic Lambda function, and when I say basic, I mean basic!&lt;/p&gt;

&lt;p&gt;Think of this Lambda as verifying that it can communicate with Kafka and all the permissions are set up correctly.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package com.danieljameskay.main

import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.RequestHandler
import com.danieljameskay.models.MSKEvent
import com.google.gson.Gson
import com.google.gson.GsonBuilder

class App(var gson: Gson = GsonBuilder().setPrettyPrinting().create()) : RequestHandler&amp;lt;MSKEvent, String&amp;gt; {
    override fun handleRequest(event: MSKEvent, context: Context): String {
        val logger = context.logger
        logger.log(gson.toJson(event))
        return "OK"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Quickly running through the code. The JSON Event is Mapped into an &lt;code&gt;MSKEvent&lt;/code&gt; object and the contents are logged out.&lt;/p&gt;

&lt;p&gt;Following the link &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/with-msk.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;, we have to create a Role with some extra permissions and configure the Event Source for the Lambda function as shown below in the below CloudFormation Template.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: '2010-09-09'
Description: "Basic Lambda Function to verify MSK connectivity."
Parameters:
  EventSourceArn:
    Description: ARN of the Event Source, Kafka in this case
    Type: String
  TopicName:
    Description: Topic for the Lambda to consume from
    Type: String
  S3Bucket:
    Description: S3 Bucket for the JAR
    Type: String
  S3Key:
    Description: JAR Name
    Type: String
  Handler:
    Description: Application Handler
    Type: String
Resources:
  BasicKafkaConsumerLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName:
        Fn::Sub: BasicKafkaConsumerLambdaRole
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
        Version: 2012-10-17
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSLambdaExecute
        - arn:aws:iam::aws:policy/service-role/AWSLambdaMSKExecutionRole
      Path: /

  BasicKafkaConsumerLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Name: BasicKafkaConsumerLambdaFunction
      Description: BasicKafkaConsumerLambdaFunction
      Runtime: java11
      Code:
        S3Bucket: !Ref S3Bucket
        S3Key: !Ref S3Key
      Handler: !Ref Handler
      MemorySize: 128
      Timeout: 10
      Role:
        Fn::GetAtt:
          - BasicKafkaConsumerLambdaRole
          - Arn

  EventSourceMapping:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      EventSourceArn: !Ref EventSourceArn
      FunctionName: !Ref BasicKafkaConsumerLambdaFunction
      StartingPosition : LATEST
      Topics:
        - !Ref TopicName

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

&lt;/div&gt;


&lt;p&gt;With the Lambda, Role, and Event Source Mapping configured in CloudFormation, we can go ahead and run the script.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ ./deploy-lambda.sh UBER_JAR_LOCATION \
S3_BUCKET_TO_UPLOAD_TOO \
STACK_NAME \
EVENT_SOURCE_ARN \
TOPIC_NAME \
S3_BUCKET_WHICH_CONTAINS_JAR \
JAR_NAME \
LAMBDA_HANDLER 

➜ ./deploy-lambda.sh ./basic-function-all.jar \
s3://my-bucket/basic-function-all.jar \
Basic-Lambda-Stack \     
arn:aws:kafka:eu-west-2:111111111111:cluster/MSKCluster/f0705c96-e239-4f74-a0f7-f82031a2fc65-4 \
example \
my-bucket \
basic-function-all.jar \
com.danieljameskay.main.App::handleRequest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This script is extremely basic, it uses Gradle to build the app, creates a JAR, uploads it to S3, and finally creates the CloudFormation stack. It should take a couple of minutes for everything to be deployed.&lt;/p&gt;

&lt;p&gt;As we did before, connect back to the Bastion and produce some basic test records.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ec2-user@ip-1-2-3-4 ~]➜ ./kafka/kafka_2.12-2.2.1/bin/kafka-console-producer.sh --topic example --broker-list b-1.mskcluster.vxbd2z.c4.kafka.eu-west-2.amazonaws.com:9092 --property "parse.key=true" --property "key.separator=:"
&amp;gt; 1:Hello!
&amp;gt; 2:Hey!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then we can head to the Terminal and query the CloudWatch Logs.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ aws logs get-log-events --log-group-name /aws/lambda/BasicKafkaConsumerLambdaFunction --log-stream-name '2020/09/20/[$LATEST]8dd5e9881b7f456897d62d39b654e36d'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We can see in the logs the records printed out. Below is a prettier version that has been escaped, values restored and is easier to read.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "records": {
    "example-0": [
      {
        "topic": "example",
        "partition": 0,
        "offset": 0,
        "timestamp": 1600598451721,
        "timestampType": "CREATE_TIME",
        "key": "MQ==",
        "value": "SGVsbG8h"
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "records": {
    "example-1": [
      {
        "topic": "example",
        "partition": 1,
        "offset": 0,
        "timestamp": 1600598468528,
        "timestampType": "CREATE_TIME",
        "key": "Mg==",
        "value": "SGV5IQ=="
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So we can see the records have been grouped into an array based on the partition they came from. We also get some metadata such as the timestamp and the value of the record has been encoded in base64.&lt;/p&gt;

&lt;p&gt;The custom class I created which is used by the function handler to map the JSON into an MSKEvent object in this example drops the &lt;code&gt;EventSource&lt;/code&gt; and &lt;code&gt;EventSourceARN&lt;/code&gt; fields and if a Key isn't provided a zero will be visible in the payload.&lt;/p&gt;

&lt;p&gt;Below is an example of what the payload would look like if our function consumed multiple records from different partitions.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "records": {
    "example-1": [
      {
        "topic": "example",
        "partition": 1,
        "offset": 0,
        "timestamp": 1599337456523E12,
        "timestampType": "CREATE_TIME",
        "key": "0",
        "value": "cWVxd2Vxd2U="
      }
    ],
    "example-2": [
      {
        "topic": "example",
        "partition": 2,
        "offset": 0,
        "timestamp": 1599337455636E12,
        "timestampType": "CREATE_TIME",
        "key": "0",
        "value": "cXdlcXdl"
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Consumer Groups
&lt;/h2&gt;

&lt;p&gt;A quick note on Consumer Groups. Multiple Consumers can work as part of a Consumer Group to increase parallelism. This allows the Partitions in the Topic to be divided between the available Consumers for consumption.&lt;/p&gt;

&lt;p&gt;When the Consumer Group is registered with MSK, the group has the same name as the event source mapping UUID. If you aren't sure what the UUID is of your Event Source Mapping follow the instructions in the first link at the bottom of this post.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ aws lambda get-event-source-mapping --uuid 02d6c44a-46a7-41fe-8dd0-0f8ab903fee2
{
    "UUID": "02d6c44a-46a7-41fe-8dd0-0f8ab903fee2",
    "BatchSize": 100,
    "EventSourceArn": "arn:aws:kafka:eu-west-2:111111111111:cluster/MSKCluster/f0705c96-e239-4f74-a0f7-f82031a2fc65-4",
    "FunctionArn": "arn:aws:lambda:eu-west-2:111111111111:function:BasicKafkaConsumerLambdaFunction",
    "LastModified": "2020-09-20T11:18:49.525000+01:00",
    "LastProcessingResult": "OK",
    "State": "Enabled",
    "StateTransitionReason": "USER_INITIATED",
    "Topics": [
        "example"
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then cross-reference this using the Kafka Consumer Group CLI.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ec2-user@ip-1-2-3-4 ~]➜ ./kafka/kafka_2.12-2.2.1/bin/kafka-consumer-groups.sh  --list  --bootstrap-server b-1.mskcluster.vxbd2z.c4.kafka.eu-west-2.amazonaws.com:9092

02d6c44a-46a7-41fe-8dd0-0f8ab903fee2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So we can see that the UUID of our Event Source Mapping does match the name of the Consumer Group. The last command will display the current offset and lag for each Partition which is useful to understand how our Consumer Group is performing.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ec2-user@ip-1-2-3-4 ~]➜ ./kafka/kafka_2.12-2.2.1/bin/kafka-consumer-groups.sh --bootstrap-server b-1.mskcluster.vxbd2z.c4.kafka.eu-west-2.amazonaws.com:9092 --describe --group 02d6c44a-46a7-41fe-8dd0-0f8ab903fee2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Integrating SQS
&lt;/h2&gt;

&lt;p&gt;Now we're going to step it up a notch. &lt;/p&gt;

&lt;p&gt;We're going to insert some real-world data into Kafka then trigger a Lambda function to send that data into an SQS queue.&lt;/p&gt;

&lt;p&gt;Why SQS you might ask?&lt;/p&gt;

&lt;p&gt;SQS was the very first AWS service that launched back in November 2004 and to this very day, it is still an extremely popular service used to build queue-based solutions.&lt;/p&gt;

&lt;p&gt;One of my favorite Microservice patterns is the Anti-Corruption Layer Pattern. It allows us to provide a layer between two systems that need to be integrated where the semantics are different.&lt;/p&gt;

&lt;p&gt;We're going to build a tiny ACL in this example. This means mapping the event generated by MSK into a smaller data structure, serializing the data to JSON, and sending it into an SQS queue.&lt;/p&gt;

&lt;p&gt;I have a small application running which is receiving updates every 20 seconds from the &lt;a href="https://docs.poloniex.com/#24-hour-exchange-volume" rel="noopener noreferrer"&gt;Poloniex 24 Hour Exchange Volume API&lt;/a&gt;. Its sending the updates it receives onto MSK. The data received has the below structure.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "USDC": "2811608.201",
  "PAX": "100.623",
  "DAI": "202.840",
  "USDJ": "14553.678",
  "USDT": "36356839.873",
  "TRX": "73155426.619",
  "BUSD": "7581.134",
  "BTC": "559.254",
  "BNB": "1717.156",
  "ETH": "70.466",
  "XMR": "0.000"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This application is running on the Bastion EC2 instance just for the purpose of this demo. Before we do anything, we have to create a Topic for records to be sent to.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ec2-user@ip-1-2-3-4 ~]➜ ./kafka/kafka_2.12-2.2.1/bin/kafka-topics.sh --create --topic example.poloniex.24hr.exchange.volume --bootstrap-server b-1.mskcluster.vxbd2z.c4.kafka.eu-west-2.amazonaws.com:9092 --partitions 3 --replication-factor 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;As we did earlier in this article, we'll need to deploy our new Lambda function and some Cloudformation Templates for our SQS resource. &lt;/p&gt;

&lt;p&gt;First, we'll deploy our SQS Queue.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ ./SQS-Integration/deploy-infrastructure STACK_NAME NAME_OF_QUEUE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The CloudFormation Template for SQS is really simple, it just requires the name of the Queue we want to create.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: "2010-09-09"
Description: "Creates an SQS queue for the Poloniex Exchange Volume Lambda to send records to."
Parameters:
  QueueName:
    Description: Name of the Queue
    Type: String
Resources: 
  SQSQueue: 
    Type: AWS::SQS::Queue
    Properties: 
      QueueName: !Ref QueueName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Application is pretty straight forward. A record is produced to Kafka, Lambda is triggered, the JSON event is mapped into an &lt;code&gt;MSKEvent&lt;/code&gt; object, the records inside the event are looped over and mapped into a basic &lt;code&gt;ExchangeVolumeUpdate&lt;/code&gt; object and sent to SQS.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;The CloudFormation template is pretty similar to the earlier one we used for the basic Lambda function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: '2010-09-09'
Description: "Poloniex Exchange Volume Lambda Function"
Parameters:
  EventSourceArn:
    Description: ARN of the Event Source, Kafka in this case
    Type: String
  TopicName:
    Description: Topic for the Lambda to consume from
    Type: String
  S3Bucket:
    Description: S3 Bucket for the JAR
    Type: String
  S3Key:
    Description: JAR Name
    Type: String
  Handler:
    Description: Application Handler
    Type: String

Resources:
  PoloniexExchangeVolumeLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName:
        Fn::Sub: PoloniexExchangeVolumeLambdaRole
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
        Version: 2012-10-17
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSLambdaExecute
        - arn:aws:iam::aws:policy/service-role/AWSLambdaMSKExecutionRole
        - arn:aws:iam::aws:policy/AmazonSQSFullAccess
      Path: /

  PoloniexExchangeVolumeLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: PoloniexExchangeVolumeLambdaFunction
      Description: PoloniexExchangeVolumeLambdaFunction
      Runtime: java11
      Code:
        S3Bucket: !Ref S3Bucket
        S3Key: !Ref S3Key
      Handler: !Ref Handler
      MemorySize: 256
      Timeout: 10
      Role:
        Fn::GetAtt:
          - PoloniexExchangeVolumeLambdaRole
          - Arn

  EventSourceMapping:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      EventSourceArn: !Ref EventSourceArn
      FunctionName: !Ref PoloniexExchangeVolumeLambdaFunction
      StartingPosition : LATEST
      Topics:
        - !Ref TopicName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we'll deploy our Lambda.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ ./deploy-lambda.sh UBER_JAR_LOCATION \
S3_BUCKET_TO_UPLOAD_TOO \
STACK_NAME \
EVENT_SOURCE_ARN \
TOPIC_NAME \
S3_BUCKET_WHICH_CONTAINS_JAR \
JAR_NAME \
LAMBDA_HANDLER

➜ ./deploy-lambda.sh ./sqs-integration-all.jar \
s3://my-bucket/sqs-integration-all.jar \
MSK-SQS-Lambda-Stack \     
arn:aws:kafka:eu-west-2:111111111111:cluster/MSKCluster/f0705c96-e239-4f74-a0f7-f82031a2fc65-4 \
example.poloniex.24hr.exchange.volume \
my-bucket \
sqs-integration-all.jar \
com.danieljameskay.sqs.integration.main.App::handleRequest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script behaves in the same way as the one used in the basic example above, it uses Gradle to build the app, creates a JAR, uploads it to S3, and finally creates the CloudFormation stack. It should take a couple of minutes for everything to be deployed.&lt;/p&gt;

&lt;p&gt;The application is running and sending records to MSK so as soon as our Lambda is deployed we should be able to use the SQS web interface to verify records are being sent to SQS with no issues.&lt;/p&gt;

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

&lt;p&gt;We can also view the CloudWatch graphs to gain insight into how our function is behaving.&lt;/p&gt;

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

&lt;p&gt;Happy days! Everything is working as it should!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;So there we are, we've run through how to invoke some basic Lambda Functions from MSK and utilized CloudFormation to create the infrastructure.&lt;/p&gt;

&lt;p&gt;I hope everyone who has read this post has enjoyed it and if anyone has any questions drop me a comment or a tweet!&lt;/p&gt;

&lt;p&gt;Cover photo by &lt;a href="https://unsplash.com/@planner1963" rel="noopener noreferrer"&gt;Pieter van de Sande&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/rivers?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Useful links:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/with-msk.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/lambda/latest/dg/with-msk.html&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventsourcemapping.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventsourcemapping.html&lt;/a&gt;&lt;br&gt;
&lt;a href="https://aws.amazon.com/blogs/compute/using-amazon-msk-as-an-event-source-for-aws-lambda/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/compute/using-amazon-msk-as-an-event-source-for-aws-lambda/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stay safe!&lt;/p&gt;

&lt;p&gt;Cheers&lt;br&gt;
DK&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kafka</category>
      <category>serverless</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a serverless COVID-19 notification pipeline</title>
      <dc:creator>Danny Kay</dc:creator>
      <pubDate>Sat, 18 Apr 2020 11:05:59 +0000</pubDate>
      <link>https://forem.com/danieljameskay/building-a-serverless-covid-19-notification-pipeline-4g79</link>
      <guid>https://forem.com/danieljameskay/building-a-serverless-covid-19-notification-pipeline-4g79</guid>
      <description>&lt;h1&gt;
  
  
  Intro
&lt;/h1&gt;

&lt;p&gt;Due to the lockdown in place because of COVID-19, I found myself with more spare time than usual so I decided to put it to better use. I'd built plenty of Lambda's over the years but I'd never built a fully serverless solution...so whatever I was building was going to be serverless.&lt;/p&gt;

&lt;p&gt;I was having a browse around Github at COVID-19 datasets and came across a repository that contained data for the UK and was updated daily...then I had a lightbulb moment! &lt;/p&gt;

&lt;p&gt;I'd seen a ton of amazing dashboards tracking cases all over the globe but my angle was different. I'd ingest the UK data from Github and somehow send out an email showing the increase in confirmed cases, tests and deaths.&lt;/p&gt;

&lt;p&gt;I put my AWS cap on and came up with a basic solution...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Lambda that ingests a CSV document from Github and stores it in S3, triggered by a CloudWatch event&lt;/li&gt;
&lt;li&gt;A Lambda that reads the CSV doc from S3 and stores the latest data in DynamoDB, utilising DynamoDB as a materialised view&lt;/li&gt;
&lt;li&gt;Another Lambda which is triggered by a DynamoDB Stream and calculates the difference between the old data and new data then sends out an email notification advising of the changes &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I put the wheels in motion...&lt;/p&gt;

&lt;h2&gt;
  
  
  First steps - Installing AWS SAM and Creating our Project
&lt;/h2&gt;

&lt;p&gt;We are going to be using the AWS SAM framework to build the pipeline. It's pretty straightforward to get installed and the instructions can be found &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With SAM installed, we can create a new project by running &lt;code&gt;sam init&lt;/code&gt; and following the CLI prompts.&lt;br&gt;
&lt;/p&gt;

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

Which template &lt;span class="nb"&gt;source &lt;/span&gt;would you like to use?

1 - AWS Quick Start Templates
2 - Custom Template Location

Choice: 1

Which runtime would you like to use?
1 - nodejs12.x
2 - python3.8
3 - ruby2.7
4 - go1.x
5 - java11
6 - dotnetcore3.1
7 - nodejs10.x
8 - python3.7
9 - python3.6
10 - python2.7
11 - ruby2.5
12 - java8
13 - dotnetcore2.1
14 - dotnetcore2.0
15 - dotnetcore1.0

Runtime: 8

Project name &lt;span class="o"&gt;[&lt;/span&gt;sam-app]: Serverless-Covid-19-Pipeline
Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

AWS quick start application templates:

1 - Hello World Example
2 - EventBridge Hello World
3 - EventBridge App from scratch &lt;span class="o"&gt;(&lt;/span&gt;100+ Event Schemas&lt;span class="o"&gt;)&lt;/span&gt;

Template selection: 1

&lt;span class="nt"&gt;-----------------------&lt;/span&gt;
Generating application:
&lt;span class="nt"&gt;-----------------------&lt;/span&gt;
Name: Serverless-Covid-19-Pipeline
Runtime: python3.7
Application Template: hello-world
Output Directory: &lt;span class="nb"&gt;.&lt;/span&gt;

Next steps can be found &lt;span class="k"&gt;in &lt;/span&gt;the README file at ./Covid-19-Serverless-Pipeline/README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I chose to write the Lambda's in Python because I'm a big fan of the language and its something I want to use more in my projects even though I'm still learning the language.&lt;/p&gt;

&lt;p&gt;With the project created we should have a folder structure like the following...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Serverless-Covid-19-Pipeline/
├── README.md
├── events/
│ └── event.json
├── HelloWorld/
│ ├── __init__.py
│ └── app.py
│ └── requirements.txt
├── template.yaml
├── .gitignore
└── tests/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can trigger a build by running the &lt;code&gt;sam build&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;▶ sam build
Building resource &lt;span class="s1"&gt;'HelloWorldFunction'&lt;/span&gt;
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
&lt;span class="o"&gt;=========================&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Invoke Function: sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Deploy: sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see our first build succeeded.  In order to test the function we need to have Docker installed on our machine, we'll do this later when we have our first Lambda function written. &lt;/p&gt;

&lt;p&gt;As this project is going to contain multiple Lambdas, we have to create the directories for the two other Lambdas, add the relevant files and rename the existing Lambda folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Serverless-Covid-19-Pipeline/
├── README.md
├── events/
│ └── event.json
├── data_ingestion_function/
│ ├── __init__.py
│ └── app.py
│ └── requirements.txt
├── s3_processing_function/
│ ├── __init__.py
│ └── app.py
│ └── requirements.txt
├── ddb_stream_notification/
│ ├── __init__.py
│ └── app.py
│ └── requirements.txt
├── template.yaml
├── .gitignore
└── tests/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm not the best person when it comes to naming things, the &lt;code&gt;data_ingestion_function&lt;/code&gt; will contain the Lamba which downloads the data from Github and save it to S3, the &lt;code&gt;s3_processing_function&lt;/code&gt; will contain the Lambda which downloads the data from S3 and saves it to DynamoDB and lastly, the &lt;code&gt;ddb_stream_notification&lt;/code&gt; will contain the Lambda which receives the DynamoDB stream and sends the message to SNS.&lt;/p&gt;

&lt;h2&gt;
  
  
  First Function - Data Ingestion
&lt;/h2&gt;

&lt;p&gt;The first Lambda function we have to build is responsible for downloading the data from the Github repository and saving it to S3. This Lambda is going to be triggered by a CloudWatch Scheduled Event, so it can be invoked at the same time every day.&lt;/p&gt;

&lt;p&gt;We're using the &lt;code&gt;requests&lt;/code&gt; and &lt;code&gt;boto3&lt;/code&gt; libraries in this example so make sure they are included in the &lt;code&gt;requirements.txt&lt;/code&gt; file and install them.&lt;/p&gt;

&lt;p&gt;The first thing we want to do open the &lt;code&gt;app.py&lt;/code&gt; file and replace the boilerplate code with our function code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2Fdata-ingestion-app-new.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2Fdata-ingestion-app-new.png" alt="Data Ingestion Lambda"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the function is invoked it downloads the raw data from Github, writes the data to a CSV file in the tmp folder and then uploads this file to the specified S3 Bucket.&lt;/p&gt;

&lt;p&gt;One of the amazing features of the AWS SAM Framework is the ability to test Lambda functions locally before deploying them to AWS.&lt;/p&gt;

&lt;p&gt;In order to test our function, we need a mock scheduled event. We can create one by running &lt;code&gt;sam local generate-event cloudwatch scheduled-event&lt;/code&gt;. This will generate a JSON snippet which we can modify as necessary and paste into a new JSON file named &lt;code&gt;scheduled-event.json&lt;/code&gt; within the events folder of the parent directory.&lt;/p&gt;

&lt;p&gt;But, we have a little problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an S3 Bucket with CloudFormation
&lt;/h2&gt;

&lt;p&gt;When we test this Lambda it will try and upload the file to our S3 Bucket...which we don't have. We need to modify the &lt;code&gt;template.yaml&lt;/code&gt; and deploy our Cloudformation Stack to create the S3 Bucket.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;AWSTemplateFormatVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2010-09-09'&lt;/span&gt;
&lt;span class="na"&gt;Transform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless-2016-10-31&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;Serverless-Covid-19-Pipeline&lt;/span&gt;

  &lt;span class="s"&gt;SAM Template for Serverless-Covid-19-Pipeline&lt;/span&gt;

&lt;span class="na"&gt;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="na"&gt;S3Bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
    &lt;span class="na"&gt;Default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;danieljameskay-data-ingestion-bucket&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;DataIngestionBucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::S3::Bucket&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;BucketName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;S3Bucket&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To deploy our CloudFormation Stack, we need to run &lt;code&gt;sam deploy --guided&lt;/code&gt;, this will allow us to save values for deployment which will be used for future deployments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;sam build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
&lt;span class="o"&gt;=========================&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Invoke Function: sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Deploy: sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;


Configuring SAM deploy
&lt;span class="o"&gt;======================&lt;/span&gt;

        Looking &lt;span class="k"&gt;for &lt;/span&gt;samconfig.toml :  Not found

        Setting default arguments &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s1"&gt;'sam deploy'&lt;/span&gt;
        &lt;span class="o"&gt;=========================================&lt;/span&gt;
        Stack Name &lt;span class="o"&gt;[&lt;/span&gt;sam-app]: serverless-covid-19-pipeline
        AWS Region &lt;span class="o"&gt;[&lt;/span&gt;us-east-1]: eu-west-2
        &lt;span class="c"&gt;#Shows you resources changes to be deployed and require a 'Y' to initiate deploy&lt;/span&gt;
        Confirm changes before deploy &lt;span class="o"&gt;[&lt;/span&gt;y/N]: y
        &lt;span class="c"&gt;#SAM needs permission to be able to create roles to connect to the resources in your template&lt;/span&gt;
        Allow SAM CLI IAM role creation &lt;span class="o"&gt;[&lt;/span&gt;Y/n]: y
        Save arguments to samconfig.toml &lt;span class="o"&gt;[&lt;/span&gt;Y/n]: y

        Looking &lt;span class="k"&gt;for &lt;/span&gt;resources needed &lt;span class="k"&gt;for &lt;/span&gt;deployment: Found!

                Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-1nw0fx8sanitw
                A different default S3 bucket can be &lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="k"&gt;in &lt;/span&gt;samconfig.toml

        Saved arguments to config file
        Running &lt;span class="s1"&gt;'sam deploy'&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;future deployments will use the parameters saved above.
        The above parameters can be changed by modifying samconfig.toml
        Learn more about samconfig.toml syntax at 
        https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html

        Deploying with following values
        &lt;span class="o"&gt;===============================&lt;/span&gt;
        Stack name                 : serverless-covid-19-pipeline
        Region                     : eu-west-2
        Confirm changeset          : True
        Deployment s3 bucket       : aws-sam-cli-managed-default-samclisourcebucket-1nw0fx8sanitw
        Capabilities               : &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"CAPABILITY_IAM"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
        Parameter overrides        : &lt;span class="o"&gt;{}&lt;/span&gt;

Initiating deployment
&lt;span class="o"&gt;=====================&lt;/span&gt;

Waiting &lt;span class="k"&gt;for &lt;/span&gt;changeset to be created..

CloudFormation stack changeset
&lt;span class="nt"&gt;------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
Operation                                                    LogicalResourceId                                            ResourceType                                               
&lt;span class="nt"&gt;------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
+ Add                                                        DataIngestionBucket                                          AWS::S3::Bucket                                            
&lt;span class="nt"&gt;------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;

Changeset created successfully. arn:aws:cloudformation:eu-west-2:983527076849:changeSet/samcli-deploy1586858125/37a6647c-ef63-4175-8b5c-dd28a8bd201c


Previewing CloudFormation changeset before deployment
&lt;span class="o"&gt;======================================================&lt;/span&gt;
Deploy this changeset? &lt;span class="o"&gt;[&lt;/span&gt;y/N]: y

2020-04-14 10:56:15 - Waiting &lt;span class="k"&gt;for &lt;/span&gt;stack create/update to &lt;span class="nb"&gt;complete

&lt;/span&gt;CloudFormation events from changeset
&lt;span class="nt"&gt;---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
ResourceStatus                               ResourceType                                 LogicalResourceId                            ResourceStatusReason                       
&lt;span class="nt"&gt;---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
CREATE_IN_PROGRESS                           AWS::S3::Bucket                              DataIngestionBucket                          -                                          
CREATE_IN_PROGRESS                           AWS::S3::Bucket                              DataIngestionBucket                          Resource creation Initiated                
CREATE_COMPLETE                              AWS::S3::Bucket                              DataIngestionBucket                          -                                          
CREATE_COMPLETE                              AWS::CloudFormation::Stack                   serverless-covid-19-pipeline                 -                                          
&lt;span class="nt"&gt;---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;

Successfully created/updated stack - serverless-covid-19-pipeline &lt;span class="k"&gt;in &lt;/span&gt;eu-west-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So now if we head over to the AWS Console and open the S3 Explorer we should see the newly created Bucket. Good job!&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating the Lambda configuration in the Cloudformation template
&lt;/h2&gt;

&lt;p&gt;We need to update the Cloudformation template with our Data Ingestion function configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;DataIngestionFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DataIngestionFunction&lt;/span&gt;
      &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data_ingestion_function/&lt;/span&gt;
      &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app.lambda_handler&lt;/span&gt;
      &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.7&lt;/span&gt;
      &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AmazonS3FullAccess&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWSLambdaBasicExecutionRole&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWSXrayWriteOnlyAccess&lt;/span&gt;
      &lt;span class="na"&gt;Tracing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Active&lt;/span&gt;
      &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;AWS_DESTINATION_BUCKET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;S3Bucket&lt;/span&gt;
          &lt;span class="na"&gt;DATA_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://raw.githubusercontent.com/tomwhite/covid-19-uk-data/master/data/covid-19-totals-uk.csv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have assigned some basic policies to the Lambda function and specified the Bucket name and URL of the Github data as environment variables.&lt;/p&gt;

&lt;p&gt;Now we should be able to build and test the function...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;sam build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke &lt;span class="s2"&gt;"DataIngestionFunction"&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; events/scheduled-event.json
Building resource &lt;span class="s1"&gt;'DataIngestionFunction'&lt;/span&gt;
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
&lt;span class="o"&gt;=========================&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Invoke Function: sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Deploy: sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;

Invoking app.lambda_handler &lt;span class="o"&gt;(&lt;/span&gt;python3.7&lt;span class="o"&gt;)&lt;/span&gt;

Fetching lambci/lambda:python3.7 Docker container image......
Mounting /Users/daniel.kay/dev/Serverless-Covid-19-Pipeline/Serverless-Covid-19-Pipeline/.aws-sam/build/DataIngestionFunction as /var/task:ro,delegated inside runtime container
&lt;span class="o"&gt;[&lt;/span&gt;INFO]  2020-04-14T13:28:13.880Z                Found credentials &lt;span class="k"&gt;in &lt;/span&gt;environment variables.

START RequestId: 46f1780b-7e04-124f-f233-6ce13fb27a11 Version: &lt;span class="nv"&gt;$LATEST&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;INFO]  2020-04-14T13:28:14.452Z        46f1780b-7e04-124f-f233-6ce13fb27a11    Requesting data from Github

&lt;span class="o"&gt;[&lt;/span&gt;INFO]  2020-04-14T13:28:14.946Z        46f1780b-7e04-124f-f233-6ce13fb27a11    Data downloaded successfully

&lt;span class="o"&gt;[&lt;/span&gt;INFO]  2020-04-14T13:28:15.758Z        46f1780b-7e04-124f-f233-6ce13fb27a11    Data uploaded to S3

END RequestId: 46f1780b-7e04-124f-f233-6ce13fb27a11
REPORT RequestId: 46f1780b-7e04-124f-f233-6ce13fb27a11  Init Duration: 1513.60 ms       Duration: 1310.72 ms    Billed Duration: 1400 ms        Memory Size: 128 MB     Max Memory Used: 43 MB

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;:&lt;span class="s2"&gt;"Lambda completed"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking good, we can see from the log lines that the data was downloaded and uploaded to S3. If we navigate to the S3 Explorer and open up the Bucket we should see the CSV file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2FS3-validate-upload.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2FS3-validate-upload.png" alt="Validate S3 Upload"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can now download the file and verify the contents. The file should contain data up until the previous day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring a Scheduled Events
&lt;/h2&gt;

&lt;p&gt;So our Data Ingestion Lambda works perfectly. It downloads the data and it uploads it to S3. But we don't want to invoke the function manually, we want it invoked at a particular time of the day. We can utilize a Cloudwatch Scheduled Event to invoke the function for us.&lt;/p&gt;

&lt;p&gt;We have to update our Cloudformation Template to include the event rule and permission for EventBridge to invoke the Lambda function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;DataIngestionScheduledRule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Events::Rule&lt;/span&gt;
    &lt;span class="s"&gt;Properties&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DataIngestionScheduledRule&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Triggers the Data Ingestion lambda once per day&lt;/span&gt;
      &lt;span class="na"&gt;ScheduleExpression&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cron(45 14 * * ? *)&lt;/span&gt;
      &lt;span class="na"&gt;State&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ENABLED&lt;/span&gt;
      &lt;span class="na"&gt;Targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt;
          &lt;span class="na"&gt;Arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;DataIngestionFunction.Arn&lt;/span&gt;
          &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DataIngestionScheduledRule&lt;/span&gt;

&lt;span class="na"&gt;DataIngestionInvokePermission&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Lambda::Permission&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;DataIngestionFunction&lt;/span&gt;
    &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lambda:InvokeFunction&lt;/span&gt;
    &lt;span class="na"&gt;Principal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;events.amazonaws.com&lt;/span&gt;
    &lt;span class="na"&gt;SourceArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;DataIngestionScheduledRule.Arn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are setting the scheduled expression to be a cron expression which will invoke the function daily at 1545 GMT due to daylight saving in the UK 😄. The target is the Data Ingestion function as this is the function we want to be invoked. &lt;/p&gt;

&lt;p&gt;Right, it's ready to be built and deployed...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;sam build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sam deploy
Building resource &lt;span class="s1"&gt;'DataIngestionFunction'&lt;/span&gt;
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
&lt;span class="o"&gt;=========================&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Invoke Function: sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; Deploy: sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;


        Deploying with following values
        &lt;span class="o"&gt;===============================&lt;/span&gt;
        Stack name                 : serverless-covid-19-pipeline
        Region                     : eu-west-2
        Confirm changeset          : True
        Deployment s3 bucket       : aws-sam-cli-managed-default-samclisourcebucket-1nw0fx8sanitw
        Capabilities               : &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"CAPABILITY_IAM"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
        Parameter overrides        : &lt;span class="o"&gt;{}&lt;/span&gt;

Initiating deployment
&lt;span class="o"&gt;=====================&lt;/span&gt;
Uploading to serverless-covid-19-pipeline/175b90da9d229e544ad442eeb0c9e280.template  1711 / 1711.0  &lt;span class="o"&gt;(&lt;/span&gt;100.00%&lt;span class="o"&gt;)&lt;/span&gt;

Waiting &lt;span class="k"&gt;for &lt;/span&gt;changeset to be created..

CloudFormation stack changeset
&lt;span class="nt"&gt;------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
Operation                                                    LogicalResourceId                                            ResourceType                                               
&lt;span class="nt"&gt;------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
+ Add                                                        DataIngestionInvokePermission                                AWS::Lambda::Permission                                    
+ Add                                                        DataIngestionScheduledRule                                   AWS::Events::Rule                                          
&lt;span class="k"&gt;*&lt;/span&gt; Modify                                                     DataIngestionFunctionRole                                    AWS::IAM::Role                                             
&lt;span class="k"&gt;*&lt;/span&gt; Modify                                                     DataIngestionFunction                                        AWS::Lambda::Function                                      
&lt;span class="nt"&gt;------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;

Changeset created successfully. arn:aws:cloudformation:eu-west-2:983527076849:changeSet/samcli-deploy1586873775/cd0112a5-5534-4d7e-95b7-a64503823b64


Previewing CloudFormation changeset before deployment
&lt;span class="o"&gt;======================================================&lt;/span&gt;
Deploy this changeset? &lt;span class="o"&gt;[&lt;/span&gt;y/N]: y

2020-04-14 15:16:25 - Waiting &lt;span class="k"&gt;for &lt;/span&gt;stack create/update to &lt;span class="nb"&gt;complete

&lt;/span&gt;CloudFormation events from changeset
&lt;span class="nt"&gt;---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
ResourceStatus                               ResourceType                                 LogicalResourceId                            ResourceStatusReason                       
&lt;span class="nt"&gt;---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
UPDATE_IN_PROGRESS                           AWS::Lambda::Function                        DataIngestionFunction                        Requested update requires the creation of  
                                                                                                                                       a new physical resource&lt;span class="p"&gt;;&lt;/span&gt; hence creating    
                                                                                                                                       one.                                       
UPDATE_COMPLETE                              AWS::Lambda::Function                        DataIngestionFunction                        -                                          
UPDATE_IN_PROGRESS                           AWS::Lambda::Function                        DataIngestionFunction                        Resource creation Initiated                
CREATE_IN_PROGRESS                           AWS::Events::Rule                            DataIngestionScheduledRule                   -                                          
CREATE_IN_PROGRESS                           AWS::Events::Rule                            DataIngestionScheduledRule                   Resource creation Initiated                
CREATE_COMPLETE                              AWS::Events::Rule                            DataIngestionScheduledRule                   -                                          
CREATE_IN_PROGRESS                           AWS::Lambda::Permission                      DataIngestionInvokePermission                Resource creation Initiated                
CREATE_IN_PROGRESS                           AWS::Lambda::Permission                      DataIngestionInvokePermission                -                                          
CREATE_COMPLETE                              AWS::Lambda::Permission                      DataIngestionInvokePermission                -                                          
UPDATE_COMPLETE_CLEANUP_IN_PROGRESS          AWS::CloudFormation::Stack                   serverless-covid-19-pipeline                 -                                          
UPDATE_COMPLETE                              AWS::CloudFormation::Stack                   serverless-covid-19-pipeline                 -                                          
DELETE_COMPLETE                              AWS::Lambda::Function                        DataIngestionFunction                        -                                          
DELETE_IN_PROGRESS                           AWS::Lambda::Function                        DataIngestionFunction                        -                                          
&lt;span class="nt"&gt;---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;

Successfully created/updated stack - serverless-covid-19-pipeline &lt;span class="k"&gt;in &lt;/span&gt;eu-west-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see from the above that the invoke permissions and the scheduled rule have been created successfully. To validate this, we can head into CloudWatch Console and check out the Rules within the Events section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2FScheduled-rule-verify.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2FScheduled-rule-verify.png" alt="Verify scheduled rule"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we navigate to the Lambda Console in AWS and open our Lambda from within the Functions section, we should see that the CloudWatch Event is now set up as a trigger for this Lambda.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2FLambda-trigger.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2FLambda-trigger.png" alt="Lambda trigger"&gt;&lt;/a&gt;&lt;br&gt;
Exciting times!&lt;/p&gt;

&lt;p&gt;So how do we know if this function was in fact triggered at 1545? &lt;/p&gt;

&lt;p&gt;We can do this in a few different ways: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can check the timestamp on the CSV file that was created when we were testing as this will have a new modified date and time&lt;/li&gt;
&lt;li&gt;If the file wasn't there to begin with there should be a CSV file in the bucket&lt;/li&gt;
&lt;li&gt;We can check the CloudWatch logs&lt;/li&gt;
&lt;li&gt;CloudWatch ServiceLens (my favorite)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we check the CloudWatch logs it will look very similar to the log lines we saw when we were testing earlier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2FCloudWatch-logs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2FCloudWatch-logs.png" alt="CloudWatch logs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see the data was downloaded and uploaded to S3 without any issues.&lt;/p&gt;

&lt;p&gt;CloudWatch ServiceLens provides us with complete observability of our applications and services by providing logs, metrics, tracing and alarms in one friendly dashboard. &lt;/p&gt;

&lt;p&gt;I'm planning on writing another post about ServiceLens at a later date.&lt;/p&gt;
&lt;h2&gt;
  
  
  Second Function - Data Processing
&lt;/h2&gt;

&lt;p&gt;We are now going to start working on the second Lambda function. This function will be responsible for listening to S3 events from the Bucket we created earlier, using the event to download the CSV file and insert the data for the most recent date into DynamoDB.&lt;/p&gt;

&lt;p&gt;As we've covered how we interact with the AWS SAM CLI, these steps won't be as heavily detailed as they were in previous sections&lt;/p&gt;

&lt;p&gt;First things first, we need a DynamoDB table. We can update our CloudFormation template to include a DynamoDB resource type with some basic attributes. &lt;/p&gt;

&lt;p&gt;Whilst we are doing this we can also add our Lambda configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;DynamoDBTable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;AWS::DynamoDB::Table&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;TableName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;danieljameskay-covid19-data&lt;/span&gt;
    &lt;span class="na"&gt;KeySchema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt;
        &lt;span class="na"&gt;AttributeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;country&lt;/span&gt;
        &lt;span class="na"&gt;KeyType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;HASH&lt;/span&gt;
    &lt;span class="na"&gt;AttributeDefinitions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt;
        &lt;span class="na"&gt;AttributeName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;country&lt;/span&gt;
        &lt;span class="na"&gt;AttributeType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;S&lt;/span&gt;
    &lt;span class="na"&gt;ProvisionedThroughput&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ReadCapacityUnits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;WriteCapacityUnits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;StreamSpecification&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;StreamViewType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;NEW_AND_OLD_IMAGES&lt;/span&gt;

&lt;span class="na"&gt;S3ProcessingFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;S3ProcessingFunction&lt;/span&gt;
    &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;s3_processing_function/&lt;/span&gt;
    &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app.lambda_handler&lt;/span&gt;
    &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.7&lt;/span&gt;
    &lt;span class="na"&gt;Tracing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Active&lt;/span&gt;
    &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;AWS_DOWNLOAD_BUCKET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;S3Bucket&lt;/span&gt;
        &lt;span class="na"&gt;DDB_TABLE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;danieljameskay-covid19-data&lt;/span&gt;
    &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWSLambdaBasicExecutionRole&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWSXrayWriteOnlyAccess&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AmazonDynamoDBFullAccess&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AmazonS3FullAccess&lt;/span&gt;
    &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;s3Notification&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;S3&lt;/span&gt;
        &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;DataIngestionBucket&lt;/span&gt;
          &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;s3:ObjectCreated:*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are specifying the key of our table is the &lt;code&gt;country&lt;/code&gt;. The type is &lt;code&gt;hash&lt;/code&gt; which is also known as the partition key.  We're also specifying a &lt;code&gt;StreamSpecification&lt;/code&gt; of type &lt;code&gt;NEW_AND_OLD_IMAGES&lt;/code&gt; which means that when the values for the country are updated, DynamoDB will stream the new value and old value to any listeners...in our case, our Notification Lambda.&lt;/p&gt;

&lt;p&gt;We should now be able to run &lt;code&gt;sam build &amp;amp;&amp;amp; sam deploy&lt;/code&gt; to deploy our DynamoDB Table.&lt;/p&gt;

&lt;p&gt;Now the code...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2Fs3-processing-lambda-new.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2Fs3-processing-lambda-new.png" alt="S3 processing app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the function is invoked it downloads the CSV file from Github and stores it in the tmp directory, processes it and inserts the most recent record into DynamoDB along with the date that the data corresponds to.&lt;/p&gt;

&lt;p&gt;There's probably a nicer way to write some of this logic but as I'm still learning Python...it does the job 👨‍💻&lt;/p&gt;

&lt;p&gt;As we did earlier we can test this Lambda by running &lt;code&gt;sam build "S3ProcessingFunction" &amp;amp;&amp;amp; sam local invoke "S3ProcessingFunction" -e events/s3-put-event.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We can verify the Lambda has worked correctly by verifying the log lines...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[INFO]  2020-04-16T10:25:10.372Z        bfcc35ee-2908-1a44-afe0-e02816837dbc    Downloading CSV file from S3
[INFO]  2020-04-16T10:25:10.663Z        bfcc35ee-2908-1a44-afe0-e02816837dbc    File downloaded to tmp directory
[INFO]  2020-04-16T10:25:10.669Z        bfcc35ee-2908-1a44-afe0-e02816837dbc    Inserting record into DynamoDB...
[INFO]  2020-04-16T10:25:10.937Z        bfcc35ee-2908-1a44-afe0-e02816837dbc    Record added to DynamoDB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and checking DynamoDB via the AWS CLI...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ aws dynamodb scan --table-name danieljameskay-covid19-data                                             
{
    "Count": 1, 
    "Items": [
        {
            "date": {
                "S": "04-13-2020"
            }, 
            "country": {
                "S": "UK"
            }, 
            "confirmed_cases": {
                "S": "88621"
            }, 
            "tests": {
                "S": "290720"
            }, 
            "deaths": {
                "S": "11329"
            }
        }
    ], 
    "ScannedCount": 1, 
    "ConsumedCapacity": null
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that our test has successfully run and the data has been inserted into DynamoDB. &lt;/p&gt;

&lt;p&gt;We should now be able to run &lt;code&gt;sam build &amp;amp;&amp;amp; sam deploy&lt;/code&gt; to deploy our new Lambda.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lets see if what we've done so far works 🤔
&lt;/h2&gt;

&lt;p&gt;So, we've tested our 2 Lambdas locally and 1 of them in AWS. We can wait until the time we set earlier to see if our Lambda's work or we can trigger the Data Ingestion Lambda from the AWS CLI...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ aws lambda invoke --function-name DataIngestionFunction out --log-type None
{
    "ExecutedVersion": "$LATEST", 
    "StatusCode": 200
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then we can check DynamoDB.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ aws dynamodb scan --table-name danieljameskay-covid19-data                 
{
    "Count": 1, 
    "Items": [
        {
            "date": {
                "S": "04-15-2020"
            }, 
            "country": {
                "S": "UK"
            }, 
            "confirmed_cases": {
                "S": "98476"
            }, 
            "tests": {
                "S": "313769"
            }, 
            "deaths": {
                "S": "12868"
            }
        }
    ], 
    "ScannedCount": 1, 
    "ConsumedCapacity": null
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking good! So we manually triggered the Data Ingestion Lambda which in turn triggered the S3 Processing Lambda using the S3 Event which added the new data to the Table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Third Function - Notification
&lt;/h2&gt;

&lt;p&gt;We have a Lambda which downloads the data and another which processes this data and saves it to DynamoDB. &lt;/p&gt;

&lt;p&gt;The last Lambda in this solution listens to a stream from our DynamoDB Table which contains the old data and the new data. Using this data it works out the percent difference between the two datasets and builds a message which is then used to be sent out using AWS SNS to an email address.&lt;/p&gt;

&lt;p&gt;Right, CloudFormation additions...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;DDBNotificationFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ddb_notification_function&lt;/span&gt;
    &lt;span class="na"&gt;FunctionName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DDBNotificationFunction'&lt;/span&gt;
    &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app.lambda_handler&lt;/span&gt;
    &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3.7&lt;/span&gt;
    &lt;span class="na"&gt;Tracing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Active&lt;/span&gt;
    &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;SNS_TOPIC&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;SNSTopic&lt;/span&gt;
    &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AmazonSNSFullAccess&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWSLambdaBasicExecutionRole&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWSXrayWriteOnlyAccess&lt;/span&gt;
    &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Stream&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DynamoDB&lt;/span&gt;
        &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Stream&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt;  &lt;span class="s"&gt;DynamoDBTable.StreamArn&lt;/span&gt;
          &lt;span class="na"&gt;StartingPosition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TRIM_HORIZON&lt;/span&gt;

&lt;span class="na"&gt;SNSTopic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::SNS::Topic&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;TopicName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ddb-stream-covid-19-daily-data"&lt;/span&gt;

&lt;span class="na"&gt;SNSSubscription&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::SNS::Subscription&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4i4nbyt@2go-mail.com&lt;/span&gt;
    &lt;span class="na"&gt;Protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;email&lt;/span&gt;
    &lt;span class="na"&gt;TopicArn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt;  &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SNSTopic'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we have our last Lambda configuration. The only thing to really point out here is we are specifying DynamoDB as the type of event source for this Lambda and the Stream ARN of our DynamoDB Table.&lt;/p&gt;

&lt;p&gt;Lastly, we are creating an SNS Topic and Subscription. Our Lambda will send the message to the SNS Topic and the Subscription will be responsible for sending it to our temporary email address.&lt;/p&gt;

&lt;p&gt;Code time people...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2Fddb-stream-notification-new.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2Fddb-stream-notification-new.png" alt="DDB stream notification"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are retrieving the old and new records from the event which triggered the Lambda function. Then we establish the percentage difference between the old attributes and new attributes and finally publishing a message with the percentage change to SNS. &lt;/p&gt;

&lt;p&gt;We should now be able to run &lt;code&gt;sam build &amp;amp;&amp;amp; sam deploy&lt;/code&gt; to deploy our new Lambda.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing from the Lambda Console
&lt;/h2&gt;

&lt;p&gt;In the previous sections, we tested our functions from the AWS CLI and AWS SAM CLI but we can invoke our function from Lambda Console.&lt;/p&gt;

&lt;p&gt;With the Notification Lambda open in the Lambda Console, we can click "Test" followed by "Configure Test Events". Using the "Hello World" template, we can just overwrite the JSON payload with a JSON payload generated by running &lt;code&gt;sam local generate-event dynamodb update&lt;/code&gt; and changing the values.&lt;/p&gt;

&lt;p&gt;Below is an example of the event that the DynamoDB Notification Lambda receives when the data is changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Records"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"eventID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"24df338c43fa112256a16d7d13c6eb14"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"eventName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MODIFY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"eventVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"eventSource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws:dynamodb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"awsRegion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eu-west-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"dynamodb"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"ApproximateCreationDateTime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1587043428&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Keys"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UK"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"NewImage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"04-13-2020"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UK"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"confirmed_cases"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"98621"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"tests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"291720"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"deaths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12329"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"OldImage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"04-12-2020"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UK"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"confirmed_cases"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"88621"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"tests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"290720"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"deaths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"S"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"11329"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"SequenceNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5571000000000004914106821"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"SizeBytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"StreamViewType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NEW_AND_OLD_IMAGES"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"eventSourceARN"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:dynamodb:eu-west-2:983527076849:table/danieljameskay-covid19-data/stream/2020-04-15T09:00:54.674"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then use this saved event and some fake data to trigger our Lambda.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2Flambda-success.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2Flambda-success.png" alt="Lambda success"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As our Lambda has successfully run we should have received an email. Let's check our inbox...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2Fsns-email.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2Fsns-email.png" alt="Sns email"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oh yeah 😎😎😎 &lt;/p&gt;

&lt;p&gt;And there we have it, the email with the percentage changes. It looks amazing, doesn't it?&lt;/p&gt;

&lt;h2&gt;
  
  
  End to end
&lt;/h2&gt;

&lt;p&gt;We haven't seen the solution work end to end as of yet, let's change that! &lt;/p&gt;

&lt;p&gt;I modified the time for the &lt;code&gt;DataIngestionScheduledRule&lt;/code&gt; to 10 minutes in the future and altered the DynamoDB record so it had the values for the 15th April so when the Data Ingestion Lambda ran it would pull down the data for the 16th April.&lt;/p&gt;

&lt;p&gt;So I deployed the changes, made a coffee and came back to see the email in my inbox with the difference between the two days.&lt;/p&gt;

&lt;p&gt;I also checked SerivceLens and everything had run smoothly with no errors :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2FServiceLens.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdanieljameskay%2Fimages%2Fmaster%2FServiceLens.png" alt="Service lens"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Working on this has been extremely rewarding. I know my way around AWS pretty well but, I've never used CloudFormation before and I'm still learning Python which added an extra challenge.&lt;/p&gt;

&lt;p&gt;We could have used LocalStack and local DynamoDB to aid with the testing locally so we wouldn't have to interact with AWS until we deployed.&lt;/p&gt;

&lt;p&gt;Things I would have liked to do and may do in the future:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Utilize AWS Amplify and build a React/GraphQL application that interacts with the full UK dataset as opposed to the most recent dates&lt;/li&gt;
&lt;li&gt;Use some more datasets for USA and Europe&lt;/li&gt;
&lt;li&gt;Use the X-Ray SDK to track AWS SDK calls &lt;/li&gt;
&lt;li&gt;Learn more about CloudFormation and tidy up the &lt;code&gt;template.yaml&lt;/code&gt; file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With everything that's been going on with COVID-19 building this solution and writing it up has kept me preoccupied in these uncertain times. Hopefully, there will be another post or two coming in the future.&lt;/p&gt;

&lt;p&gt;Big thanks to Tom White for the data used in this post. For anyone interested in his Github profile, it can be found &lt;a href="https://github.com/tomwhite" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Cover Photo by &lt;a href="https://unsplash.com/@dhaya_edd_art?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Dhaya Eddine Bentaleb&lt;/a&gt; on Unsplash.&lt;/p&gt;

&lt;p&gt;I hope everyone who has read this post has enjoyed it and if anyone has any questions drop me a comment or drop a tweet!&lt;/p&gt;

&lt;p&gt;Stay safe!&lt;/p&gt;

&lt;p&gt;Cheers&lt;/p&gt;

&lt;p&gt;DK&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>aws</category>
      <category>serverless</category>
      <category>python</category>
    </item>
  </channel>
</rss>
