Ditching WordPress for Jekyll - Part 3 - Automating CDN push

WordPress   Jekyll   AWS

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.

CloudFront Automatic Cache Invalidation Setup and Workflow

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": "*"
        }
    ]
}

References