Graduate Program KB

AWS Workshop 05

Before you begin

We'll be using your AWS Sandbox accounts for this workshop.

  1. Make sure you have a an sso-session configuration section in your ~/.aws/config:
[sso-session j1]
sso_start_url = https://j1.awsapps.com/start
sso_region = ap-southeast-2
sso_registration_scopes = sso:account:access

If you don't, then you'll need to configure your sso-session section with the aws configure sso-session wizard:

aws configure sso
SSO session name: j1
SSO start URL [None]: https://j1.awsapps.com/start
SSO region [None]: ap-southeast-2
SSO registration scopes [None]: sso:account:access
  1. Next, we'll create and SSO linked developer profile for your sandbox account
aws configure sso
SSO session name (Recommended): j1

When prompted: - Choose your sandbox account from the list - Next, choose the SandboxDeveloper role - Set the CLI default client Region to ap-southeast-2 - CLI default output format to json

  1. Update the cli_pager setting in the pf-sandbox-developer profile:
aws configure set cli_pager "" --profile pf-sandbox-developer
  1. Verify that you have a [profile pf-sandbox-developer] section in your ~/.aws/config file:
grep -n -A6 "[profile pf-sandbox-developer]" ~/.aws/config
[profile pf-sandbox-developer]
sso_session = j1
sso_account_id = YOUR_SANDBOX_ACCOUNT_ID
sso_role_name = SandboxDeveloper
region = ap-southeast-2
output = json
cli_pager =
  1. Add an alias to your ~/.bash_aliases to log you in to your AWS sandbox account using your pf-sandbox-developer profile:
echo "alias pfsbd='aws sso login --profile pf-sandbox-developer'" >> ~/.bash_aliases
source ~/.bash_aliases
  1. If you've previously completed steps 1 and 2, refresh your pf-sandbox-developer and pf-sandbox-admin token:
pfsbd
Attempting to automatically open the SSO authorization page in your default browser.
If the browser does not open or you wish to use a different device to authorize this request, open the following URL:

https://device.sso.ap-southeast-2.amazonaws.com/

Then enter the code:

ABCD-1234
Successfully logged into Start URL: https://j1.awsapps.com/start
  1. Verify that you can access resources in your AWS sandbox account using the pf-sandbox-developer profile:
aws ec2 describe-vpcs --profile pf-sandbox-developer --query Vpcs[0].VpcId
"vpc-xxxxxxxxxxxxxxx"

We'll be using the pf-sandbox-developer profile for the remainder of the workshop.

  1. Install jq
sudo apt-get install jq
  1. Repeat steps 2-7 and set up a pf-sandbox-admin profile for your Product Factory Sandbox Administrator role.

CI/CD in the cloud

What is CI/CD?

Imagine a Pez dispenser as your application. The Pez candy represents the different pieces of code, or features, that make up the application.

Continuous Integration (CI) is like regularly loading new Pez candy (code) into the dispenser (application). Instead of waiting to load a whole bunch of candy at once, you add them in one at a time, as soon as they're ready. This practice allows you to catch any 'candy' that doesn't fit well (bugs in the code) quickly and easily, since you're only adding one piece at a time. It's much easier to spot a problem when you're only looking at one new piece, rather than dozens or hundreds.

Continuous Deployment (CD) is like having a system that automatically pushes a piece of candy out of the dispenser whenever a new one is loaded in. This means that any time you add a new piece of code (candy), it's immediately deployed to the live application (dispenser), and available for use.

Together, CI/CD forms a process that allows developers to release new features and fixes quickly, reliably, and with less risk of introducing bugs. It enables rapid, incremental updates rather than large, infrequent ones.

In terms of Amazon Web Services (AWS), they offer several tools that support CI/CD.

For CI, there's AWS CodeBuild, which compiles source code, runs tests, and produces software packages that are ready to deploy.

For CD, there's AWS CodePipeline, which automates the build, test, and deployment processes, allowing you to quickly release new features.

