<?xml version="1.0" encoding="utf-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom">
  <title></title>
  <subtitle></subtitle>
  <id>https://www.endpointdev.com/blog/tags/aws/</id>
  <link href="https://www.endpointdev.com/blog/tags/aws/"/>
  <link href="https://www.endpointdev.com/blog/tags/aws/" rel="self"/>
  <updated>2022-11-09T00:00:00+00:00</updated>
  <author>
    <name>End Point Dev</name>
  </author>
  
    <entry>
      <title>Introduction to Terraform with AWS</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/11/introduction-to-terraform-with-aws/"/>
      <id>https://www.endpointdev.com/blog/2022/11/introduction-to-terraform-with-aws/</id>
      <published>2022-11-09T00:00:00+00:00</published>
      <author>
        <name>Jeffry Johar</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/11/introduction-to-terraform-with-aws/portdickson.webp&#34; alt=&#34;Port Dickson, a Malaysian Beach. Rocks in the forground jut out into an inlet, across which is a line of red-roofed houses.&#34;&gt;&lt;br&gt;
Photo by Jeffry Johar&lt;/p&gt;
&lt;!-- https://www.pexels.com/photo/malaysia-rocky-beach-hotel-by-the-beach-avilion-13550224/ --&gt;
&lt;p&gt;Terraform is a tool from HashiCorp to enable infrastructure as code (IaC). With it users can define and manage IT infrastructure in source code form.&lt;/p&gt;
&lt;p&gt;Terraform is a declarative tool. It will ensure the desired state as defined by the user.&lt;/p&gt;
&lt;p&gt;Terraform comes with multiple plugins or providers which enable it to manage a wide variety of cloud providers and technologies such as but not limited to AWS, GCP, Azure, Kubernetes, Docker and others.&lt;/p&gt;
&lt;p&gt;This blog will go over how to use Terraform with AWS.&lt;/p&gt;
&lt;h3 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;For this tutorial we will need the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An active AWS account.&lt;/li&gt;
&lt;li&gt;An internet connection to download required files.&lt;/li&gt;
&lt;li&gt;A decent editor such as Vim or Notepad++ to edit the configuration files.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;install-aws-cli&#34;&gt;Install AWS CLI&lt;/h3&gt;
&lt;p&gt;We need to set up the AWS CLI (command-line interface) for authentication and authorization to AWS.&lt;/p&gt;
&lt;p&gt;Execute the following command to install the AWS CLI on macOS:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl -O https://awscli.amazonaws.com/AWSCLIV2.pkg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo installer -pkg AWSCLIV2.pkg -target /&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For other OSes see &lt;a href=&#34;https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html&#34;&gt;Amazon’s docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Execute the following command and enter the &lt;a href=&#34;https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html&#34;&gt;AWS Account and Access Keys&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ aws configure&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;install-terraform&#34;&gt;Install Terraform&lt;/h3&gt;
&lt;p&gt;We need to install Terraform. It is just a command line tool. Execute the following to install Terraform on macOS:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ brew tap hashicorp/tap
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ brew install hashicorp/tap/terraform&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For other OSes see &lt;a href=&#34;https://learn.hashicorp.com/tutorials/terraform/install-cli&#34;&gt;Terraform’s installation docs&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;create-the-terraform-configuration-file&#34;&gt;Create the Terraform configuration file&lt;/h3&gt;
&lt;p&gt;Before we can create any Terraform configuration file for a project, we need to create a directory where Terraform will pick up any configuration in the current directory and will store the state of the created infrastructure in a file.&lt;/p&gt;
&lt;p&gt;The name of the directory can be anything. For this tutorial we are going to name it &lt;code&gt;terraform-aws&lt;/code&gt;. Create the directory and &lt;code&gt;cd&lt;/code&gt; to it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ mkdir terraform-aws
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cd terraform-aws&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Create the following file and name it &lt;code&gt;main.tf&lt;/code&gt;. This is the main configuration file for our Terraform project. This configuration will provision an EC2 instance, install Amazon Linux 2 as the OS and install Nginx as the web server. The comments start with a hash &lt;code&gt;#&lt;/code&gt;. They describe each section&amp;rsquo;s function. For simplicity, the configuration is using the default VPC that comes with the selected AWS region.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# Set AWS as the cloud provider
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;terraform {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  required_providers {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    aws = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      source  = &amp;#34;hashicorp/aws&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      version = &amp;#34;~&amp;gt; 4.16&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  required_version = &amp;#34;&amp;gt;= 1.2.0&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# Set AWS region
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;provider &amp;#34;aws&amp;#34; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  region = &amp;#34;ap-southeast-1&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# Set the default VPC as the VPC
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;resource &amp;#34;aws_default_vpc&amp;#34; &amp;#34;main&amp;#34; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  tags = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Name = &amp;#34;Default VPC&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# Set AWS security group to allow SSH and HTTP
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;resource &amp;#34;aws_security_group&amp;#34; &amp;#34;ssh_http&amp;#34; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name        = &amp;#34;ssh_http&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  description = &amp;#34;Allow SSH and HTTP&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  vpc_id      = aws_default_vpc.main.id
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ingress {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    from_port   = 22
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    to_port     = 22
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    protocol    = &amp;#34;tcp&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cidr_blocks = [&amp;#34;0.0.0.0/0&amp;#34;] # make this your IP address or range
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ingress {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    from_port   = 80
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    to_port     = 80
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    protocol    = &amp;#34;tcp&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cidr_blocks = [&amp;#34;0.0.0.0/0&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  egress {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    from_port   = 0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    to_port     = 0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    protocol    = &amp;#34;-1&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cidr_blocks = [&amp;#34;0.0.0.0/0&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# AWS EC2 configuration
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# The user_data contains the script to install Nginx
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;resource &amp;#34;aws_instance&amp;#34; &amp;#34;app_server&amp;#34; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ami           = &amp;#34;ami-0b89f7b3f054b957e&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  instance_type = &amp;#34;t2.micro&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  key_name = &amp;#34;kaptenjeffry&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  vpc_security_group_ids = [aws_security_group.ssh_http.id]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  user_data = &amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   #!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   sudo yum update
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   sudo amazon-linux-extras install nginx1 -y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   sudo systemctl start nginx
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  EOF
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  tags = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Name = &amp;#34;Nginx by Terraform&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# EC2 Public IP
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;output &amp;#34;app_server_public_ip&amp;#34; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  description = &amp;#34;Public IP address of app_server&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  value       = aws_instance.app_server.public_ip
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;initialize-the-project&#34;&gt;Initialize the project&lt;/h3&gt;
&lt;p&gt;Initialize the project by downloading the required plugin. For this example, it will download the AWS plugin. Initialize the project by executing the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ terraform init&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;validate-the-configuration-file&#34;&gt;Validate the configuration file&lt;/h3&gt;
&lt;p&gt;Check the syntax of the configuration file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ terraform validate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;apply-the-configuration&#34;&gt;Apply the configuration&lt;/h3&gt;
&lt;p&gt;This will make Terraform create and provision the resources specified in the configuration file. It will ask to review the configuration; answer yes to proceed. Take note of the public IP of the provisioned EC2.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ terraform apply&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Sample output:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/11/introduction-to-terraform-with-aws/blog08-01.webp&#34; alt=&#34;Terraform Apply output. Highlighted is a line reading &amp;ldquo;Enter a value:&amp;rdquo;. &amp;ldquo;yes&amp;rdquo; has been entered as the answer. Also highlighted is a line under &amp;ldquo;Outputs:&amp;rdquo; reading &amp;ldquo;app_server_public_ip = &amp;ldquo;46.137.236.88&amp;rdquo;.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;access-the-provisioned-ec2-and-nginx&#34;&gt;Access the provisioned EC2 and Nginx&lt;/h3&gt;
&lt;p&gt;Use the &lt;code&gt;key_name&lt;/code&gt; that is configured in &lt;code&gt;main.tf&lt;/code&gt; and the generated public IP address to SSH to the EC2 Instance.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ssh -i kaptenjeffry.pem ec2-user@46.137.236.88&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Use the generated public IP address in a web browser to access the Nginx service. Please make sure to use &lt;code&gt;http&lt;/code&gt; protocol since the Nginx is running on port 80.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/11/introduction-to-terraform-with-aws/blog08-02.webp&#34; alt=&#34;The default Nginx page in a web browser. The top of the page reads &amp;ldquo;Welcome to nginx on Amazon Linux!&amp;rdquo;&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;That&amp;rsquo;s all, folks. This is the bare minimum Terraform configuration to quickly deploy an EC2 instance at AWS.&lt;/p&gt;
&lt;p&gt;For more cool stuffs you can visit the &lt;a href=&#34;https://registry.terraform.io/providers/hashicorp/aws/latest/docs&#34;&gt;Terraform main documentation for AWS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Have a nice day :)&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Kubernetes From The Ground Up With AWS EC2</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/10/kubernetes-from-the-ground-up-with-aws-ec2/"/>
      <id>https://www.endpointdev.com/blog/2022/10/kubernetes-from-the-ground-up-with-aws-ec2/</id>
      <published>2022-10-06T00:00:00+00:00</published>
      <author>
        <name>Jeffry Johar</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/10/kubernetes-from-the-ground-up-with-aws-ec2/ship.webp&#34; alt=&#34;A docked fishing ship faces the camera. A man stands on a dinghy next to it.&#34;&gt;&lt;br&gt;
Photo by Darry Lin &lt;!-- https://www.pexels.com/@darrylin/ --&gt;&lt;/p&gt;
&lt;p&gt;One way to learn Kubernetes infrastructure is to build it from scratch. This way of learning was introduced by the founding father of Kubernetes himself: &lt;a href=&#34;https://twitter.com/kelseyhightower&#34;&gt;Mr. Kelsey Hightower&lt;/a&gt;. The lesson is known as &lt;a href=&#34;https://github.com/kelseyhightower/kubernetes-the-hard-way&#34;&gt;“Kubernetes The Hard Way”&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For this blog entry I would like to take a less demanding approach than Kubernetes The Hard Way, while still being educational. I would like to highlight only the major steps in creating a Kubernetes cluster and what is covered in &lt;a href=&#34;https://training.linuxfoundation.org/certification/certified-kubernetes-administrator-cka/&#34;&gt;CKA (Certified Kubernetes Administrator) exams&lt;/a&gt;. Thus we are going to use the &lt;code&gt;kubeadm&lt;/code&gt; tools to build the Kubernetes cluster.&lt;/p&gt;
&lt;p&gt;The steps of creating a Kubernetes cluster are hidden to you if you are using a Kubernetes as a service such as AWS EKS, GCP GKE or the enterprise suites of Kubernetes such as Red Hat Openshift or VMware Tanzu. All of these products let you use Kubernetes without the need to worry about creating it.&lt;/p&gt;
&lt;h3 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;For this tutorial we will need the following from AWS:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An active AWS account&lt;/li&gt;
&lt;li&gt;EC2 instances with Amazon Linux 2 as the OS&lt;/li&gt;
&lt;li&gt;AWS Keys for SSH to access control node and managed nodes&lt;/li&gt;
&lt;li&gt;Security group which allows SSH and HTTP&lt;/li&gt;
&lt;li&gt;A decent editor such as Vim or Notepad++ to create the inventory and the playbook&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;ec2-instances-provisioning&#34;&gt;EC2 Instances provisioning&lt;/h3&gt;
&lt;p&gt;Provisioning of the the control plane, a.k.a. the master node:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to AWS Console → EC2 → Launch Instances.&lt;/li&gt;
&lt;li&gt;Set the Name tag to &lt;code&gt;Master&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Select the Amazon Linux 2 AMI.&lt;/li&gt;
&lt;li&gt;Select a key pair. If there are no available key pairs, please create one according to Amazon’s instructions.&lt;/li&gt;
&lt;li&gt;Allow SSH and 6443 TCP ports.&lt;/li&gt;
&lt;li&gt;Set Number of Instances to 1.&lt;/li&gt;
&lt;li&gt;Click Launch Instance.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Provisioning of the worker nodes, a.k.a. the minions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to AWS Console → EC2 → Launch Instances.&lt;/li&gt;
&lt;li&gt;Set the Name tag to &lt;code&gt;Node&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Select the Amazon Linux 2 AMI.&lt;/li&gt;
&lt;li&gt;Select a key pair. If there are no available key pairs, please create one according to Amazon’s instructions.&lt;/li&gt;
&lt;li&gt;Allow SSH TCP port.&lt;/li&gt;
&lt;li&gt;Set Number of Instances to 2.&lt;/li&gt;
&lt;li&gt;Click Launch Instance.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;installing-the-container-runtime&#34;&gt;Installing the container runtime&lt;/h3&gt;
&lt;p&gt;All Kubernetes nodes require some sort of container runtime engine. For these nodes we are going to use Docker. Log in to all EC2 instances and execute the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install Docker.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo yum update -y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo amazon-linux-extras install docker -y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo usermod -a -G docker ec2-user
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo service docker start
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo systemctl enable docker.service
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo su - ec2-user&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Verify the Docker installation.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker ps&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We should get an empty Docker status:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;CONTAINER ID   IMAGE          COMMAND                  CREATED       STATUS      PORTS                    NAMES&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install TC (Traffic Controller). This is required by the kubeadm tool.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo yum install tc -y&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;kubernetes-control-plane-setup&#34;&gt;Kubernetes control plane setup&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add the Kubernetes repository. Log in to the node and paste the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cat &amp;lt;&amp;lt;&amp;#39;EOF&amp;#39; | sudo tee /etc/yum.repos.d/kubernetes.repo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[kubernetes]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;name=Kubernetes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-$basearch
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;enabled=1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gpgcheck=1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;exclude=kubelet kubeadm kubectl
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the Kubernetes binaries for Control Plane (&lt;code&gt;kubelet&lt;/code&gt;, &lt;code&gt;kubeadm&lt;/code&gt;, &lt;code&gt;kubectl&lt;/code&gt;) and enable it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo systemctl enable --now kubelet&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Initiate the Control Plane. The &lt;code&gt;--ignore-preflight-errors&lt;/code&gt; switch is required because we are using a system which has fewer than 2 CPUs and less than 2 GB of RAM. The &lt;code&gt;--pod-network-cidr&lt;/code&gt; value is the default value for flannel (a networking add-on).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo kubeadm init --ignore-preflight-errors=NumCPU,Mem --pod-network-cidr=10.244.0.0/16&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There are 3 important points from the output of this command. They are the successful note on the cluster initalization, the kubeconfig setup and the worker node joining string. The following is a sample output:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/10/kubernetes-from-the-ground-up-with-aws-ec2/kubeadm01.webp&#34; alt=&#34;The output of kubeadm, with the three important points highlighted. They read: 1: &amp;ldquo;Your Kubernetes control-lane as been initialized successfully!&amp;rdquo;, 2: &amp;ldquo;mkdir -p $HOME/.kube;
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config;
sudo chown $(id -u):$(id -g) $HOME/.kube/config&amp;rdquo;, 3: &amp;ldquo;kubeadm join 172.XX.XX.XX:6643 &amp;ndash;token XXXX &amp;ndash;discover-token-ca-cert-hash XX&amp;rdquo;&#34;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the configuration file for kubectl a.k.a. kubeconfig to connect to the Kubernetes cluster. The scripts are from previous output:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir -p $HOME/.kube
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo chown $(id -u):$(id -g) $HOME/.kube/config&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the pod network add-on. We are going to use &lt;a href=&#34;https://github.com/flannel-io/flannel&#34;&gt;flannel&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;kubernetes-worker-nodes-setup&#34;&gt;Kubernetes worker nodes setup&lt;/h3&gt;
&lt;p&gt;Execute the following in all worker nodes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add the Kubernetes repository. Log in to the node and paste the following&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cat &amp;lt;&amp;lt;&amp;#39;EOF&amp;#39; | sudo tee /etc/yum.repos.d/kubernetes.repo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[kubernetes]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;name=Kubernetes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-$basearch
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;enabled=1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gpgcheck=1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;exclude=kubelet kubeadm
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the Kubernetes binaries for worker nodes (kubelet, kubeadm) and enable kubelet.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo yum install -y kubelet kubeadm --disableexcludes=kubernetes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo systemctl enable --now kubelet&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Execute the join command with &lt;code&gt;sudo&lt;/code&gt;. This command is from step #3 in the Kubernetes Control Plane Setup section.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/10/kubernetes-from-the-ground-up-with-aws-ec2/kubeadm-join.webp&#34; alt=&#34;A command and its results: sudo kubeadm join 172.XX.XX.XX:6443 &amp;ndash;token XXXX &amp;ndash;discovery-token-ca-cert-hash sha256:4XXX&#34;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;hello-kubernetes-&#34;&gt;Hello, Kubernetes :)&lt;/h3&gt;
&lt;p&gt;We have successfully created a Kubernetes cluster. Let’s check on the cluster and try to deploy some sample applications.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get the latest status of the nodes. You might need to wait a minute or more for all nodes to become &lt;code&gt;Ready&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl get nodes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Sample output:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/10/kubernetes-from-the-ground-up-with-aws-ec2/kubeadm02.webp&#34; alt=&#34;Results of the kubectl get nodes. 3 nodes appear, each with the Ready status.&#34;&gt;&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;Deploy a sample Nginx web server&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl create deployment mynginx --image=nginx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;Scale the Deployment to have 6 replicas and check on where the pods run. The pods should be assigned randomly to the available worker nodes.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl scale --replicas=6 deployment/mynginx
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl get pods -o wide&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Sample output:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/10/kubernetes-from-the-ground-up-with-aws-ec2/kubeadm03.webp&#34; alt=&#34;Results of the kubectl get nodes and kubectl get pods. The two worker nodes are highlighted, pointing to their coinciding output from the command &amp;ldquo;kubectl get pods -o wide&amp;rdquo;.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;That&amp;rsquo;s all, folks. I hope this blog entry has shed some insights on what it takes to create a Kubernetes cluster. Have a nice day :)&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Ansible tutorial with AWS EC2</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/08/ansible-tutorial-with-aws-ec2/"/>
      <id>https://www.endpointdev.com/blog/2022/08/ansible-tutorial-with-aws-ec2/</id>
      <published>2022-08-11T00:00:00+00:00</published>
      <author>
        <name>Jeffry Johar</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/08/ansible-tutorial-with-aws-ec2/wheel.webp&#34; alt=&#34;A ferris wheel lit up by red lights at night&#34;&gt;&lt;br&gt;
Photo by David Buchi&lt;/p&gt;
&lt;p&gt;Ansible is a tool to manage multiple remote systems from a single command center. In Ansible, the single command center is known as the control node and the remote systems to be managed are known as managed nodes. The following describes the 2 nodes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Control node:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The command center where Ansible is installed.&lt;/li&gt;
&lt;li&gt;Supported systems are Unix and Unix-like (Linux, BSD, macOS).&lt;/li&gt;
&lt;li&gt;Python and sshd are required.&lt;/li&gt;
&lt;li&gt;Remote systems to be managed are listed in a YAML or INI file called an inventory.&lt;/li&gt;
&lt;li&gt;Tasks to be executed are defined in a YAML file called a playbook.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Managed node:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The remote systems to be managed.&lt;/li&gt;
&lt;li&gt;Supported systems are Unix/Unix-like, Windows, and Appliances (eg: Cisco, NetApp).&lt;/li&gt;
&lt;li&gt;Python and sshd are required for Unix/Unix-like.&lt;/li&gt;
&lt;li&gt;PowerShell and WinRM are required for Windows.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In this tutorial we will use Ansible to manage multiple EC2 instances. For simplicity, we are going to provision EC2 instances in the AWS web console. Then we will configure one EC2 as the control node that will be managing multiple EC2 instances as managed nodes.&lt;/p&gt;
&lt;h3 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;For this tutorial we will need the following from AWS:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An active AWS account.&lt;/li&gt;
&lt;li&gt;EC2 instances with Amazon Linux 2 as the OS.&lt;/li&gt;
&lt;li&gt;AWS Keys for SSH to access control node and managed nodes.&lt;/li&gt;
&lt;li&gt;Security group which allows SSH and HTTP.&lt;/li&gt;
&lt;li&gt;A decent editor such as Vim or Notepad++ to create the inventory and the playbook.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;ec2-instances-provisioning&#34;&gt;EC2 Instances provisioning&lt;/h3&gt;
&lt;p&gt;The following are the steps to provision EC2 instances with the AWS web console.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to AWS Console → EC2 → Launch Instances.&lt;/li&gt;
&lt;li&gt;Select the Amazon Linux 2 AMI.&lt;/li&gt;
&lt;li&gt;Select a key pair. If there are no available key pairs, please create one according to &lt;a href=&#34;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html&#34;&gt;Amazon&amp;rsquo;s instructions&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Allow SSH and HTTP.&lt;/li&gt;
&lt;li&gt;Set Number of Instances to 4.&lt;/li&gt;
&lt;li&gt;Click Launch Instance.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/08/ansible-tutorial-with-aws-ec2/ansible03-ec2.webp&#34; alt=&#34;AWS web console, open to the &amp;ldquo;Instances&amp;rdquo; tab in the toolbar. This is circled and pointing to the table column starting with &amp;ldquo;Public IPv4&amp;hellip;&amp;rdquo;&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;ansible-nodes-and-ssh-keys&#34;&gt;Ansible nodes and SSH keys&lt;/h3&gt;
&lt;p&gt;In this section we will gather the IP addresses of EC2 instances and set up the SSH keys.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Go to AWS Console → EC2 → Launch Instances.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Get the Public IPv4 addresses.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We will choose the first EC2 to be the Ansible control node and the rest to be the managed nodes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;control node: 13.215.159.65&lt;/li&gt;
&lt;li&gt;managed nodes: 18.138.255.51, 13.229.198.36, 18.139.0.15&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/08/ansible-tutorial-with-aws-ec2/ansible-ng02.webp&#34; alt=&#34;AWS web console, again open to the Instances tab, with the Public IPv4 column circled. A green banner says that the EC2 instance was successfully started, followed by a long ID&#34;&gt;&lt;/p&gt;
&lt;p&gt;Login to the control node using our key pair. For me, it is &lt;code&gt;kaptenjeffry.pem&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ssh -i kaptenjeffry.pem ec2-user@13.215.159.65&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Open another terminal and copy the key pair to the control node&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;scp -i kaptenjeffry.pem kaptenjeffry.pem ec2-user@13.215.159.65:~/.ssh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Go back to the control node terminal. Try to log in from the control node to one of the managed nodes by using the key pair. This is to ensure the key pair is usable to access the managed nodes.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ssh -i .ssh/kaptenjeffry.pem ec2-user@18.138.255.51&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Register the rest of the managed nodes as known hosts to the control nodes, in bulk:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ssh-keyscan -t ecdsa-sha2-nistp256 13.229.198.36 18.139.0.15 &amp;gt;&amp;gt; .ssh/known_hosts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;ansible-installation-and-configuration&#34;&gt;Ansible Installation and Configuration&lt;/h3&gt;
&lt;p&gt;In this section we will install Ansible in the control node and create the inventory file.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;In the control node, execute the following commands to install Ansible:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo yum update
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo amazon-linux-extras install ansible2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ansible --version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;yum update&lt;/code&gt; updates all installed packages using the yum package manager,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;amazon-linux-extras install&lt;/code&gt; installs Ansible, and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ansible --version&lt;/code&gt; checks the installed version of Ansible.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a file named &lt;code&gt;myinventory.ini&lt;/code&gt;. Insert the IP addresses that we identified earlier to be the managed nodes in the following format:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;[mynginx]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;red ansible_host&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;18.138.255.51&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;green ansible_host&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;13.229.198.36&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;blue ansible_host&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;18.139.0.15&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[mynginx]&lt;/code&gt; is the group name of the managed nodes,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;red&lt;/code&gt;, &lt;code&gt;green&lt;/code&gt;, and &lt;code&gt;blue&lt;/code&gt; are the aliases of each managed node, and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ansible_host=x.x.x.x&lt;/code&gt; sets the IP Address each managed node.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;myinventory.ini&lt;/code&gt; is a basic inventory file in a INI format. An inventory file could be either in INI or YAML format. For more information on inventory see the &lt;a href=&#34;https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html&#34;&gt;Ansible docs&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;ansible-modules-and-ansible-ad-hoc-commands&#34;&gt;Ansible modules and Ansible ad hoc commands&lt;/h3&gt;
&lt;p&gt;Ansible modules are scripts to do a specific task at managed nodes. For example, there are modules to check availability, copy files, install applications, and lots more. To get the full list of modules, you can check the official &lt;a href=&#34;https://docs.ansible.com/ansible/2.9/modules/list_of_all_modules.html&#34;&gt;Ansible modules page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A quick way to use Ansible modules is with an ad hoc command. Ad hoc commands use the &lt;code&gt;ansible&lt;/code&gt; command-line interface to execute modules at the managed nodes. The usage is as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ansible &amp;lt;pattern&amp;gt; -m &amp;lt;module&amp;gt; -a &amp;#34;&amp;lt;module options&amp;gt;&amp;#34; -i &amp;lt;inventory&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;pattern&amp;gt;&lt;/code&gt; is the IP address, hostname, alias or group name,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-m module&lt;/code&gt; is name of the module to be used,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-a &amp;quot;&amp;lt;module options&amp;gt;&amp;quot;&lt;/code&gt; sets options for the module, and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-i &amp;lt;inventory&amp;gt;&lt;/code&gt; is the inventory of the managed nodes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;ad-hoc-command-examples&#34;&gt;Ad hoc command examples&lt;/h4&gt;
&lt;p&gt;The following are some example of Ansible ad hoc commands:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ping&lt;/code&gt; checks SSH connectivity and Python interpreter at the managed node. To use the &lt;code&gt;ping&lt;/code&gt; module against the &lt;code&gt;mynginx&lt;/code&gt; group of servers (all 3 hosts: red, green, and blue), run:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ansible mynginx -m ping -i myinventory.ini&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&#34;/blog/2022/08/ansible-tutorial-with-aws-ec2/ansible04-ping-all.webp&#34; alt=&#34;Sample output of ping. Several green blocks of JSON show successful ping responses&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;copy&lt;/code&gt; copies files to a managed node. To copy a text file (&lt;code&gt;/home/ec2-user/hello.txt&lt;/code&gt; in our test case) from the Control node to &lt;code&gt;/tmp/&lt;/code&gt; at all managed nodes in the &lt;code&gt;mynginx&lt;/code&gt; group, run:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ansible mynginx -m copy \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-a &amp;#39;src=/home/ec2-user/hello.txt dest=/tmp/hello.txt&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-i myinventory.ini&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;shell&lt;/code&gt; executes a shell script at a managed node. To use module shell to execute &lt;code&gt;uptime&lt;/code&gt; at all managed nodes in the &lt;code&gt;mynginx&lt;/code&gt; group, run:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ansible mynginx -m shell -a &amp;#39;uptime&amp;#39; -i myinventory.ini&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;ansible-playbooks&#34;&gt;Ansible playbooks&lt;/h3&gt;
&lt;p&gt;Ansible playbooks are configuration files in a YAML format that tell Ansible what to do. A playbook executes its assigned tasks sequentially from top to bottom. Tasks in playbooks are grouped by a block of instructions called a play. The following diagram shows the high level structure of a playbook:&lt;/p&gt;
&lt;p align=&#34;center&#34;&gt;
  &lt;img alt=&#34;An outer box labeled &#39;Playbook&#39; contains two smaller boxes. The first is labeled &#39;Play 1&#39;, the second is labeled &#39;Play 2&#39;. They contain stacked boxes similar to each other. The first box is a lighter color than the others, labeled &#39;Hosts 1 (or Hosts 2, for the &#39;Play 2&#39; box)&#39;. The others are labeled &#39;Task 1&#39;, &#39;Task 2&#39;, and after an ellipsis &#39;Task N&#39;.&#34; src=&#34;/blog/2022/08/ansible-tutorial-with-aws-ec2/high-level-playbook.webp&#34; width=&#34;400&#34; /&gt;
&lt;/p&gt;
&lt;p&gt;Now we are going to use a playbook to install Nginx at our three managed nodes as depicted in the following diagram:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/08/ansible-tutorial-with-aws-ec2/ansible01.webp&#34; alt=&#34;At the left, a box representing a control node, with the Ansible logo inside. Pointing to the Ansible logo inside the control node box are flags reading &amp;ldquo;playbook&amp;rdquo; and &amp;ldquo;inventory&amp;rdquo;. The control node box points to three identical &amp;ldquo;managed node&amp;rdquo; boxes, each with the Nginx logo inside.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Create the following YAML file and name it &lt;code&gt;nginx-playbook.yaml&lt;/code&gt;. This is a playbook with one play that will install and configure Nginx service at the managed node.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;---&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;- &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;Installing and Managing Nginx Server &lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;hosts&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;mynginx   &lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;become&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;True&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;vars&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;nginx_version&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;nginx_html&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;/usr/share/nginx/html&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;user_home&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;/home/ec2-user&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;index_html&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;index.html&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;tasks&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;- &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;Install the latest version of nginx&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;command&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;amazon-linux-extras install nginx{{ nginx_version }}=latest -y&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;- &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;Start nginx service&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;service&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;nginx&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;state&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;started&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;- &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;Enable nginx service&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;service&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;         &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;nginx&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;         &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;enabled&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;yes&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;- &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;name&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;Copy index.html to managed nodes&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;copy&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;src&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;{{ user_home }}/{{ index_html }}&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;dest&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;{{ nginx_html }}&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt; (top most) is the name of this play,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hosts&lt;/code&gt; specifies the managed nodes for this play,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;become&lt;/code&gt; says whether to use superuser privilege (sudo for Linux),&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vars&lt;/code&gt; defines variables for this play,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tasks&lt;/code&gt; is the start of the task section,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt; (under task section) specifies the name of each task, and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt; (in a &lt;code&gt;service&lt;/code&gt; section) specifies the name of a module.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s try to execute this playbook. Firstly we need to create the source &lt;code&gt;index.html&lt;/code&gt; to be copied to managed nodes.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;echo &amp;#39;Hello World!&amp;#39; &amp;gt; index.html&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Execute &lt;code&gt;ansible-playbook&lt;/code&gt; against our playbook. Just like the ad hoc command, we need to specify the inventory with the &lt;code&gt;-i&lt;/code&gt; switch.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ansible-playbook nginx-playbook.yaml -i myinventory.ini&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&#34;/blog/2022/08/ansible-tutorial-with-aws-ec2/ansible-playbook-run.webp&#34; alt=&#34;A shell with the results of the ansible-playbook command above&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now we can &lt;code&gt;curl&lt;/code&gt; our managed nodes to check on the Nginx service and the custom &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl 18.138.255.51
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl 13.229.198.36
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl 18.139.0.15&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p align=&#34;center&#34;&gt;
&lt;img alt=&#34;The output of each curl command above, with the responses being identical: &#39;Hello World!&#39;&#34; src=&#34;/blog/2022/08/ansible-tutorial-with-aws-ec2/ansible-ng10.webp&#34; width=&#34;600&#34; /&gt;
&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;That’s all, folks. We have successfully managed EC2 instances with Ansible. This tutorial covered the fundamentals of Ansible to start managing remote servers.&lt;/p&gt;
&lt;p&gt;Ansible rises above its competitors due to its simplicity of its installation, configuration, and usage. To get further information about Ansible you may visit its &lt;a href=&#34;https://docs.ansible.com/ansible/latest/getting_started/index.html&#34;&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>How to deploy a Django App with Aurora Serverless and AWS Copilot</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/06/how-to-deploy-django-app-with-aurora-serverless-and-copilot/"/>
      <id>https://www.endpointdev.com/blog/2022/06/how-to-deploy-django-app-with-aurora-serverless-and-copilot/</id>
      <published>2022-06-26T00:00:00+00:00</published>
      <author>
        <name>Jeffry Johar</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/06/how-to-deploy-django-app-with-aurora-serverless-and-copilot/aurora-banner.webp&#34; alt=&#34;Photo of an aurora&#34;&gt;&lt;br&gt;
Photo by Виктор Куликов&lt;/p&gt;
&lt;!-- Photo licensed under Legal Simplicity (public domain) from https://www.pexels.com/photo/white-tent-on-green-grass-field-under-aurora-borealis-during-night-time-8601966/ --&gt;
&lt;p&gt;AWS Copilot has the capability to provision an external database for its containerized work load. The database options are DynamoDB (NoSQL), Aurora Serverless (SQL), and S3 Buckets. For this blog we are going to provision and use Aurora Serverless with a containerized Django app. Aurora Serverless comes with 2 options for its engine: MySQL or PostgreSQL.&lt;/p&gt;
&lt;p&gt;Watch &lt;a href=&#34;https://www.youtube.com/watch?v=FzxqIdIZ9wc&#34;&gt;Amazon&amp;rsquo;s 2-minute introduction video&lt;/a&gt; to get the basic idea of Aurora Serverless.&lt;/p&gt;
&lt;p&gt;We are going to work with the same Django application from &lt;a href=&#34;/blog/2022/06/how-to-deploy-containerized-django-app-with-aws-copilot/&#34;&gt;my last article on AWS Copilot&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In my last article, the Django application was deployed with SQLite as the database. The application&amp;rsquo;s data is stored in SQLite which resides internally inside the container. The problem with this setup is the data is not persistent. Whenever we redeploy the application, the container will get a new filesystem. Thus all old data will be removed automatically.&lt;/p&gt;
&lt;p&gt;Now we are moving away the application&amp;rsquo;s data externally so that the life of the data does not depend on the container. We are going to put the data on the Aurora Serverless with PostgreSQL as the engine.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/how-to-deploy-django-app-with-aurora-serverless-and-copilot/django-sqlite.webp&#34; alt=&#34;Diagram of Django app with SQLite database&#34;&gt;&lt;/p&gt;
&lt;p style=&#34;text-align: center; font-weight: bold&#34;&gt;Django with SQLite as the internal database&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/how-to-deploy-django-app-with-aurora-serverless-and-copilot/django-aurora.webp&#34; alt=&#34;Diagram of Django app with AWS Aurora database&#34;&gt;&lt;/p&gt;
&lt;p style=&#34;text-align: center; font-weight: bold&#34;&gt;Django with Aurora Serverless as the external database&lt;/p&gt;
&lt;br&gt;
&lt;h3 id=&#34;the-prerequisites&#34;&gt;The Prerequisites&lt;/h3&gt;
&lt;p&gt;Docker, AWS CLI, and AWS Copilot CLI are required. Please refer to &lt;a href=&#34;/blog/2022/06/how-to-deploy-containerized-django-app-with-aws-copilot/&#34;&gt;my last article&lt;/a&gt; for how to install them.&lt;/p&gt;
&lt;h3 id=&#34;the-django-project&#34;&gt;The Django Project&lt;/h3&gt;
&lt;p&gt;Create a Django project by using a Python Docker Image. You can clone my Git project to get the Dockerfile, docker-compose.yaml and requirements.txt that I&amp;rsquo;m using:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ git clone https://github.com/aburayyanjeffry/django-copilot.git django-aurora&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Go to the &lt;code&gt;django-aurora&lt;/code&gt; directory and execute &lt;code&gt;docker-compose&lt;/code&gt; to create a Django project named &amp;ldquo;mydjango&amp;rdquo;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cd django-aurora
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ docker-compose run web django-admin startproject mydjango&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;the-deployment-with-aws-copilot&#34;&gt;The Deployment with AWS Copilot&lt;/h3&gt;
&lt;p&gt;Execute the following command to create a AWS Copilot application with the name of &amp;ldquo;mydjango&amp;rdquo;, a load balancer container with the service name &amp;ldquo;django-web&amp;rdquo; which is made from the Dockerfile in the current directory.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ copilot init \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-a mydjango \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-t &amp;#34;Load Balanced Web Service&amp;#34; -n django-web \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-d ./Dockerfile&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Answer N to the following question. We want to defer the deployment until we have set up the database.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;All right, you&amp;#39;re all set for local development.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; Would you like to deploy a test environment? [? for help] (y/N) N&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We need to create an environment for our application. Execute the following to create an environment named &lt;code&gt;test&lt;/code&gt; for the &amp;ldquo;mydjango&amp;rdquo; application with the default configuration.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ copilot env init \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;--name test \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;--profile default \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;--app mydjango \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;--default-config&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we are going to generate a config for our Aurora Serverless database. Basically this is the CloudFormation template that will be used for Aurora Serverless.&lt;/p&gt;
&lt;p&gt;Execute the following to generate the configuration for an Aurora cluster named &amp;ldquo;mydjango-db&amp;rdquo; that we will use for the &amp;ldquo;django-web&amp;rdquo; application. The Aurora cluster will be using the PostgreSQL engine and the database name will be &amp;ldquo;mydb&amp;rdquo;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ copilot storage init \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-n mydjango-db \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-t Aurora -w \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;django-web \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;--engine PostgreSQL \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;--initial-db mydb&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Take note of the injected environment variable name. This is where the database info and credentials are stored, and we will use this variable in later steps.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;✔ Wrote CloudFormation template at copilot/django-web/addons/mydjango-db.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Recommended follow-up actions:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; - Update django-web&amp;#39;s code to leverage the injected environment variable MYDJANGODB_SECRET.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Edit mydjango/settings.py to include the following. We will pass the injected environment variable we got previously to the function for getting the DBINFO variables.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;pathlib&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; Path
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;os&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ALLOWED_HOSTS = [&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;*&amp;#39;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DBINFO = json.loads(os.environ.get(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;MYDJANGODB_SECRET&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DATABASES = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;default&amp;#39;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;ENGINE&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;django.db.backends.postgresql&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;HOST&amp;#39;&lt;/span&gt;: DBINFO[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;host&amp;#39;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;PORT&amp;#39;&lt;/span&gt;: DBINFO[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;port&amp;#39;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;: DBINFO[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;dbname&amp;#39;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;USER&amp;#39;&lt;/span&gt;: DBINFO[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;username&amp;#39;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;PASSWORD&amp;#39;&lt;/span&gt;: DBINFO[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;password&amp;#39;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Deploy the application:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ copilot deploy --name django-web&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Open the terminal of the service:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ copilot svc exec&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Execute the following commands to migrate the initial database and to create a superuser account:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ python manage.py migrate
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ python manage.py createsuperuser&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Execute the following command to check on the environment variable. Take note of the &lt;code&gt;MYDJANGODB_SECRET&lt;/code&gt; variable. It is the variable that holds the database information.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ env | grep MYDJANGODB_SECRET&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;how-to-query-aurora-serverless&#34;&gt;How to query Aurora Serverless&lt;/h3&gt;
&lt;p&gt;We can use the &lt;a href=&#34;https://console.aws.amazon.com/rds/home&#34;&gt;Query Editor at AWS Console&lt;/a&gt; for RDS to query Aurora Serverless.&lt;/p&gt;
&lt;p&gt;Click the DB base on the DB identifier from the injected environment variable and click Modify.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/how-to-deploy-django-app-with-aurora-serverless-and-copilot/rds-01-modify.webp&#34; alt=&#34;Screenshot of Amazon RDS main control panel&#34;&gt;&lt;/p&gt;
&lt;p&gt;Click the check box for Data API.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/how-to-deploy-django-app-with-aurora-serverless-and-copilot/rds-02-api.webp&#34; alt=&#34;Screenshot of Amazon RDS Web Service Data API checkbox&#34;&gt;&lt;/p&gt;
&lt;p&gt;Select Apply Immediately.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/how-to-deploy-django-app-with-aurora-serverless-and-copilot/rds-03-immediately.webp&#34; alt=&#34;Screenshot of Amazon RDS Apply Immediately option&#34;&gt;&lt;/p&gt;
&lt;p&gt;Click Query Editor and fill in the Database information from the injected environment variable.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/how-to-deploy-django-app-with-aurora-serverless-and-copilot/rds-04-dbinfo.webp&#34; alt=&#34;Screenshot showing environment variable data extracted into the AWS RDS connection setup panel&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now you may use the Query Editor to query the database. Execute the following query to list all tables in the database:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/how-to-deploy-django-app-with-aurora-serverless-and-copilot/rds-05-query.webp&#34; alt=&#34;Screenshot of Amazon RDS Query Editor and results&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;the-end&#34;&gt;The End&lt;/h3&gt;
&lt;p&gt;That&amp;rsquo;s all, folks. We have deployed a containerized Django application and an Aurora Serverless with AWS Copilot. For further info on AWS Copilot &lt;a href=&#34;https://aws.github.io/copilot-cli/&#34;&gt;visit its website&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>How to deploy a containerized Django app with AWS Copilot</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/06/how-to-deploy-containerized-django-app-with-aws-copilot/"/>
      <id>https://www.endpointdev.com/blog/2022/06/how-to-deploy-containerized-django-app-with-aws-copilot/</id>
      <published>2022-06-21T00:00:00+00:00</published>
      <author>
        <name>Jeffry Johar</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/06/how-to-deploy-containerized-django-app-with-aws-copilot/pilots.webp&#34; alt=&#34;Photo of 2 pilots in an airplane cockpit&#34;&gt;&lt;/p&gt;
&lt;!-- Photo licensed under CC0 (public domain) from https://pxhere.com/en/photo/609377 --&gt;
&lt;p&gt;Generally there are 2 major options at AWS when it comes to deployment of containerized applications. You can either go for EKS or ECS.&lt;/p&gt;
&lt;p&gt;EKS (Elastic Kubernetes Service) is the managed Kubernetes service by AWS. ECS (Elastic Container Service), on the other hand, is AWS&amp;rsquo;s own way to manage your containerized application. You can learn more about EKS and ECS &lt;a href=&#34;https://aws.amazon.com/blogs/containers/amazon-ecs-vs-amazon-eks-making-sense-of-aws-container-services/&#34;&gt;on the AWS website&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For this post we will use ECS.&lt;/p&gt;
&lt;h3 id=&#34;the-chosen-one-and-the-sidekick&#34;&gt;The chosen one and the sidekick&lt;/h3&gt;
&lt;p&gt;With ECS chosen, now you have to find a preferably easy way to deploy your containerized application on it.&lt;/p&gt;
&lt;p&gt;There are quite a number of resources from AWS that are needed for your application to live on ECS, such as VPC (Virtual Private Cloud), Security Group (firewall), EC2 (virtual machine), Load Balancer, and others. Creating these resources manually is cumbersome so AWS has came out with a tool that can automate the creation of all of them. The tool is known as AWS Copilot and we are going to learn how to use it.&lt;/p&gt;
&lt;h3 id=&#34;install-docker&#34;&gt;Install Docker&lt;/h3&gt;
&lt;p&gt;Docker or Docker Desktop is required for building the Docker image later. Please refer to my previous article on &lt;a href=&#34;/blog/2022/06/getting-started-with-docker-and-kubernetes-on-macos/&#34;&gt;how to install Docker Desktop on macOS&lt;/a&gt;, or &lt;a href=&#34;https://docs.docker.com/get-docker/&#34;&gt;follow Docker&amp;rsquo;s instructions&lt;/a&gt; for Linux and Windows.&lt;/p&gt;
&lt;h3 id=&#34;set-up-aws-cli&#34;&gt;Set up AWS CLI&lt;/h3&gt;
&lt;p&gt;We need to set up the Docker AWS CLI (command-line interface) for authentication and authorization to AWS.&lt;/p&gt;
&lt;p&gt;Execute the following command to install the AWS CLI on macOS:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl -O &amp;#34;https://awscli.amazonaws.com/AWSCLIV2.pkg&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo installer -pkg AWSCLIV2.pkg -target /&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For other OSes see &lt;a href=&#34;https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html&#34;&gt;Amazon&amp;rsquo;s docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Execute the following command and enter the &lt;a href=&#34;https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html&#34;&gt;AWS Account and Access Keys&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ aws configure&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;install-aws-copilot-cli&#34;&gt;Install AWS Copilot CLI&lt;/h3&gt;
&lt;p&gt;Now it&amp;rsquo;s time for the main character: AWS Copilot.&lt;/p&gt;
&lt;p&gt;Install AWS Copilot with Homebrew for macOS:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ brew install aws/tap/copilot-cli&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;See &lt;a href=&#34;https://aws.github.io/copilot-cli/docs/getting-started/install/&#34;&gt;AWS Copilot Installation&lt;/a&gt; for other platforms.&lt;/p&gt;
&lt;h3 id=&#34;the-django-project&#34;&gt;The Django project&lt;/h3&gt;
&lt;p&gt;Create a Django project by using a Python Docker Image. You can clone my Git project to get the &lt;code&gt;Dockerfile&lt;/code&gt;, &lt;code&gt;docker-compose.yaml&lt;/code&gt; and &lt;code&gt;requirements.txt&lt;/code&gt; that I&amp;rsquo;m using.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ git clone https://github.com/aburayyanjeffry/django-copilot.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Go to the &lt;code&gt;django-pilot&lt;/code&gt; directory and execute &lt;code&gt;docker-compose&lt;/code&gt; to create a Django project named &amp;ldquo;mydjango&amp;rdquo;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cd django-copilot
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ docker-compose run web django-admin startproject mydjango .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Edit &lt;code&gt;mydjango/settings.py&lt;/code&gt; to allow all hostnames for its URL. This is required because by default AWS will generate a random URL for the application. Find the following variable and set the value as follows:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ALLOWED_HOSTS = [&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;*&amp;#39;&lt;/span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;the-deployment-with-aws-copilot&#34;&gt;The Deployment with AWS Copilot&lt;/h3&gt;
&lt;p&gt;Create an AWS Copilot &amp;ldquo;Application&amp;rdquo;. This is a grouping of services such as web app or database, environments (development, QA, production), and CI/CD pipelines. Execute the following command to create an Application with the name of &amp;ldquo;mydjango&amp;rdquo;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ copilot init -a mydjango&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Select the Workload type. Since this Django is an internet-facing app we will choose the &amp;ldquo;Load Balanced Web Service&amp;rdquo;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Which workload type best represents your architecture?  [Use arrows to move, type to filter, ? for more help]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Request-Driven Web Service  (App Runner)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;gt; Load Balanced Web Service   (Internet to ECS on Fargate)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Backend Service             (ECS on Fargate)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Worker Service              (Events to SQS to ECS on Fargate)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Scheduled Job               (Scheduled event to State Machine to Fargate)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Give the Workload a name. We are going to name it &amp;ldquo;mydjango-web&amp;rdquo;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Workload type: Load Balanced Web Service
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  What do you want to name this service? [? for help] mydjango-web&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Select the Dockerfile in the current directory.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Which Dockerfile would you like to use for mydjango-web?  [Use arrows to move, type to filter, ? for more help]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;gt; ./Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Enter custom path for your Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Use an existing image instead&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Accept to create a test environment.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;All right, you&amp;#39;re all set for local development.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Would you like to deploy a test environment? [? for help] (y/N) y&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Wait and see. At the end of the deployment you will get the URL of your application. Open it in a browser.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/how-to-deploy-containerized-django-app-with-aws-copilot/sample.webp&#34; alt=&#34;Sample output of AWS copilot init run&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/how-to-deploy-containerized-django-app-with-aws-copilot/browser.webp&#34; alt=&#34;Sample view from a browser of a Django app default debug page stating &amp;ldquo;The install worked successfully! Congratulations!&amp;rdquo;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now let&amp;rsquo;s migrate some data, create a superuser, and try to log in. The Django app comes with a SQLite database. Execute the following command to get a terminal for the Django app:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ copilot svc exec&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once in the terminal, execute the following to migrate the initial data and to create the superuser:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ python manage.py migrate
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ python manage.py createsuperuser&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&#34;/blog/2022/06/how-to-deploy-containerized-django-app-with-aws-copilot/sample-db.webp&#34; alt=&#34;Output from Django database migration run&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now you may access the admin page and login by using the created credentials.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/how-to-deploy-containerized-django-app-with-aws-copilot/login01.webp&#34; alt=&#34;Django login page screenshot&#34;&gt;&lt;/p&gt;
&lt;p&gt;You should see the Django admin:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/06/how-to-deploy-containerized-django-app-with-aws-copilot/login02.webp&#34; alt=&#34;Django admin page screenshot after successful login&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;a-mini-cheat-sheet&#34;&gt;A mini cheat sheet&lt;/h3&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;AWS Copilot commands&lt;/th&gt;
          &lt;th&gt;Remarks&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;copilot app ls&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;To list available Applications&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;copilot app show -n appname&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;To get the details of an Application&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;copilot app delete -n appname&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;To delete an Application&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;copilot svc ls&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;To list available Services&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;copilot svc show -n svcname&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;To get the details of a Service&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;copilot svc delete -n svcname&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;To delete a Service&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;the-end&#34;&gt;The End&lt;/h3&gt;
&lt;p&gt;That&amp;rsquo;s all, folks.&lt;/p&gt;
&lt;p&gt;AWS Copilot is a tool to automate the deployment of AWS infrastructure for our containerized application needs. It takes away most of the worries about infrastructure and enables us to focus sooner on the application development.&lt;/p&gt;
&lt;p&gt;For further info on AWS Copilot &lt;a href=&#34;https://aws.github.io/copilot-cli/&#34;&gt;visit its website&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Generate PDF with Chrome, Puppeteer, and Serverless Stack</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/01/generate-pdf-serverless-stack-chrome-puppeteer/"/>
      <id>https://www.endpointdev.com/blog/2022/01/generate-pdf-serverless-stack-chrome-puppeteer/</id>
      <published>2022-01-02T00:00:00+00:00</published>
      <author>
        <name>Afif Sohaili</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/01/generate-pdf-serverless-stack-chrome-puppeteer/banner.jpg&#34; alt=&#34;Cloud Computing and Serverless&#34;&gt;&lt;/p&gt;
&lt;!-- Image from Pixabay, Pixabay license: https://pixabay.com/illustrations/cloud-network-website-computer-6515064/ --&gt;
&lt;p&gt;Function as a Service (FaaS) solutions are becoming more and more mainstream today. The FaaS model allows developers to not have to worry about managing infrastructure and instead focus on writing the application logic. In the FaaS model, developers write individual functions that run specific tasks that are deployed together on a FaaS platform, including but not limited to Amazon Web Services (AWS), Azure, and Google Cloud Platform (GCP).&lt;/p&gt;
&lt;p&gt;These functions don&amp;rsquo;t always run like a traditional application server does, waiting for a request. Instead, these FaaS platforms only spin up instances of these functions whenever there is traffic and will shut them back down once there are no more requests after a given period of time. This helps make FaaS a really cheap platform while traffic is low. It is a good approach for on-demand tasks that are part of the application but not necessarily the most common path in a customer&amp;rsquo;s everyday journey within the application.&lt;/p&gt;
&lt;h3 id=&#34;the-tools&#34;&gt;The tools&lt;/h3&gt;
&lt;p&gt;In this tutorial, we&amp;rsquo;re going to be looking at implementing PDF download with Chrome, Puppeteer, and Serverless Stack, but first, let&amp;rsquo;s have a brief introduction of the tools that we are going to be using.&lt;/p&gt;
&lt;h4 id=&#34;1-serverless-stack&#34;&gt;1. Serverless Stack&lt;/h4&gt;
&lt;p&gt;Serverless Stack is a framework for building full-stack serverless apps. The bigger player in this space is the Serverless framework. Serverless Stack is giving the Serverless framework (yes, I know, it can be very confusing at times) a challenge to the throne. The latter has been around for many years now and has been the authority for building serverless apps. There are pros and cons to both of them.&lt;/p&gt;
&lt;p&gt;Serverless Stack&amp;rsquo;s biggest advantage is the live Lambda debugging, a faster development process and overall better development experience. With the Serverless framework, developers have to either constantly deploy to the cloud to test any change, or spend some time setting up &lt;code&gt;serverless-offline&lt;/code&gt; for simulated Lambda and API gateway environments on their local machines.&lt;/p&gt;
&lt;p&gt;But with the Serverless framework, developers are not tied to using a specific platform, as it can be used to deploy functions on AWS, Azure, and Google Cloud Platform. Serverless Stack, on the other hand, only allows deploying to AWS since its internals are built on top of AWS CDK.&lt;/p&gt;
&lt;h4 id=&#34;2-chrome-and-puppeteer&#34;&gt;2. Chrome and Puppeteer&lt;/h4&gt;
&lt;p&gt;Chrome will be used in this tutorial as a headless browser, controlled using Puppeteer. Puppeteer is a Node.js package that provides an API to make Chrome or Chromium navigate to a specific URL, take screenshots, click buttons, type in input fields, as well as take PDF screenshots. Note that the PDF screenshot feature is specific to Chrome and not Chromium, so make sure to double check that you are using the right browser.&lt;/p&gt;
&lt;h3 id=&#34;implementing-pdf-screenshot-and-download-with-serverless-stack-chrome-and-puppeteer&#34;&gt;Implementing PDF screenshot and download with Serverless Stack, Chrome, and Puppeteer&lt;/h3&gt;
&lt;p&gt;Now, let&amp;rsquo;s get to the meat of this tutorial. We are going to implement PDF generation with Chrome and Puppeteer, deployed on AWS Lambda through Serverless Stack. Here is what we will do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use Puppeteer in Chrome to navigate to a receipt page that we implemented.&lt;/li&gt;
&lt;li&gt;Instruct Chrome to take a PDF screenshot of the page.&lt;/li&gt;
&lt;li&gt;Stream the PDF file back to the user.&lt;/li&gt;
&lt;li&gt;Deploy this implementation on AWS Lambda using Serverless Stack and demonstrate the development experience throughout.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;PDF generation is a great use case for FaaS. It represents something that doesn&amp;rsquo;t happen very often and is triggered on demand by the user. Instead of letting our monolith application handle that intermittent traffic, we can delegate the task to FaaS in the cloud which will do this job without worrying us about scaling the infrastructure to cater to this particular use case. Then our application can just focus on handling the regular web traffic for general use.&lt;/p&gt;
&lt;h4 id=&#34;prerequisite-sample-receipt-pages&#34;&gt;Prerequisite: Sample receipt pages&lt;/h4&gt;
&lt;p&gt;First, let&amp;rsquo;s create some demo pages for PDF screenshots. I&amp;rsquo;m not going to go in-depth on these since it&amp;rsquo;s not the topic of the day. I have deployed these simple HTML pages that are styled like receipts on this Surge instance &lt;a href=&#34;https://unwieldy-key.surge.sh/&#34;&gt;here&lt;/a&gt;. You can find the code on GitHub &lt;a href=&#34;https://github.com/afifsohaili-ep/receipt-demos&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;getting-started&#34;&gt;Getting started&lt;/h4&gt;
&lt;p&gt;Let&amp;rsquo;s generate a new Serverless Stack project.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ npx create-serverless-stack@latest pdf-generator
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ &lt;span style=&#34;color:#038&#34;&gt;cd&lt;/span&gt; pdf-generator
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ npm install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Congratulations, we have just created a new project with Serverless Stack! Here we get a basic template of a working Serverless Stack app. You can go &lt;a href=&#34;https://docs.serverless-stack.com/installation&#34;&gt;here&lt;/a&gt; for more information on how to get started with Serverless Stack.&lt;/p&gt;
&lt;p&gt;First, let&amp;rsquo;s look at the files we get from bootstrapping the project and customize them wherever we see fit.&lt;/p&gt;
&lt;h5 id=&#34;1-sstjson&#34;&gt;1. sst.json&lt;/h5&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;pdf-generator&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;region&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;us-east-1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;main&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;stacks/index.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This file is the entry point to the Serverless Stack app, and here we can define the region of choice, the name of the app, and the main file that Serverless Stack will use.&lt;/p&gt;
&lt;h5 id=&#34;2-stacksindexjs&#34;&gt;2. stacks/index.js&lt;/h5&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // stacks/index.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; app.setDefaultFunctionProps({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   runtime: &amp;#34;nodejs12.x&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-new MyStack(app, &amp;#34;my-stack&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+new MyStack(app, &amp;#34;pdf-generator&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;stacks/index.js&lt;/code&gt; is the main file declared in &lt;code&gt;sst.json&lt;/code&gt;. When building the project, Serverless Stack will use this file as an entry point to our application. This file is pretty simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It configures the Lambda functions to use the Node.js 12.x runtime.&lt;/li&gt;
&lt;li&gt;It registers a stack called &lt;code&gt;my-stack&lt;/code&gt;. This is the name of the CloudFormation stack on AWS. We definitely want to give it a better name than &lt;code&gt;my-stack&lt;/code&gt; here, so let&amp;rsquo;s rename that to &lt;code&gt;pdf-generator&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&#34;3-stacksmystackjs&#34;&gt;3. stacks/MyStack.js&lt;/h5&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; * as sst from &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;@serverless-stack/resources&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;default&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; MyStack &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;extends&lt;/span&gt; sst.Stack {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  constructor(scope, id, props) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;super&lt;/span&gt;(scope, id, props);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// Create a HTTP API
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; api = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; sst.Api(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;this&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Api&amp;#34;&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      routes: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;GET /&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;src/lambda.handler&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// Show the endpoint in the output
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;this&lt;/span&gt;.addOutputs({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;ApiEndpoint&amp;#34;&lt;/span&gt;: api.url,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;MyStack.js&lt;/code&gt; is where we declare the resources we need inside a given CloudFormation stack. This can be anything from a cluster of Lambda functions, API Gateway endpoints, DynamoDB tables, S3 buckets, etc. The full list of resources that we can create with Serverless Stack is listed &lt;a href=&#34;https://docs.serverless-stack.com/constructs/Api&#34;&gt;here in the documentation&lt;/a&gt;. For our project we just need one API endpoint with API gateway, so what we are provided with here is already sufficient.&lt;/p&gt;
&lt;h4 id=&#34;deploying-the-project&#34;&gt;Deploying the project&lt;/h4&gt;
&lt;p&gt;Now, we can try to deploy this project to AWS. Ensure that the AWS Access Key ID and AWS Secret Access Key are set in the development environment, and then run &lt;code&gt;npx sst start&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# export AWS credentials&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;=&amp;lt;access key id&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;=&amp;lt;secret access key&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# or if the credentials are set in ~/.aws/credentials&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;AWS_PROFILE&lt;/span&gt;=&amp;lt;AWS profile name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Start the app in development mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;npx sst start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After a few minutes, we will have the Cloudformation stack deployed on our AWS account. Keep in mind that the first deployment will take a bit longer than subsequent deployments. When the deployment is done, we should be able to see that it is watching for file changes from our local machine.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;threadlightly-pdf-generator-pdf-generator | CREATE_COMPLETE | AWS::ApiGatewayV2::Route | ApiRouteGET8AC7D3F8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;threadlightly-pdf-generator-pdf-generator | CREATE_COMPLETE | AWS::Lambda::Permission | ApiRouteGETthreadlightlypdfgeneratorpdfgeneratorApiRouteGET7230D6CDPermissionE4542537
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;threadlightly-pdf-generator-pdf-generator | CREATE_COMPLETE | AWS::CloudFormation::Stack | threadlightly-pdf-generator-pdf-generator
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; ✅  threadlightly-pdf-generator-pdf-generator
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Stack threadlightly-pdf-generator-pdf-generator
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Status: deployed
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Outputs:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ApiEndpoint: https://&amp;lt;yourapigatewayurl&amp;gt;.execute-api.us-east-1.amazonaws.com
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;==========================
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; Starting Live Lambda Dev
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;==========================
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Transpiling Lambda code...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Debug session started. Listening for requests...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let&amp;rsquo;s head to the API URL given in the output and we should get a response back like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! Your request was received at 30/Nov/2021:06:21:13 +0000.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;developing-with-serverless-stack&#34;&gt;Developing with Serverless Stack&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s try updating one of the files provided to see how we can test the changes we have made and will make.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // stacks/MyStack.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; const api = new sst.Api(this, &amp;#34;Api&amp;#34;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   routes: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    &amp;#34;GET /&amp;#34;: &amp;#34;src/lambda.handler&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    &amp;#34;GET /downloads/receipt&amp;#34;: &amp;#34;src/lambda.handler&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;   },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // stacks/index.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   runtime: &amp;#34;nodejs12.x&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-new MyStack(app, &amp;#34;my-stack&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+new MyStack(app, &amp;#34;pdf-generator&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here we did two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We updated the API endpoint to &lt;code&gt;GET /downloads/receipt&lt;/code&gt; instead of just &lt;code&gt;GET /&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We made the Lambda function return the URL given in the query string.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once we save these changes we should see Serverless Stack automatically reloading our code and since we changed the API endpoint path, Serverless Stack will need to make changes to our infrastructure (i.e. the API Gateway resources), and whenever it detects that it has to change the infrastructure of our application, it will immediately prompt us to redeploy.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Rebuilding code...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Rebuilding infrastructure...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Done building code
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Press ENTER to redeploy infrastructure&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once we hit ENTER, Serverless Stack will automatically update our infrastructure for us: delete our old endpoint, create a new endpoint on &lt;code&gt;GET /downloads/receipt&lt;/code&gt;, and hook up our &lt;code&gt;src/lambda.js&lt;/code&gt; file to handle that endpoint.&lt;/p&gt;
&lt;p&gt;When it&amp;rsquo;s ready, try hitting the API gateway URL again, but this time append &lt;code&gt;/downloads/receipt?url=https://google.com&lt;/code&gt; to it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// https://&amp;lt;yourapigatewayurl&amp;gt;.execute-api.us-east-1.amazonaws.com/downloads/receipt?url=https://google.com
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello! You&amp;#39;ve requested to print the receipt at page https://google.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Great, now we have learned how to make changes to our Serverless Stack application during development. Let&amp;rsquo;s go ahead and add Puppeteer and Chrome to our Lambda function.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // stacks/MyStack.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; import * as sst from &amp;#34;@serverless-stack/resources&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+import { LayerVersion } from &amp;#34;@aws-cdk/aws-lambda&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+const layerArn = &amp;#34;arn:aws:lambda:us-east-1:764866452798:layer:chrome-aws-lambda:25&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // Create a HTTP API
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; const api = new sst.Api(this, &amp;#34;Api&amp;#34;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   routes: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-     &amp;#34;GET /downloads/receipt&amp;#34;: &amp;#34;src/lambda.handler&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+     &amp;#34;GET /downloads/receipt&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+       function: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         handler: &amp;#34;src/lambda.handler&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         // Increase the timeout for generating screenshots
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         timeout: 15,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         // Load Chrome in a Layer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         layers: [LayerVersion.fromLayerVersionArn(this, &amp;#34;Layer&amp;#34;, layerArn)],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         // Exclude bundling it in the Lambda function
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+         bundle: { externalModules: [&amp;#34;chrome-aws-lambda&amp;#34;] },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+       }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+     },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;   },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here, we are configuring our AWS Lambda function to use a Lambda layer that includes Chrome in our Lambda functions. You can think of these Lambda layers as being similar to NPM packages that you pull for extending your projects except that this is a 3rd party package for extending AWS Lambda functions instead.&lt;/p&gt;
&lt;p&gt;With this layer, your AWS Lambda function will boot with the Chrome binary already included. This layer is maintained &lt;a href=&#34;https://github.com/shelfio/chrome-aws-lambda-layer&#34;&gt;here&lt;/a&gt;. Make sure you&amp;rsquo;re using the ARN from the right region. In this tutorial, we are using &lt;code&gt;us-east-1&lt;/code&gt; per Serverless Stack&amp;rsquo;s default, so we are going to pick the ARN for &lt;code&gt;us-east-1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, let&amp;rsquo;s update our Lambda handler to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use Puppeteer to instruct Chrome to navigate to the URL passed in the parameter.&lt;/li&gt;
&lt;li&gt;Take a screenshot of the web page and save a PDF out of that. Puppeteer provides a handy &lt;code&gt;page.pdf&lt;/code&gt; API to do just that.&lt;/li&gt;
&lt;li&gt;Ultimately, stream that PDF file back to the user.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here&amp;rsquo;s the code for that, with some comments to help you out:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+import chrome from &amp;#34;chrome-aws-lambda&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+const puppeteer = chrome.puppeteer;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt; export async function handler(event) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-  return {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    statusCode: 200,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    headers: { &amp;#34;Content-Type&amp;#34;: &amp;#34;text/plain&amp;#34; },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-    body: `Hello! You&amp;#39;ve requested to print the receipt at page ${event.queryStringParameters.url}`,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-  };
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  let browser
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  let response
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  try {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    const { url } = event.queryStringParameters;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    browser = await puppeteer.launch({
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      args: chrome.args,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      executablePath: await chrome.executablePath,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    const page = await browser.newPage();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    // Use A5 size at 150dpi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    const width = 874
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    const height = 1240
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    await page.setViewport({ width, height });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    // Navigate to the url
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    await page.goto(url, { waitUntil: &amp;#39;networkidle2&amp;#39; });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    // Take the screenshot
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    await page.pdf({path: &amp;#39;receipt.pdf&amp;#39;, width: width + &amp;#34;px&amp;#34;, height: height + &amp;#34;px&amp;#34;, printBackground: true});
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    response = {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      statusCode: 200,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      body: JSON.stringify({message: &amp;#39;Screenshot taken&amp;#39;})
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    };
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  } catch(err) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    response = {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      statusCode: 500,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+      body: JSON.stringify({message: `An error occured. ${err.message}`})
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  } finally {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+    await browser &amp;amp;&amp;amp; browser.close()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  return response
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let&amp;rsquo;s also not forget to install Puppeteer and Chrome.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ npm install puppeteer puppeteer-core chrome-aws-lambda&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, give Serverless Stack some time to reload our new changes. Once we have the API endpoint ready to go, let&amp;rsquo;s head to the browser and hit our API endpoint, passing the URL to the sample receipt page that we did earlier as a query param.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// Visit https://&amp;lt;yourapigatewayurl&amp;gt;.execute-api.us-east-1.amazonaws.com/downloads/receipt?url=https://unwieldy-key.surge.sh/index.html
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// Response:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&amp;#34;message&amp;#34;:&amp;#34;Screenshot taken&amp;#34;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Great! We are getting back a 200 status code and a message saying &amp;ldquo;Screenshot taken&amp;rdquo;. That means our code works and there were no exceptions. We&amp;rsquo;re halfway there.&lt;/p&gt;
&lt;p&gt;Now, if we check the project folder, we should now see a new file called &lt;code&gt;receipt.pdf&lt;/code&gt; there. This is the &lt;code&gt;receipt.pdf&lt;/code&gt; captured by Puppeteer. We can open this file and verify that this is the demo receipt page that I deployed earlier that we passed to the endpoint.&lt;/p&gt;
&lt;p&gt;We can also hit the API endpoint with the second sample receipt URL (i.e. &lt;code&gt;url=https://unwieldy-key.surge.sh/receipt-2.html&lt;/code&gt;) and we should see the &lt;code&gt;receipt.pdf&lt;/code&gt; file gets replaced with the receipt from the new URL.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/01/generate-pdf-serverless-stack-chrome-puppeteer/page-screenshot.png&#34; alt=&#34;receipt.pdf file that we get&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that in a real-world application, the URL you would want to screenshot might need some form of authentication, so you are going to have to figure that out for your application. In this case the sample receipt page is accessible publicly, so we don&amp;rsquo;t run into this problem, but chances are your users&amp;rsquo; receipt page will not be accessible publicly like this.&lt;/p&gt;
&lt;p&gt;One way you could solve this is to create a special user account that has permission to visit these authenticated pages for this purpose. Then, prior to visiting the receipt page, you&amp;rsquo;d program Puppeteer to first log in as the special user and then head to the receipt page URL to be screenshot.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;However, we are only halfway there. We can see the &lt;code&gt;receipt.pdf&lt;/code&gt; file now only because the &lt;code&gt;receipt.pdf&lt;/code&gt; file was made available to us by Serverless Stack, which automatically downloads that file to our local machine in development mode. In a real AWS Lambda execution, the file will remain in the filesystem of that Lambda execution context instead, and the user will not see it.&lt;/p&gt;
&lt;p&gt;Hence we have to read and stream that file back to the user instead of returning a JSON message saying &amp;ldquo;Screenshot taken&amp;rdquo;. Getting a 200 status code is a great start, but that still does not give our users the actual PDF that they wanted, so let&amp;rsquo;s update our Lambda handler to do just that:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // src/lambda.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+import * as fs from &amp;#34;fs&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; // Take the screenshot
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; await page.pdf({path: &amp;#39;receipt.pdf&amp;#39;, width: width + &amp;#34;px&amp;#34;, height: height + &amp;#34;px&amp;#34;, printBackground: true});
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+const buffer = fs.readFileSync(&amp;#39;receipt.pdf&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; response = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   statusCode: 200,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;-  body: JSON.stringify({message: &amp;#39;Screenshot taken&amp;#39;})
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#fdd&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  headers: {&amp;#39;Content-type&amp;#39; : &amp;#39;application/pdf&amp;#39;},
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  body: buffer.toString(&amp;#39;base64&amp;#39;),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;+  isBase64Encoded : true,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#000;background-color:#dfd&#34;&gt;&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The changes this time are pretty simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Import the &lt;code&gt;fs&lt;/code&gt; module from Node.js, which will give us access to &lt;code&gt;readFileSync&lt;/code&gt; API that can read files on the disk.&lt;/li&gt;
&lt;li&gt;Read the generated &lt;code&gt;receipt.pdf&lt;/code&gt; file as a Buffer.&lt;/li&gt;
&lt;li&gt;Return the file&amp;rsquo;s Buffer as a base64 string in the response body.&lt;/li&gt;
&lt;li&gt;Set the content-type to be &lt;code&gt;application/pdf&lt;/code&gt;. Setting the content-type appropriately will tell the browser what to do with the file. Some browsers will display the PDF in an in-browser PDF reader, while others will download the file.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&amp;rsquo;s it! Now let&amp;rsquo;s test the change. Hit the same URL again.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2022/01/generate-pdf-serverless-stack-chrome-puppeteer/pdf-file.png&#34; alt=&#34;The PDF returned in a response, rendered in a PDF viewer in Edge&#34;&gt;&lt;/p&gt;
&lt;p&gt;Bingo, the endpoint now returns the PDF to the user! Now we can integrate this endpoint with our main application to delegate these on-demand PDF generation to managed FaaS infrastructure.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;FaaS platforms are a really great way to offload intermittent and on-demand traffic from our main application. This ensures our application runs smoother and can serve our general customers better, while at the same time handling these on-demand tasks really well.&lt;/p&gt;
&lt;h5 id=&#34;caveats&#34;&gt;Caveats&lt;/h5&gt;
&lt;p&gt;Building a full-blown application on the stack is also possible, but in my personal experience, it comes with its own complexities and tradeoffs that may or may not be worth the engineering time.&lt;/p&gt;
&lt;p&gt;For example, databases are an issue with going full-blown development on FaaS. Because FaaS can scale indefinitely, the number of connections to the database is a limitation.&lt;/p&gt;
&lt;p&gt;You can switch to FaaS-friendly databases such as DynamoDB or FaunaDB, but these are NoSQL databases and require a different mindset in structuring the application data than the simpler relational databases and that can cost tremendous engineering time. Relational databases are easier to work with for most types of applications.&lt;/p&gt;
&lt;p&gt;Nonetheless, more and more tools are being developed to tackle these complexities, such as &lt;a href=&#34;https://planetscale.com/&#34;&gt;PlanetScale&lt;/a&gt; and &lt;a href=&#34;https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-2.how-it-works.html&#34;&gt;Serverless Aurora v2&lt;/a&gt; (still in preview). All in all, it is definitely a trend worth following closely.&lt;/p&gt;
&lt;h3 id=&#34;further-readings&#34;&gt;Further readings&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Full code on GitHub: &lt;a href=&#34;https://github.com/afifsohaili-ep/serverless-stack-pdf-generator&#34;&gt;https://github.com/afifsohaili-ep/serverless-stack-pdf-generator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Code for the sample receipt pages: &lt;a href=&#34;https://github.com/afifsohaili-ep/receipt-demos&#34;&gt;https://github.com/afifsohaili-ep/receipt-demos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Serverless Stack: &lt;a href=&#34;https://serverless-stack.com/&#34;&gt;https://serverless-stack.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Serverless framework: &lt;a href=&#34;https://serverless.com/&#34;&gt;https://serverless.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.alldaydevops.com/blog/serverless-databases-the-good-the-bad-and-the-ugly&#34;&gt;Serverless Databases: The Good, the Bad, and the Ugly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Facial Recognition Using Amazon DeepLens: Counting Liquid Galaxy Interactions</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2019/05/facial-recognition-amazon-deeplens/"/>
      <id>https://www.endpointdev.com/blog/2019/05/facial-recognition-amazon-deeplens/</id>
      <published>2019-05-01T00:00:00+00:00</published>
      <author>
        <name>Ben Ironside Goldstein</name>
      </author>
      <content type="html">
        &lt;p&gt;I have been exploring the possible uses of a machine-learning-enabled camera for the Liquid Galaxy. The Amazon Web Services (AWS) &lt;a href=&#34;https://aws.amazon.com/deeplens/&#34;&gt;DeepLens&lt;/a&gt; is a camera that can receive and transmit data over wifi, and that has computing hardware built in. Since its hardware enables it to use machine learning models, it can perform computer vision tasks in the field.&lt;/p&gt;
&lt;h3 id=&#34;the-amazon-deeplens-camera&#34;&gt;The Amazon DeepLens camera&lt;/h3&gt;
&lt;img style=&#34;float: left; width: 400px; padding-right: 2em;&#34; src=&#34;/blog/2019/05/facial-recognition-amazon-deeplens/deeplens-front-angle.jpg&#34; alt=&#34;DeepLens&#34; /&gt;
&lt;p&gt;This camera is the first of its kind—likely the first of many, given the ongoing rapid adoption of Internet of Things (IoT) devices and computer vision. It came to End Point’s attention as hardware that could potentially interface with and extend End Point’s immersive visualization platform, the &lt;a href=&#34;https://www.visionport.com/&#34;&gt;Liquid Galaxy&lt;/a&gt;. We’ve thought of several ways computer vision could potentially work to enhance the platform, for example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Monitoring users’ reactions&lt;/li&gt;
&lt;li&gt;Counting unique visitors to the LG&lt;/li&gt;
&lt;li&gt;Counting the number of people using an LG at a given time&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first idea would depend on parsing facial expressions. Perhaps a certain moment in a user experience causes people to look confused, or particularly delighted—valuable insights. The second idea would generate data that could help us assess the platform’s impact, using a metric crucial to any potential clients whose goals involve engaging audiences. The third idea would create a simpler metric: the average number of people engaging with the system over a period of time. Nevertheless, this idea has a key advantage over the second: it doesn’t require distinguishing between people, which makes it a much more tractable project. This post focuses on the third idea.&lt;/p&gt;
&lt;p&gt;To set up the camera, the user has to plug it into a power outlet and connect it to wifi. The camera will still work even with a slow network connection, though when the connection is slower the delay between the camera seeing something and reporting it is longer. However, this delay was hardly noticable on my home network which has slow-to-moderate speeds of about 17 Mbps down and 33 Mbps up.&lt;/p&gt;
&lt;h3 id=&#34;computer-vision-and-the-amazon-deeplens&#34;&gt;Computer Vision and the Amazon DeepLens&lt;/h3&gt;
&lt;p&gt;A &lt;a href=&#34;https://en.wikipedia.org/wiki/Deep_learning&#34;&gt;deep learning model&lt;/a&gt; is a neural network with multiple layers of processing units. It is called “deep” because it has multiple layers. The inputs and outputs of each processing unit are numbers. These units are roughly analogous to neurons: they receive input from units in the previous layer, and output it to units in the next layer after transforming it based on a function. These “activation functions” can change in a variety of ways. The last layer’s outputs translate into the results. These models work because these functions get tuned based on how well the model works. For example, to make a model that labels each human face in a picture and draws a box around it, we would start with a corpus of pictures with boxes drawn around faces, as well as the versions of the pictures without the boxes drawn. We would test the model on the non-labeled images by checking—for each picture—whether the output generated by the model is correct. If not, the computer chooses different unit functions, tries again, and compares the results. Repeating this process thousands of times yields models which work remarkably well for a wide range of tasks, including computer vision.&lt;/p&gt;
&lt;p&gt;In deep learning for computer vision, training on large sets of labeled images enables models to generalize about visual characteristics. The training process takes a lot of computing resources, but once models are trained, they can produce results quickly and with relative ease. This is why the DeepLens is able to perform computer vision with its limited computing resources.&lt;/p&gt;
&lt;p&gt;Since the DeepLens is an Amazon product, it comes as no surprise that the user interface and backend for DeepLens consist of AWS services. One of the most important is &lt;a href=&#34;https://aws.amazon.com/sagemaker/&#34;&gt;SageMaker&lt;/a&gt;, which is used to train, manage, optimize, and deploy machine learning models such as neural networks. It includes hosted Jupyter notebooks (&lt;a href=&#34;https://jupyter.org/&#34;&gt;Jupyter&lt;/a&gt; is a development environment for data science), as well as the computing resources required for model training and storage. With SageMaker, users can train computer vision models for deployment to DeepLens, or import and adjust pretrained models from various sources.&lt;/p&gt;
&lt;p&gt;Remote management of the DeepLens depends on &lt;a href=&#34;https://aws.amazon.com/lambda/&#34;&gt;AWS Lambda&lt;/a&gt;, a “serverless” cloud service that provides an environment to run backend code and integrate with other cloud services. It runs the show, allowing users to manage everything from the camera’s behavior to what happens to gathered data. Another service, &lt;a href=&#34;https://aws.amazon.com/greengrass/&#34;&gt;AWS Greengrass&lt;/a&gt;, connects the instructions from AWS Lambda to the DeepLens, managing tasks like authentication, updates, and reactions to local events.&lt;/p&gt;
&lt;p&gt;Amazon’s IoT service saves information about each DeepLens, and allows users to manage their devices, for example by choosing which model is active on the device, or viewing a live stream from the camera. It also keeps track of what’s going on with the hardware, even when it’s off. When a model is running on the DeepLens, you can view a live stream of its inferences about what it’s seeing (the labeled images). Amazon has released various pretrained models designed to work on the DeepLens. Using a model for detecting faces, we can get a live stream that looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/05/facial-recognition-amazon-deeplens/one-face-recognition.jpg&#34; alt=&#34;one-face-recognition&#34;&gt;
&lt;br&gt;Me looking at the DeepLens in my kitchen&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/05/facial-recognition-amazon-deeplens/multi-face-recognition.jpg&#34; alt=&#34;multi-face-recognition&#34;&gt;
&lt;br&gt;Facial recognition inferences on multiple people. (Witness my smile of satisfaction at finally finding enthusiastic subjects of facial recognition.)&lt;/p&gt;
&lt;p&gt;Each face that the camera detects gets a box around it, along with the model’s level of certainty that it is a face. The above pictures were the results of an attempt to simulate the conditions where this could be used.&lt;/p&gt;
&lt;h3 id=&#34;the-model&#34;&gt;The Model&lt;/h3&gt;
&lt;p&gt;The model I used was trained on data from &lt;a href=&#34;http://www.image-net.org/&#34;&gt;ImageNet&lt;/a&gt;, a public database with hundreds or thousands of images associated with nouns. (For example they have 1537 &lt;a href=&#34;http://www.image-net.org/synset?wnid=n03376595&#34;&gt;pictures of folding chairs&lt;/a&gt;.) ImageNet is &lt;a href=&#34;https://arxiv.org/search/?query=imagenet&amp;amp;searchtype=all&amp;amp;source=header&#34;&gt;commonly&lt;/a&gt; used to train and test computer vision models.&lt;/p&gt;
&lt;p&gt;However, the training for this model didn’t stop there: Amazon used transfer learning from another large image dataset, &lt;a href=&#34;http://cocodataset.org/#home&#34;&gt;MS-COCO&lt;/a&gt;, to fine-tune the model for face detection. Transfer learning works essentially by retraining the last layer of an already-trained model. In this way it harnesses the “insights” of the existing model (e.g. about shapes, colors, and positions) by repurposing this information to make predictions about something else. In this case, whether something is a face.&lt;/p&gt;
&lt;p&gt;Since this model was pretrained and optimized by Amazon for the DeepLens, it provides a low effort route to implementing a computer vision model on the DeepLens. I didn’t have to do any of the processing on my own hardware. The DeepLens hardware took care of all the predictions, though the biggest resource savings were from not having to train the model myself (which can take days, or longer).&lt;/p&gt;
&lt;p&gt;When the facial recognition model is deployed and the DeepLens is on, an AWS Lambda function written in Python repeatedly prompts the camera to get frames from the camera:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;frame = awscam.getLastFrame()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&amp;hellip;to resize the frames before inference (the model accepts frames of particular size):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;frame_resize = cv2.resize(frame, (input_height, input_width))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&amp;hellip;to pass the frames to the model:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;parsed_inference_results = model.parseResult(model_type, model.doInference(frame_resize))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&amp;hellip;and to use the results to draw boxes around the faces:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), (&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;255&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;165&lt;/span&gt;, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;20&lt;/span&gt;), &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;10&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can see from how often “cv2” appears in the code above, this implementation relies heavily on code from &lt;a href=&#34;https://opencv.org&#34;&gt;OpenCV&lt;/a&gt;, an open source computer vision framework. Finally, the results are sent to the cloud:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;client.publish(topic=iot_topic, payload=json.dumps(cloud_output))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the last code snippet above, iot_topic refers to an Amazon “MQTT topic” (Message Queuing Telemetry Transport), for IoT devices. &lt;a href=&#34;https://en.wikipedia.org/wiki/MQTT&#34;&gt;MQTT&lt;/a&gt; is the standard connectivity framework for DeepLens and many other IoT devices. One of its advantages for this context is that it can handle situations with intermittent connectivity, by smoothly queueing messages for when the network connection is stable. The essence of MQTT is to enable publishing and subscribing to different topics. The system of topics enables results from a DeepLens to trigger other processes. For example, the DeepLens could publish a message when it sees a face, and this could prompt another cloud service to do something else, such as save what time and how long the face appeared.&lt;/p&gt;
&lt;p&gt;I wanted to test how data from this model would compare to a human’s perception. The first step was to understand what data the camera offers. It produces data about each frame analyzed: a timestamp (in 13-digit &lt;a href=&#34;https://en.wikipedia.org/wiki/Unix_time&#34;&gt;Unix time&lt;/a&gt;), and the predicted probability that something it identifies is a face. To gather this data, I used the AWS IoT service to manually subscribe to a secure MQTT topic where the DeepLens published its predictions. Each frame processed produces data like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;format&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;json&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;payload&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;face&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.5654296875&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;qos&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1554853281975&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;topic&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;$aws/things/deeplens_bnU5sr2sSD2ecW5YkfJZtw/infer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The data generated by a single frame (with one face) when processed by the DeepLens.&lt;/p&gt;
&lt;p&gt;For my purposes, I was only interested in the timestamps and payloads (which contain the number of faces identified, and their probabilities). I decided to test the facial recognition model under several different conditions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;No faces present&lt;/li&gt;
&lt;li&gt;One face present&lt;/li&gt;
&lt;li&gt;Multiple faces present&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For condition 1 I just aimed it at an empty room for 20 minutes, and for condition 2 I sat in front of the camera for 20 minutes. For condition 3, I aimed the camera at a public space for 20 minutes, and while it was running I kept an ongoing count of the number of people looking in the general direction of the camera (I put the camera in front of a wall with a TV on it so people would be more likely to look towards it). Then I averaged my count over the duration of the sample, which resulted in an average engagement number of 2.5 people, meaning that on average, 2.5 people were looking at the camera. In an attempt to minimize bias, I made my human-eye assessment before looking at any of the data.&lt;/p&gt;
&lt;p&gt;I’ll spoil one aspect of the results right away: there were no false positives under any condition. Even the lower probability guesses corresponded to actual faces, though this result might not hold true in a room with lots of face-like art, that’s not too common of a scenario. This simplified things, since it meant there was no need to set a lower bound on the probabilities which we should count—any face detected by the camera is a face. This also highlights one of my remaining questions about the model: is there useful information to be gained from the probabilities?&lt;/p&gt;
&lt;p&gt;Another important note: I noticed early in the experiment that it almost never detects a face farther than 15 feet away. For the use case of a Liquid Galaxy, the 15-foot range is too short to capture all types of engagement (some people look at it across the room), but from my experience with the system I think that users within this range could be accurately described as focused users—something worth measuring, but certainly not everything worth measuring. After noticing this, I retested condition 2 with my face about 5 feet from the DeepLens, after initially trying it from across a room.&lt;/p&gt;
&lt;h3 id=&#34;how-did-the-deeplens-counts-compare-to-my-counts&#34;&gt;How did the DeepLens counts compare to my counts?&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/05/facial-recognition-amazon-deeplens/results.png&#34; alt=&#34;results&#34;&gt;&lt;/p&gt;
&lt;p&gt;The model matched my performance in conditions 1 and 2, which makes a strong statement about its reliability in relatively static and close-up conditions such as looking at an empty room, or looking at someone stare at their laptop across a small table. In contrast, it did not count as many faces as I did in condition 3—so I’m happy to report I can still outperform A.I. on something.&lt;/p&gt;
&lt;p&gt;Anyway, this suggests that the model is somewhat conservative, at least compared to my count (likely partly due to my eyes having a range larger than 15 feet). Therefore, when considering usage statistics gathered by a similar method, it might make most sense to think of the results as a lower bound, e.g. “the average number of people focused on the system was more than 2.1”.&lt;/p&gt;
&lt;p&gt;It would be useful to experiment with the multiple faces condition again, to see how robust these findings are. It would also be helpful to keep track of factors like how much people move, the lighting, and the orientation of the camera, to see if they might impact the results. It would also be useful to automate the data collection and analysis.&lt;/p&gt;
&lt;p&gt;This investigation has showed me that the DeepLens has a lot of potential as a tool for measuring engagement. Perhaps a future post will examine how it can be used to count users.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Thanks for reading! You are welcome to learn more about &lt;a href=&#34;https://www.visionport.com/&#34;&gt;End Point Liquid Galaxy&lt;/a&gt; and &lt;a href=&#34;https://aws.amazon.com/deeplens/&#34;&gt;AWS DeepLens&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Amazon AWS upgrades to Postgres with Bucardo</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2017/06/amazon-aws-upgrades-to-postgres-with/"/>
      <id>https://www.endpointdev.com/blog/2017/06/amazon-aws-upgrades-to-postgres-with/</id>
      <published>2017-06-06T00:00:00+00:00</published>
      <author>
        <name>Greg Sabino Mullane</name>
      </author>
      <content type="html">
        &lt;div class=&#34;separator&#34; style=&#34;clear: both; float: right; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2017/06/amazon-aws-upgrades-to-postgres-with/image-0.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&#34;&gt;&lt;img border=&#34;0&#34; data-original-height=&#34;236&#34; data-original-width=&#34;320&#34; src=&#34;/blog/2017/06/amazon-aws-upgrades-to-postgres-with/image-0.jpeg&#34;/&gt;&lt;/a&gt;&lt;br/&gt;&lt;small&gt;(&lt;a href=&#34;https://flic.kr/p/6uzTXi&#34;&gt;Bird-chasing photo&lt;/a&gt; by &lt;a href=&#34;https://www.flickr.com/photos/dougww/&#34;&gt;Doug Waldron&lt;/a&gt;)&lt;/small&gt;&lt;/div&gt;
&lt;p&gt;Many of our clients at End Point are using the incredible &lt;a href=&#34;https://aws.amazon.com/rds/&#34;&gt;Amazon Relational Database Service&lt;/a&gt; (RDS),
which allows for quick setup and use of a database system. Despite minimizing
many database administration tasks, some issues still exist, one of which is
upgrading. Getting to a new version of Postgres is simple enough with
RDS, but we’ve had clients use &lt;a href=&#34;https://github.com/bucardo/bucardo&#34;&gt;Bucardo&lt;/a&gt; to do the upgrade, rather than Amazon’s built-in upgrade process.
Some of you may be exclaiming  &lt;em&gt;“A trigger-based replication system just to upgrade?!”&lt;/em&gt;
While using it may seem unintuitive, there are some very good reasons to use Bucardo
for your RDS upgrade:&lt;/p&gt;
&lt;h4 id=&#34;minimize-application-downtime&#34;&gt;Minimize application downtime&lt;/h4&gt;
&lt;p&gt;Many businesses are very sensitive to any database downtime, and upgrading your database
to a new version always incurs that cost. Although RDS uses the ultra-fast &lt;a href=&#34;https://www.postgresql.org/docs/current/static/pgupgrade.html&#34;&gt;pg_upgrade &amp;ndash;links&lt;/a&gt; method, the whole upgrade process can take quite
a while—​or at least too long for the business to accept. Bucardo can reduce the application
downtime from around seven minutes to ten seconds or less.&lt;/p&gt;
&lt;h4 id=&#34;upgrade-more-than-one-version-at-once&#34;&gt;Upgrade more than one version at once&lt;/h4&gt;
&lt;p&gt;As of this writing (June 2017), RDS only allows upgrading of one major Postgres version at a time. Since
pg_upgrade can easily handle upgrading older versions, this limitation will probably be fixed
someday. Still, it means even more application downtime—​to the tune of seven minutes for
each major version. If you are going from 9.3 to 9.6 (via 9.4 and 9.5), that’s at least 21 minutes
of application downtime, with many unnecessary steps along the way. The total time
for Bucardo to jump from 9.3 to 9.6 (or any major version to another one) is still under ten seconds.&lt;/p&gt;
&lt;h4 id=&#34;application-testing-with-live-data&#34;&gt;Application testing with live data&lt;/h4&gt;
&lt;p&gt;The Bucardo upgrade process involves setting up a second RDS instance running the newer version,
copying the data from the current RDS server, and then letting Bucardo replicate the changes
as they come in. With this system, you can have two “live” databases you can point your applications
to. With RDS, you must create a snapshot of your current RDS, upgrade &lt;em&gt;that&lt;/em&gt;, and then point
your application to the new (and frozen-in-time) database. Although this is still useful for testing your application
against the newer version of the database, it is not as useful as having an automatically-updated version of
the database.&lt;/p&gt;
&lt;h4 id=&#34;control-and-easy-rollback&#34;&gt;Control and easy rollback&lt;/h4&gt;
&lt;p&gt;With Bucardo, the initial setup costs, and the overhead of using triggers on your production
database, is balanced a bit by ensuring you have complete control over the upgrade process.
The migration can happen when you want, at a pace you want, and can even happen in stages as you point
some of the applications in your stack to the new version, while keeping some pointed at
the old. And rolling back is as simple as pointing apps back at the older version. You could
even set up Bucardo as “master-master”, such that both new and old versions can write data
at the same time (although this step is rarely necessary).&lt;/p&gt;
&lt;h4 id=&#34;database-bloat-removal&#34;&gt;Database bloat removal&lt;/h4&gt;
&lt;p&gt;Although the pg_upgrade program that Amazon RDS uses for upgrading is extraordinarily
fast and efficient, the data files are seldom, if ever, changed at all, and &lt;a href=&#34;https://www.compose.com/articles/postgresql-bloat-origins-monitoring-and-managing/&#34;&gt;table and index bloat&lt;/a&gt; is never removed. On the other hand,
an upgrade system using Bucardo creates the tables from scratch on the new database, and
thus completely removes all historical bloat. (Indeed, one time a client thought
something had gone wrong, as the new version’s total database size had shrunk radically -
but it was simply removal of all table bloat!).&lt;/p&gt;
&lt;h4 id=&#34;statistics-remain-in-place&#34;&gt;Statistics remain in place&lt;/h4&gt;
&lt;p&gt;The pg_upgrade program currently has a glaring flaw—​no copying of the information in
the pg_statistic table. Which means that although an Amazon RDS upgrade completes in about
seven minutes, the performance will range somewhere from slightly slow to completely unusable,
until all those statistics are regenerated on the new version via
the &lt;a href=&#34;https://www.postgresql.org/docs/current/static/sql-analyze.html&#34;&gt;ANALYZE command&lt;/a&gt;. How long this can take depends on a number of factors,
but in general, the larger your database, the longer it will take—​a database-wide
analyze can take hours on very large databases. As mentioned above, upgrading via
Bucardo relies on COPYing the data to a fresh copy of the table. Although the statistics
also need to be created when using Bucardo, the time cost for this does NOT apply to
the upgrade time, as it can be done any time earlier, making the effective cost of
generating statistics zero.&lt;/p&gt;
&lt;h3 id=&#34;upgrading-rds-the-amazon-way&#34;&gt;Upgrading RDS the Amazon way&lt;/h3&gt;
&lt;p&gt;Having said all that, the native upgrade system for RDS is very simple and fast. If the
drawbacks above do not apply to you—​or can be suffered with minimal business pain -
then this way should always be the upgrade approach to use. Here is a quick walk through
of how an Amazon RDS upgrade is done.&lt;/p&gt;
&lt;p&gt;For this example, we will create a new Amazon RDS instance. The creation is
amazingly simple: just log into aws.amazon.com, choose RDS, choose PostgreSQL
(always the best choice!), and then fill in a few details, such as preferred version,
server size, etc. The “DB Engine Version” was set as
“PostgreSQL 9.3.16-R1”, the “DB Instance Class” as
db.t2.small &amp;ndash; 1 vCPU, 2 GiB RAM, and “Multi-AZ Deployment” as
no. All other choices are the default. To finish up this section
of the setup, “DB Instance Identifier” was set to gregtest, the
“Master Username” to greg, and the “Master Password” to b5fc93f818a3a8065c3b25b5e45fec19&lt;/p&gt;
&lt;p&gt;Clicking on “Next Step” brings up more options, but the only one that needs to change is to
specify the “Database Name” as gtest. Finally, the “Launch DB Instance” button.
The new database is on the way! Select “View your DB Instance” and then keep reloading
until the “Status” changes to Active.&lt;/p&gt;
&lt;p&gt;Once the instance is running, you will be shown a connection string that looks like this:
gregtest.zqsvirfhzvg.us-east-1.rds.amazonaws.com:5432. That standard
port is not a problem, but who wants to ever type that hostname out, or
even have to look at it? The &lt;a href=&#34;/blog/2016/10/postgres-connection-service-file/&#34;&gt;pg_service.conf file&lt;/a&gt; comes to the rescue with
this new entry inside the ~/.pg_service.conf file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[gtest]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;host=gregtest.zqsvirfhzvg.us-east-1.rds.amazonaws.com
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;port=5432
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dbname=gtest
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;user=greg
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;password=b5fc93f818a3a8065c3b25b5e45fec19
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;connect_timeout=10&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we run a quick test to make sure psql is able to connect, and that the database is an Amazon RDS database:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ psql service=gtest -Atc &amp;#34;show rds.superuser_variables&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;session_replication_role&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We want to use the pgbench program to add a little content to the database, just
to give the upgrade process something to do. Unfortunately, we cannot simply
feed the “service=gtest” line to the pgbench program, but a little
environment variable craftiness gets the job done:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ unset PGSERVICEFILE PGSERVICE PGHOST PGPORT PGUSER PGDATABASE
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ export PGSERVICEFILE=/home/greg/.pg_service.conf PGSERVICE=gtest
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pgbench -i -s 4
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NOTICE:  table &amp;#34;pgbench_history&amp;#34; does not exist, skipping
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NOTICE:  table &amp;#34;pgbench_tellers&amp;#34; does not exist, skipping
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NOTICE:  table &amp;#34;pgbench_accounts&amp;#34; does not exist, skipping
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;NOTICE:  table &amp;#34;pgbench_branches&amp;#34; does not exist, skipping
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;creating tables...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;100000 of 400000 tuples (25%) done (elapsed 0.66 s, remaining 0.72 s)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;200000 of 400000 tuples (50%) done (elapsed 1.69 s, remaining 0.78 s)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;300000 of 400000 tuples (75%) done (elapsed 4.83 s, remaining 0.68 s)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;400000 of 400000 tuples (100%) done (elapsed 7.84 s, remaining 0.00 s)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;vacuum...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;set primary keys...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;done.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At 68MB in size, this is still not a big database—​so let’s create a large table, then
create a bunch of databases, to make pg_upgrade work a little harder:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;## Make the whole database 1707 MB:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ psql service=gtest -c &amp;#34;CREATE TABLE extra AS SELECT * FROM pgbench_accounts&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SELECT 400000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ for i in {1..5}; do psql service=gtest -qc &amp;#34;INSERT INTO extra SELECT * FROM extra&amp;#34;; done
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;## Make the whole cluster about 17 GB:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ for i in {1..9}; do psql service=gtest -qc &amp;#34;CREATE DATABASE gtest$i TEMPLATE gtest&amp;#34; ; done
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ psql service=gtest -c &amp;#34;SELECT pg_size_pretty(sum(pg_database_size(oid))) FROM pg_database WHERE datname ~ &amp;#39;gtest&amp;#39;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;17 GB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To start the upgrade, we log into the AWS console, and choose “Instance Actions”, then “Modify”. Our only choices for
instances are
“9.4.9” and “9.4.11”, plus some older revisions in the 9.3 branch. Why anything other than the latest revision in
the next major branch (i.e. 9.4.11) is shown, I have no idea! Choose 9.4.11, scroll down to the
bottom, choose “Apply Immediately”, then “Continue”, then “Modify DB Instance”. The upgrade has begun!&lt;/p&gt;
&lt;p&gt;How long will it take? All one can do is keep refreshing to see when your new database is ready. As mentioned above,
7 minutes and 30 seconds is the total time. The logs show how things break down:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;11:52:43 DB instance shutdown
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;11:55:06 Backing up DB instance
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;11:56:12 DB instance shutdown
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;11:58:42 The parameter max_wal_senders was set to a value incompatible with replication. It has been adjusted from 5 to 10.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;11:59:56 DB instance restarted
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;12:00:18 Updated to use DBParameterGroup default.postgres9.4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;How much of that time is spent on upgrading though? Surprisingly little. We can do a quick local test to see how
long the same database takes to upgrade from 9.3 to 9.4 using pg_upgrade &amp;ndash;links: 20 seconds! Ideally Amazon
will improve upon the total downtime at some point.&lt;/p&gt;
&lt;h3 id=&#34;upgrading-rds-with-bucardo&#34;&gt;Upgrading RDS with Bucardo&lt;/h3&gt;
&lt;p&gt;As an asynchronous, trigger-based replication system, Bucardo is perfect for situations like this where you need to
temporarily sync up two concurrent versions of Postgres. The basic process is to create a new Amazon RDS instance
of your new Postgres version (e.g. 9.6), install the Bucardo program on a cheap EC2 box, and then have Bucardo replicate
from the old Postgres version (e.g. 9.3) to the new one. Once both instances are in sync, just point your application
to the new version and shut the old one down. One way to perform the upgrade is detailed below.&lt;/p&gt;
&lt;p&gt;Some of the steps are simplified, but the overall process is
intact. First, find a temporary box for Bucardo to run on. It doesn’t have to
be powerful, or have much disk space, but as network connectivity is important, using an EC2 box
is recommended. Install Postgres (9.6 or better, because of pg_dump) and Bucardo (latest or HEAD recommended), then put your
old and new RDS databases into your pg_service.conf file as “rds93” and “rds96” to keep things
simple.&lt;/p&gt;
&lt;p&gt;The next step is to make a copy of the database on the new Postgres 9.6 RDS database. We want the bare
minimum schema here: no data, no triggers, no indexes, etc. Luckily, this is simple using pg_dump:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pg_dump service=rds93 --section=pre-data | psql -q service=rds96&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;From this point forward, no &lt;a href=&#34;https://en.wikipedia.org/wiki/Data_definition_language&#34;&gt;DDL&lt;/a&gt; should be run on the old server. We take a snapshot of the
post-data items right away and save it to a file for later:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pg_dump service=rds93 --section=post-data -f rds.postdata.pg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Time to get Bucardo ready. Recall that Bucardo can only replicate tables that have a
primary key or unique index. But if those tables are small enough, you can simply
copy them over at the final point of migration later.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ bucardo install
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ bucardo add db A dbservice=rds93
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ bucardo add db B dbservice=rds96
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;## Create a sync and name it &amp;#39;migrate_rds&amp;#39;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ bucardo add sync migrate_rds tables=all dbs=A,B&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That’s it! The current database will now have triggers that are recording
any changes made, so we may safely do a bulk copy to the new database. This
step might take a very long time, but that’s not a problem.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pg_dump service=rds93 --section=data | psql -q service=rds96&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Before we create the indexes on the new server, we start the Bucardo sync to copy
over any rows that were changed while the pg_dump was going on. After that, the
indexes, primary keys, and other items can be created:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ bucardo start
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ tail -f log.bucardo ## Wait until the sync finishes once
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ bucardo stop
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ psql service=rds96 -q -f rds.postdata.pg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For the final migration, we simply stop anything from writing to the 9.3 database,
have Bucardo perform a final sync of any changed rows, and then point your
application to the 9.6 database. The whole process can happen very quickly:
well under a minute for most cases.&lt;/p&gt;
&lt;p&gt;Upgrading major Postgres versions is never a trivial task, but both Bucardo and pg_upgrade
allow it to be orders of magnitude faster and easier than the old method of using the
pg_dump utility. Upgrading your Amazon AWS Postgres instance is fast and easy
using the AWS pg_upgrade method, but it has limitations, so having Bucardo
help out can be a very useful option.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Elastic Beanstalk Whenever</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/01/elastic-beanstalk-whenever/"/>
      <id>https://www.endpointdev.com/blog/2015/01/elastic-beanstalk-whenever/</id>
      <published>2015-01-27T00:00:00+00:00</published>
      <author>
        <name>Kent Krenrich</name>
      </author>
      <content type="html">
        &lt;p&gt;I recently got the opportunity to pick up development on a Ruby on Rails application that was originally set up to run on &lt;a href=&#34;https://aws.amazon.com&#34;&gt;AWS&lt;/a&gt; using their &lt;a href=&#34;https://aws.amazon.com/elasticbeanstalk/&#34;&gt;Elastic Beanstalk&lt;/a&gt; deployment tools. One of our first tasks was to move some notification hooks out of the normal workflow into scripts and schedule those batch scripts using cron.&lt;/p&gt;
&lt;p&gt;Historically, I’ve had extremely good luck with &lt;a href=&#34;https://github.com/javan/whenever&#34;&gt;Whenever&lt;/a&gt;. In my previous endeavors I’ve utilized Capistrano which Whenever merged with seamlessly. With how simple it was to integrate Whenever with Capistrano, I anticipated a similar experience dealing with Elastic Beanstalk. While the integration was not as seamless as Capistrano, I did manage to make it work.&lt;/p&gt;
&lt;p&gt;My first stumbling block was finding documentation on how to do after or post hooks. I managed to find &lt;a href=&#34;https://forums.aws.amazon.com/thread.jspa?messageID=493887&#34;&gt;this forum post&lt;/a&gt; and &lt;a href=&#34;http://junkheap.net/blog/2013/05/20/elastic-beanstalk-post-deployment-scripts/&#34;&gt;this blog post&lt;/a&gt; which helped me out a lot. The important detail is that there is a “post” directory to go along with “pre” and “enact”, but it’s not present by default, so it can be easy to miss.&lt;/p&gt;
&lt;p&gt;I used &lt;a href=&#34;http://junkheap.net/blog/2013/05/20/elastic-beanstalk-post-deployment-scripts/&#34;&gt;Marcin’s delayed_job config&lt;/a&gt; as a base. The first thing I had to address was an apparent change in Elastic Beanstalk’s configuration structure. Marcin’s config has&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  . /opt/elasticbeanstalk/support/envvars&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;but that file doesn’t exist on the system I was working on. With a small amount of digging, I found:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  . /opt/elasticbeanstalk/containerfiles/envvars&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;in one of the other ebextensions. Inspecting that file showed a definition for and exportation of $EB_CONFIG_APP_CURRENT suggesting this is a similar file just stored in a different location now.&lt;/p&gt;
&lt;p&gt;Another change that appears to have occurred since Marcin developed his config is that directories will be created automatically if they don’t already exist when adding a file in the files section of the config. That allows us to remove the entire commands section to simplify things.&lt;/p&gt;
&lt;p&gt;That left me with a config that looked like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;files:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/opt/elasticbeanstalk/hooks/appdeploy/post/99_update_cron.sh&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  mode: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;000755&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  owner: root
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  group: root
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  content: |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;#! /usr/bin/env bash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    . /opt/elasticbeanstalk/containerfiles/envvars
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    su -c &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;cd &lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;$EB_CONFIG_APP_CURRENT&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;; bundle exec whenever --update-cron&amp;#34;&lt;/span&gt; - &lt;span style=&#34;color:#369&#34;&gt;$EB_CONFIG_APP_USER&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This command completed successfully but on staging the cron jobs failed to run. The reason for that was an environment mismatch. The runner entries inside the cron commands weren’t receiving a RAILS_ENV or other type of environment directive so they were defaulting to production and failing when no database was found.&lt;/p&gt;
&lt;p&gt;After some greping I was able to find a definition for RACK_ENV in:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/opt/elasticbeanstalk/containerfiles/envvars.d/sysenv&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Making use of it, I came up with this final version:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;files:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/opt/elasticbeanstalk/hooks/appdeploy/post/99_update_cron.sh&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  mode: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;000755&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  owner: root
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  group: root
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  content: |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;#! /usr/bin/env bash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    . /opt/elasticbeanstalk/containerfiles/envvars
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    . /opt/elasticbeanstalk/containerfiles/envvars.d/sysenv
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    su -c &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;cd &lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;$EB_CONFIG_APP_CURRENT&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;; bundle exec whenever --update-cron --set=&amp;#39;environment=&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;$RACK_ENV&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;&amp;#34;&lt;/span&gt; - &lt;span style=&#34;color:#369&#34;&gt;$EB_CONFIG_APP_USER&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


      </content>
    </entry>
  
    <entry>
      <title>PostgreSQL EC2/EBS/RAID 0 snapshot backup</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2010/02/postgresql-ec2-ebs-raid0-snapshot/"/>
      <id>https://www.endpointdev.com/blog/2010/02/postgresql-ec2-ebs-raid0-snapshot/</id>
      <published>2010-02-23T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;One of our clients uses &lt;a href=&#34;https://aws.amazon.com/&#34;&gt;Amazon Web Services&lt;/a&gt; to host their production application and database servers on EC2 with EBS (Elastic Block Store) storage volumes. Their main database is &lt;a href=&#34;/expertise/postgresql/&#34;&gt;PostgreSQL&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A big benefit of Amazon’s cloud services is that you can easily add and remove virtual server instances, storage space, etc. and pay as you go. One known problem with Amazon’s EBS storage is that it is much more I/O limited than, say, a nice SAN.&lt;/p&gt;
&lt;p&gt;To partially mitigate the I/O limitations, they’re using 4 EBS volumes to back a Linux software RAID 0 block device. On top of that is the xfs filesystem. This gives roughly 4x the I/O throughput and has been effective so far.&lt;/p&gt;
&lt;p&gt;They ship WAL files to a secondary server that serves as warm standby in case the primary server fails. That’s working fine.&lt;/p&gt;
&lt;p&gt;They also do nightly backups using pg_dumpall on the master so that there’s a separate portable (SQL) backup not dependent on the server architecture. The problem that led to this article is that extra I/O caused by pg_dumpall pushes the system beyond its I/O limits. It adds both reads (from the PostgreSQL database) and writes (to the SQL output file).&lt;/p&gt;
&lt;p&gt;There are several solutions we are considering so that we can keep both binary backups of the database and SQL backups, since both types are valuable. In this article I’m not discussing all the options or trying to decide which is best in this case. Instead, I want to consider just one of the tried and true methods of backing up the binary database files on another host to offload the I/O:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an atomic snapshot of the block devices&lt;/li&gt;
&lt;li&gt;Spin up another virtual server&lt;/li&gt;
&lt;li&gt;Mount the backup volume&lt;/li&gt;
&lt;li&gt;Start Postgres and allow it to recover from the apparent “crash” the server had (since there wasn’t a clean shutdown of the database before the snapshot&lt;/li&gt;
&lt;li&gt;Do whatever pg_dump or other backups are desired&lt;/li&gt;
&lt;li&gt;Make throwaway copies of the snapshot for QA or other testing&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The benefit of such snapshots is that you get an exact backup of the database, with whatever table bloat, indexes, statistics, etc. exactly as they are in production. That’s a big difference from a freshly created database and import from pg_dump.&lt;/p&gt;
&lt;p&gt;The difference here is that we’re using 4 EBS volumes with RAID 0 striped across them, and there isn’t currently a way to do an atomic snapshot of all 4 volumes at the same time. So it’s no longer “atomic” and who knows what state the filesystem metadata and the file data itself would be in?&lt;/p&gt;
&lt;p&gt;Well, why not try it anyway? Filesystem metadata doesn’t change that often, especially in the controlled environment of a Postgres data volume. Snapshotting within a relatively short timeframe would be pretty close to atomic, and probably look to the software (operating system and database) like some kind of strange crash since some EBS volumes would have slightly newer writes than others. But aren’t all crashes a little unpredictable? Why shouldn’t the software be able to deal with that? Especially if we have Postgres make a checkpoint right before we snapshot.&lt;/p&gt;
&lt;p&gt;I wanted to know if it was crazy or not, so I tried it on a new set of services in a separate AWS account. Here are the notes and some details of what I did:&lt;/p&gt;
&lt;p&gt;1. Created one EC2 image:&lt;/p&gt;
&lt;p&gt;Amazon EC2 Debian 5.0 lenny AMI built by Eric Hammond&lt;br&gt;
Debian AMI ID ami-4ffe1926 (x86_64)&lt;br&gt;
Instance Type: High-CPU Extra Large (c1.xlarge) — 7 GB RAM, 8 CPU cores&lt;/p&gt;
&lt;p&gt;2. Created 4 x 10 GB EBS volumes&lt;/p&gt;
&lt;p&gt;3. Attached volumes to the image&lt;/p&gt;
&lt;p&gt;4. Created software RAID 0 device:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mdadm -C /dev/md0 -n &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;4&lt;/span&gt; -l &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt; -z max /dev/sdf /dev/sdg /dev/sdh /dev/sdi&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;5. Created XFS filesystem on top of RAID 0 device:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkfs -t xfs -L /pgdata /dev/md0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;6. Set up in /etc/fstab and mounted:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir /pgdata
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# edit /etc/fstab, with noatime&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mount /pgdata&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;7. Installed PostgreSQL 8.3&lt;/p&gt;
&lt;p&gt;8. Configured postgresql.conf to be similar to primary production database server&lt;/p&gt;
&lt;p&gt;9. Created empty new database cluster with data directory in /pgdata&lt;/p&gt;
&lt;p&gt;10. Started Postgres and imported a play database (from public domain census name data and Project Gutenberg texts), resulting in about 820 MB in data directory&lt;/p&gt;
&lt;p&gt;11. Ran some bulk inserts to grow database to around 5 GB&lt;/p&gt;
&lt;p&gt;12. Rebooted EC2 instance to confirm everything came back up correctly on its own&lt;/p&gt;
&lt;p&gt;13. Set up two concurrent data-insertion processes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;50 million row insert based on another local table (INSERT INTO &amp;hellip; SELECT &amp;hellip;), in a single transaction (hits disk hard, but nothing should be visible in the snapshot because the transaction won’t have committed before the snapshot is taken)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Repeated single inserts in autocommit mode (Python script writing INSERT statements using random data from /usr/share/dict/words piped into psql), to verify that new inserts made it into the snapshot, and no partial row garbage leaked through&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;14. Started those “beater” jobs, which mostly consumed 2-3 CPU cores&lt;/p&gt;
&lt;p&gt;15. Manually inserted a known test row and created a known view that should appear in the snapshot&lt;/p&gt;
&lt;p&gt;16. Started Postgres’s backup mode that allows for copying binary data files in a non-atomic manner, which also does a CHECKPOINT and thus also a filesystem sync:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;SELECT&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;pg_start_backup(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;raid_backup&amp;#39;&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;17. Manually inserted a 2nd known test row &amp;amp; 2nd known test view that I don’t want to appear in the snapshot after recovery&lt;/p&gt;
&lt;p&gt;18. Ran snapshot script which calls ec2-create-snapshot on each of the 4 EBS volumes—​during first run, run serially quite slowly taking about 1 minute total; during second run, run in parallel such that the snapshot point was within 1 second for all 4 volumes&lt;/p&gt;
&lt;p&gt;19. Tell Postgres the backup’s over:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;SELECT&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;pg_stop_backup();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;20. Ran script to create new EBS volumes derived from the 4 snapshots (which aren’t directly usable and always go into S3), using &lt;code&gt;ec2-create-volume --snapshot&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;21. Run script to attach new EBS volumes to devices on the new EC2 instance using &lt;code&gt;ec2-attach-volume&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;22. Then, on the new EC2 instance for doing backups:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mdadm --assemble --scan&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mount /pgdata&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Start Postgres&lt;/li&gt;
&lt;li&gt;Count rows on the 2 volatile tables; confirm that the table with the in-process transaction doesn’t show any new rows, and that the table getting individual rows committed to reads correctly&lt;/li&gt;
&lt;li&gt;&lt;code&gt;VACUUM VERBOSE&lt;/code&gt; — and confirm no errors or inconsistencies detected&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pg_dumpall&lt;/code&gt; # confirmed no errors and data looks sound&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It worked! No errors or problems, and pretty straightforward to do.&lt;/p&gt;
&lt;p&gt;Actually before doing all the above I first did a simpler trial run with no active database writes happening, and didn’t make any attempt for the 4 EBS snapshots to happen simultaneously. They were actually spread out over almost a minute, and it worked fine. With the confidence that the whole thing wasn’t a fool’s errand, I then put together the scripts to do lots of writes during the snapshot and made the snapshots run in parallel so they’d be close to atomic.&lt;/p&gt;
&lt;p&gt;There are lots of caveats to note here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This is an experiment in progress, not a how-to for the general public.&lt;/li&gt;
&lt;li&gt;The data set that was snapshotted was fairly small.&lt;/li&gt;
&lt;li&gt;Two successful runs, even with no failures, is not a very big sample set. :)&lt;/li&gt;
&lt;li&gt;I didn’t use Postgres’s point-in-time recovery (PITR) here at all—​I just started up the database and let Postgres recover from an apparent crash. Shipping over the few WAL logs from the master collected during the pg_backup run &lt;em&gt;after&lt;/em&gt; the snapshot copying is complete would allow a theoretically fully reliable recovery to be made, not just a practically non-failing recovery as I did above.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So there’s more work to be done to prove this technique viable in production for a mission-critical database, but it’s a promising start worth further investigation. It shows that there &lt;em&gt;is&lt;/em&gt; a way to back up a database across multiple EBS volumes without adding noticeably to its I/O load by utilizing the Amazon EBS data store’s snapshotting and letting a separate EC2 server offload the I/O of backups or anything else we want to do with the data.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Walden University Presidential Youth Debate goes live</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2008/10/walden-university-presidential-youth/"/>
      <id>https://www.endpointdev.com/blog/2008/10/walden-university-presidential-youth/</id>
      <published>2008-10-22T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;This afternoon was the launch of Walden University’s &lt;a href=&#34;http://www.youthdebate2008.org/&#34;&gt;Presidential Youth Debate&lt;/a&gt; website, which features 14 questions and video responses from Presidential candidates Barack Obama and John McCain. The video responses are about 44 minutes long overall.&lt;/p&gt;
&lt;p&gt;The site has a fairly simple feature set but is technologically interesting for us. It was developed by Dan Collis-Puro and Ethan Rowe using &lt;a href=&#34;http://radiantcms.org/&#34;&gt;Radiant&lt;/a&gt;, &lt;a href=&#34;https://www.postgresql.org/&#34;&gt;PostgreSQL&lt;/a&gt;, &lt;a href=&#34;https://www.centos.org/&#34;&gt;CentOS Linux&lt;/a&gt;, &lt;a href=&#34;https://www.rubyonrails.org/&#34;&gt;Ruby on Rails&lt;/a&gt;, &lt;a href=&#34;https://www.phusionpassenger.com/&#34;&gt;Phusion Passenger&lt;/a&gt;, &lt;a href=&#34;http://httpd.apache.org/&#34;&gt;Apache&lt;/a&gt;, &lt;a href=&#34;https://www.varnish-cache.org/&#34;&gt;Varnish&lt;/a&gt;, and &lt;a href=&#34;https://aws.amazon.com/s3/&#34;&gt;Amazon S3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nice work, guys!&lt;/p&gt;

      </content>
    </entry>
  
</feed>
