Ditching WordPress for Jekyll - Part 3 - Automating CDN push
In Part 2 we talked about deploying the Jekyll site to AWS, and how to update a Jekyll site. Now it’s time to talk about automation. The first thing I chose to automate is the need to create a cache invalidation in CloudFront so that the updated site content would propagate across the CDN.
Creating an invalidation manually in the AWS console or via the AWS CLI is a pretty simple procedure. Automating this step is a bit more involved, but really not too bad.
:exclamation: This workflow results in a Lambda invocation and an SNS Topic notification for every modified file. We will fix it in the next iteration of the workflow.
This is a callout. It can be used to make an aside without disrupting the flow of the main document.
The Process
Turn on S3 object-level logging
- Configure a CloudTrail to log events from the S3 bucket
- Confirm event logging in CloudWatch
Forward S3 ‘PutObject’ events to an SNS topic
- Create an SNS topic ‘S3PutObjectEvents’
- Create an EventBridge rule to foward events to ‘S3PutObjectEvents’ SNS topic
Create a CloudFront invalidation
- Create a Lambda function called ‘CreateInvalidatoinFn’ with Python boto3 code
- Create an Execution Role to allow Lambbda to perform ‘createInvalidation’‘opeartion against CloudFront
- Attach an Inline Policy to allow Lambda access to certain CloudFront Operations
- Configure ‘CreateInvalidatoinFn’ to be triggered by ‘S3PutObjectEvents’ SNS topic
Create a notification channel
- Create an SNS topic ‘CreateInvalidationNotify’
- Configure ‘CreateInvalidatoinFn’ Lambda function to send its return to the ‘CreateInvalidationNotify’ SNS topic
- Subscribe to the topic to receive an e-mail notification of a successful invalidation
Code Snippets
EventBridge Rule - Event Pattern
{
"source": ["aws.s3"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventSource": ["s3.amazonaws.com"],
"eventName": ["PutObject"],
"requestParameters": {
"bucketName": ["yourS3bucket"]
}
}
}
Lambda Boto3 Python Code
Timestamp is used to uniquely identify an invocation.
import json
import boto3
import datetime
distrId = "yourCfDistrId"
timestamp = str(datetime.datetime.now())
def lambda_handler(event, context):
client = boto3.client('cloudfront')
response = client.create_invalidation(
DistributionId=distrId,
InvalidationBatch={
'Paths': {
'Quantity': 1,
'Items': ['/*']},
'CallerReference': timestamp
}
)
return {
'statusCode': 200,
'body': json.dumps(f"Created invalidation {timestamp} on {distrId}")
}
Execution Role Inline Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"cloudfront:GetDistribution",
"cloudfront:ListInvalidations",
"cloudfront:ListDistributions",
"cloudfront:GetInvalidation",
"cloudfront:CreateInvalidation"
],
"Resource": "*"
}
]
}