There's also AWS CodeDeploy, which automates application deployments to different kinds of compute infrastructure such as Amazon EC2, AWS Fargate, AWS Lambda, and instances running on-premises.

These services are designed to work together to create a fully automated CI/CD platform. Just like a well-oiled Pez dispenser, you can continuously load in new candy and have it dispensed efficiently and effectively.

Workshop Overview

In this workshop we'll learn how to build and deploy the front-end of a multi-layered application on the AWS platform.

Our application will eventually be composed of three services:

1 - A front-end web application built with React 2 - A HTTP API built with Express 3 - A Mongo database instance

In this workshop we'll be focussing on the front-end application. In the next workshop, we'll turn our attention to the HTTP API and the database.

Front End Application

Let's create a simple React application that fetches the jokes from our created API.

Step 1: Set Up Your React App

  1. First, make sure you have Node.js and npm (node package manager) installed on your computer. If you don't have them, you can download and install from here.

  2. Install create-react-app, a tool that sets up a new React project with a single command. You can install it globally with this command:

npm install -g create-react-app
  1. Create a new React app. Let's call it "jokes-client". Run this command:
npx create-react-app jokes-client
  1. Navigate to the new jokes-client directory:
cd jokes-client

Step 2: Install Axios

We will use axios to make HTTP requests from our React app. To install it, run:

npm install axios

Step 3: Create a Jokes Component

  1. Create a new file in your src folder and name it "Joke.js".

  2. Open "Joke.js" in your text editor and add the following code:

import React, { useState, useEffect } from "react";
import axios from "axios";

const Joke = () => {
  const [joke, setJoke] = useState("");

  useEffect(() => {
    axios
      .get(process.env.JOKE_SERVICE_URL)
      .then((response) => {
        setJoke(response.data);
      })
      .catch((error) => {
        setJoke("Joke is on you, the joke server is unavailable. Please try again later!");
      });
  }, []);

  return (
    <div>
      <h1>Joke of the Day</h1>
      <p>{joke}</p>
    </div>
  );
};

export default Joke;

This component uses the useEffect hook to fetch a joke from our API when the component is first rendered. It stores the fetched joke in state using the useState hook, and then displays the joke in the component's return statement.

Step 4: Use the Joke Component in Your App

  1. Open "App.js" in your text editor and replace its contents with the following:
import React from "react";
import Joke from "./Joke";

function App() {
  return (
    <div className="App">
      <Joke />
    </div>
  );
}

export default App;

This code imports our Joke component and uses it in the App component.

Now, when you start your React app with npm start and navigate to http://localhost:3000 in your browser, you should see a random JavaScript-themed joke that is fetched from the Express API you created earlier.

Make sure your Express server is running and that you've set the process.env.JOKE_SERVICE_URL environment variable to point to your Express server, so the React app can fetch jokes from it!

Infrastructure

You'll now be guided through the process of creating a CloudFormation template, a JSON- or YAML-formatted text file that describes a set of AWS resources and properties. The template in focus creates a frontend infrastructure for an the workshop, including an AWS CodeCommit repository, an S3 bucket for the website, an IAM role for AWS CodeBuild, a CodeBuild project, a CloudWatch Event rule, and a CloudFront distribution. Each resource will be explained in detail, including its purpose and configuration.

Workshop Steps

Step 1: Define the AWS Template Format Version and Description

Here, we begin by defining the AWS CloudFormation template format version and a brief description of what the stack does.

AWSTemplateFormatVersion: "2010-09-09"
Description: AWS Workshop 05 Front End Infrastructure
Step 2: Define the Parameters

This parameter allows users to pass values into the stack during runtime. In this case, we use it to accept the CodeCommit profile.

Parameters:
  CodeCommitProfileParameter:
    Type: String
    Default: pf-sandbox-developer
Step 3: Create a CodeCommit Repository

This AWS::CodeCommit::Repository resource creates an AWS CodeCommit repository named AWSWorkshop05FrontEnd.

Resources:
  Repository:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: AWSWorkshop05FrontEnd
      RepositoryDescription: Front End Application for AWS Workshop 05
