AWS Workshop 05
Before you begin
We'll be using your AWS Sandbox accounts for this workshop.
- 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
- 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
- Update the
cli_pager
setting in thepf-sandbox-developer
profile:
aws configure set cli_pager "" --profile pf-sandbox-developer
- 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 =
- Add an alias to your
~/.bash_aliases
to log you in to your AWS sandbox account using yourpf-sandbox-developer
profile:
echo "alias pfsbd='aws sso login --profile pf-sandbox-developer'" >> ~/.bash_aliases
source ~/.bash_aliases
- If you've previously completed steps 1 and 2, refresh your
pf-sandbox-developer
andpf-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
- 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.
- Install
jq
sudo apt-get install jq
- 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
-
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.
-
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
- Create a new React app. Let's call it "jokes-client". Run this command:
npx create-react-app jokes-client
- 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
-
Create a new file in your
src
folder and name it "Joke.js". -
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
- 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!