Step 4: Create an S3 Bucket

This AWS::S3::Bucket resource creates an S3 bucket that will host the static website files. The bucket's name includes the AWS region and account ID.

WebsiteBucket:
  Type: "AWS::S3::Bucket"
  Properties:
    BucketName: !Sub aws-workshop05-front-end-${AWS::Region}-${AWS::AccountId}
    WebsiteConfiguration:
      IndexDocument: index.html
      ErrorDocument: error.html
Step 5: Create an IAM Role for CodeBuild

The AWS::IAM::Role resource creates an IAM role that AWS CodeBuild can assume to carry out builds. The role has necessary permissions to retrieve the source code, upload build artifacts to the S3 bucket, and write logs to CloudWatch Logs.

CodeBuildServiceRole:
  Type: "AWS::IAM::Role"
  Properties:
    AssumeRolePolicyDocument:
      Version: "2012-10-17"
      Statement:
        - Effect: Allow
          Principal:
            Service: codebuild.amazonaws.com
          Action: "sts:AssumeRole"
    Path: /
    Policies:
      - PolicyName: CodeBuildServiceRolePolicy
        PolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Action:
                - "s3:PutObject"
                - "s3:GetObject"
                - "s3:ListBucket"
              Resource:
                - !Sub "arn:aws:s3:::${WebsiteBucket}"
                - !Sub "arn:aws:s3:::${WebsiteBucket}/*"
            - Effect: Allow
              Action: "codecommit:GitPull"
              Resource: !GetAtt Repository.Arn
            - Effect: Allow
              Action:
                - "logs:CreateLogGroup"
                - "logs:CreateLogStream"
                - "logs:PutLogEvents"
              Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:aws-workshop05-front-end-code-build-project:*"
Step 6: Create a CodeBuild Project

This AWS::CodeBuild::Project resource creates a CodeBuild project. It uses the IAM role from the previous step, specifies the build environment details, and defines the source repository and build specification.

CodeBuildProject:
  Type: "AWS::CodeBuild::Project"
  Properties:
    Name: AWSWorkshop05FrontEnd
    Description: CodeBuild project to build and deploy React application
    ServiceRole: !GetAtt CodeBuildServiceRole.Arn
    Artifacts:
      Type: "NO_ARTIFACTS"
    Environment:
      Type: LINUX_CONTAINER
      ComputeType: BUILD_GENERAL1_SMALL
      Image: "aws/codebuild/amazonlinux2-x86_64-standard:5.0"
      EnvironmentVariables:
        - Name: "BUCKET_NAME"
          Value: !Ref WebsiteBucket
    Source:
      Type: CODECOMMIT
      Location: !GetAtt Repository.CloneUrlHttp
      BuildSpec: "buildspec.yaml"
    LogsConfig:
      CloudWatchLogs:
        GroupName: "aws-workshop05-front-end-code-build-project"
        Status: "ENABLED"
Step 7: Create an IAM Role for CloudWatch Events

This AWS::IAM::Role resource creates an IAM role that CloudWatch Events can assume to trigger the CodeBuild project. This role has permissions to start a build in CodeBuild.

AmazonCloudWatchEventRole:
  Type: AWS::IAM::Role
  Properties:
    AssumeRolePolicyDocument:
      Version: 2012-10-17
      Statement:
        - Effect: Allow
          Principal:
            Service:
              - events.amazonaws.com
          Action: sts:AssumeRole
    Path: /
    Policies:
      - PolicyName: cwe-codebuild-execution
        PolicyDocument:
          Version: 2012-10-17
          Statement:
            - Effect: Allow
              Action: codebuild:StartBuild
              Resource: !GetAtt CodeBuildProject.Arn
Step 8: Create a CloudWatch Event Rule

This AWS::Events::Rule resource creates a CloudWatch Events rule that monitors the CodeCommit repository and triggers the CodeBuild project when a commit is made to the master branch.

AmazonCloudWatchEventRule:
  Type: AWS::Events::Rule
  Properties:
    EventPattern:
      source:
        - aws.codecommit
      detail-type:
        - "CodeCommit Repository State Change"
      resources:
        - !GetAtt Repository.Arn
      detail:
        event:
          - referenceCreated
          - referenceUpdated
        referenceType:
          - branch
        referenceName:
          - master
    Targets:
      - Arn: !GetAtt CodeBuildProject.Arn
        RoleArn: !GetAtt AmazonCloudWatchEventRole.Arn
        Id: codebuild-project
        InputTransformer:
          InputTemplate: '{"projectName": "AWSWorkshop05FrontEnd"}'
Step 9: Create an S3 Bucket Policy

This AWS::S3::BucketPolicy resource creates an S3 bucket policy that allows CloudFront to access objects in the S3 bucket.

  WebsiteBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref WebsiteBucket
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId
            Action:
              - s3:GetObject
            Resource: !Sub "

arn:aws:s3:::${WebsiteBucket}/*"
Step 10: Create a CloudFront Origin Access Identity

This AWS::CloudFront::CloudFrontOriginAccessIdentity resource creates a CloudFront Origin Access Identity that will be used to enable secure access to the S3 bucket from the CloudFront distribution.

CloudFrontOriginAccessIdentity:
  Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
  Properties:
    CloudFrontOriginAccessIdentityConfig:
      Comment: !Sub "Origin Access Identity for ${AWS::StackName}"
Step 11: Create a CloudFront Distribution

This AWS::CloudFront::Distribution resource creates a CloudFront distribution that caches and serves the static website files from the S3 bucket.

CloudFrontDistribution:
  Type: AWS::CloudFront::Distribution
  Properties:
    DistributionConfig:
      Comment: !Sub "CloudFront distribution for ${AWS::StackName}"
      Enabled: true
      DefaultRootObject: index.html
      Origins:
        - Id: S3CustomOrigin
          DomainName: !GetAtt WebsiteBucket.DomainName
          S3OriginConfig:
            OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}"
      DefaultCacheBehavior:
        TargetOriginId: S3CustomOrigin
        ViewerProtocolPolicy: redirect-to-https
        AllowedMethods:
          - GET
          - HEAD
          - OPTIONS
        CachedMethods:
          - GET
          - HEAD
        Compress: true
        ForwardedValues:
          QueryString: false
          Cookies:
            Forward: none
      PriceClass: PriceClass_100
      ViewerCertificate:
        CloudFrontDefaultCertificate: true
      Restrictions:
        GeoRestriction:
          RestrictionType: none
Step 12: Define Outputs

Finally, we define a set of outputs to expose certain stack values. In this case, we expose the CloudFront distribution domain name, the repository URL, and the S3 bucket website URL.

Outputs:
  AWSWorkshop05FrontEndCloudFrontDistributionDomain:
    Description: AWS workshop 05 CloudFront Distribution Domain Name
    Value: !GetAtt CloudFrontDistribution.DomainName
  AWSWorkshop05FrontEndRepoGrcUrl:
    Description: AWS Workshop 05 Repository GRC URL
    Value: !Sub
      - "codecommit::${AWS::Region}://${CodeCommitProfile}@AWSWorkshop05FrontEnd"
      - CodeCommitProfile: !Ref CodeCommitProfileParameter
  AWSWorkshop05FrontEndBuckUrl:
    Description: AWS workshop 05 S3 Bucket Website URL
    Value: !GetAtt
      - WebsiteBucket
      - WebsiteURL
Step 13: Deploy the infrastructure

To deploy the stack run the following command:

aws cloudformation deploy  --template-file infrastructure.yaml --stack-name aws-workshop05-front-end-infra --profile pf-sandbox-developer;

This CloudFormation template will allow you to automate the deployment of your frontend infrastructure, with your code residing in CodeCommit, building and deploying through CodeBuild to an S3 bucket, and then being distributed via CloudFront. You've also set up logging and event monitoring through CloudWatch. Well done!