AWS for M&E Blog

Creating dynamic MediaPackage origin mappings using Amazon CloudFront and AWS Lambda@Edge

Customers taking advantage of the elastic, on demand nature of the AWS Media Services have the ability to spin up and tear down live streaming pipelines on demand and on an event by event basis. Entire media pipelines can be deployed from a template, for a single live event. Once the event is complete the pipeline can be torn down.

From this approach, the need arises to dynamically register and de-register per event origin endpoints to content delivery networks (CDNs) such as Amazon CloudFront. As event based origins will be constantly deployed and removed they cannot be statically configured on the CDN.

Using a single CloudFront distribution for multiple live events with dynamic origins has several advantages. Whilst it is a soft quota, by default AWS accounts have a limit of 200 CloudFront distributions. This limit may be easily reached if every individual live event has its own distribution. Configuring viewer DNS and TLS certificates is also simplified, but above all, having a single distribution makes centralizing logging and monitoring simpler for operational teams. A single distribution can be monitored easily, compared to automating integration of logging and monitoring on a per event basis.

Updates to CloudFront distribution origin settings require a full configuration revision and deployment each time an origin changes. This means querying the latest configuration, applying updates and re-deploying the configuration. Configuration revisions take time to propagate globally and making these updates frequently as live events start and stop leads to additional operational and configuration management burden.

This post describes how AWS Lambda@Edge may be used to dynamically map to MediaPackage origins, without needing to register / deregister endpoints against a single Amazon CloudFront distribution each time a new set of event infrastructure is created or torn down.

Overview

When media streaming pipelines are spun up dynamically, new MediaPackage endpoints are created and must be configured as origins in CloudFront. MediaPackage origin domains are not predictable due to a randomized prefix in the subdomain.

To solve this issue, origins may be mapped dynamically using URL rewrites and Lambda@Edge. Rather than registering a new origin against the distribution, the Lambda function dynamically calculates which origin domain to route to on each origin request.

Architecture diagram explaining the traffic flow from viewers to edge locations, regional caches, original shield, Lambda@Edge and finally MediaPackage as an origin. As explained in the Overview section.

Lambda@Edge is a feature of Amazon CloudFront that runs your code in response to events generated by CloudFront. For more details see: https://aws.amazon.com/lambda/edge/.

The following is an example of a MediaPackage output URL for an HLS live stream:

https://f1d26a28c95485cc.mediapackage.eu-west-1.amazonaws.com/out/v1/2a6c3693e52a4c289185ae5dd530b979/index.m3u8

This origin URL must be translated to a CloudFront viewer URL. When translating this URL to a corresponding CloudFront URL, two steps are needed:

  • Take the first subdomain prefix (in this case f1d26a28c95485cc) and add it as the first element in the path.
  • Replace the origin domain (in this case fld26a28c95485.mediapackage.eu-west-1.amazonaws.com) with the CloudFront, or custom domain name (in this case d2usi89m529k6c.cloudfront.net)

This result is as follows:

https://d2usi89m529k6c.cloudfront.net/f1d26a28c95485cc/out/v1/2a6c3693e52a4c289185ae5dd530b979/index.m3u8

Note: f1d26a28c95485cc has been added to the path.

This transformation should be handled by the orchestration solution responsible for the dynamic creation of MediaPackage origin endpoints and publishing of playback URLs to content management systems. This example shows an HLS manifest, however the process is the same for DASH or Smooth Streaming manifests.

Walkthrough

This walkthrough will cover the following steps:

  • Creating a Lambda@Edge function to perform origin request rewrites.
  • Creating a CloudFront distribution with MediaPackage as an origin.
  • Creating a new MediaPackage origin to simulate a new dynamic entry.

Prerequisites

For this walkthrough, you should have the following prerequisites:

Creating a Lambda@Edge function

A Lambda@Edge function will dynamically route origin requests. For more information on Lambda@Edge see https://aws.amazon.com/lambda/edge/.

The Lambda@Edge code will re-construct the transformed viewer URL back to the required origin URL. For example, if the viewer request is for the following URL:

https://d2usi89m529k6c.cloudfront.net/f1d26a28c95485cc/out/v1/2a6c3693e52a4c289185ae5dd530b979/index.m3u8

Then the code will transform the origin URL to the following:

https://f1d26a28c95485cc.mediapackage.eu-west-1.amazonaws.com/out/v1/2a6c3693e52a4c289185ae5dd530b979/index.m3u8

Note: f1d26a28c95485cc has been extracted from the path and added to the subdomain prefix.

  1. Log in to the AWS Console and navigate to the AWS Lambda service page in the us-east-1. Note that Lambda@Edge functions run in all edge locations, but must be initially created in us-east-1.
  2. Choose Create Function. Give the function a meaningful name such as cloudfront-edge-rewrite and choose the latest available Python runtime.
  3. Choose Create function.
  4. In the Code Source window, replace with boilerplate code with the following block of code:
def lambda_handler(event, context):

    # Parse the input from the Lambda event:
    
    request = event['Records'][0]['cf']['request']
    original_domain = request['headers']['host'][0]['value']
    original_uri = request['uri']
    
    # Reformat the base domain and the URI path
    
    original_uri_split = original_uri.split('/')
    mediapackage_prefix = original_uri_split.pop(1)
    destination_domain = f'{mediapackage_prefix}.{original_domain}'
    destination_uri = '/'.join(original_uri_split)
    
    # Update the event request payload with the transformed values
    
    request['origin']['custom']['domainName'] = destination_domain
    request['headers']['host'] = [{
        'key': 'host',
        'value': destination_domain
    }]
    request['uri'] = destination_uri
    
    return request
  1. Choose Deploy.
  2. On the Configuration tab, choose Permissions in the left-hand menu. Follow the execution role link to the Lambda function execution role:
  3. Edit the role’s policy to match the following. The account ID placeholder will need to be replaced with the ID of the account you are using to deploy this solution. This will let the Lambda@Edge function log to all regions it runs in and not just us-east-1.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:*:111122223333:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:*:111122223333:log-group:/aws/lambda/*.cloudront-edge-rewrite:*"
            ]
        }
    ]
}

Edit the role’s trust policy to also allow Lambda@Edge as a service principal. Add edgelambda.amazonaws.com to the service principals list.

Creating a CloudFront distribution

A CloudFront distribution will receive requests from client devices, invoke the Lambda@Edge function and route traffic to the appropriate origin accordingly.

  1. Navigate to the CloudFront service page and choose Create Distribution.
  2. For Origin Domain, enter mediapackage.{region}.amazonaws.com, replacing the {region} subdomain with the AWS region identifier where you plan to run MediaPackage.
  3. For Protocol choose HTTPS only.
  4. Add a custom header with the name X-MediaPackage-CDNIdentifier and a randomly generated UUID v4 value. This header will be validated by the MediaPackage origins to ensure they only accept requests from our CloudFront instance. For more information see https://docs.aws.amazon.com/mediapackage/latest/ug/cdn-auth-setup.html.
  5. For Enable Origin Shield choose Yes and for Origin Shield Region choose the region where you plan to run MediaPackage.
  6. For Viewer protocol policy choose Redirect HTTP to HTTPS. For Cache Policy choose Elemental-MediaPackage.
  7. Choose Create Distribution. Take note of the ID of the distribution that has been created.

Associate the Lambda Function with CloudFront

With the Lambda@Edge function and CloudFront distribution created, they need to be associated so that CloudFront can invoke the Lambda@Edge function on each origin request.

  1. Navigate back to the Lambda function that was created at the start of the walkthrough. In the Versions tab of the Lambda interface, choose Publish new version.
  2. Choose Publish. In the version view of the function, in the Function overview section choose Add trigger.
  3. Choose CloudFront as the trigger source.
  4. For Distribution choose the distribution that was just created in the drop-down list.
  5. For CloudFront event choose Origin request. The selection means the function will be invoked for each origin request (when the requested file is not in the CDN cache). For OTT video, this is for each manifest or video segment that does not yet exist in the CloudFront cache.

Confirm the deployment and choose Add.

Testing with a MediaPackage origin

Test the solution by setting up a new MediaPackage origin and associated video streaming pipeline. There are various ways to set this up, however the Elemental MediaLive workflow wizard may be a good solution if starting from scratch. Bear in mind, it will also create its own CloudFront distribution. For more information see: https://docs.aws.amazon.com/medialive/latest/ug/wizard.html.

When setting up the origin, ensure that access control is set up to use a secret containing the same header value created when setting up the CloudFront distribution. For more information see https://docs.aws.amazon.com/mediapackage/latest/ug/cdn-auth-setup.html.

  1. Navigate to the CloudFront service console and choose the distribution that was just created. Make note of the Distribution domain name of the distribution.
  2. Navigate to the MediaPackage origin.

With these two values, the final viewer URL can be constructed. For example, with the following MediaPackage URL:

https://e087512ad0c32643.mediapackage.eu-west-1.amazonaws.com/out/v1/3ce9c61add934475b76c8cb2eb4b7c8c/index.m3u8

Extract the path and add the first subdomain prefix as the first path element, for example:

/e087512ad0c32643/out/v1/3ce9c61add934475b76c8cb2eb4b7c8c/index.m3u8

Append the path to the CloudFront distribution domain to construct the full viewer URL, for example:

https://d32kkjkv79fgqn.cloudfront.net/e087512ad0c32643/out/v1/3ce9c61add934475b76c8cb2eb4b7c8c/index.m3u8

Cleaning up

To avoid incurring future charges, delete the resources created in the previous steps, including the CloudFront distribution, Lambda@Edge functions and if applicable, the MediaPackage channel and live streaming architecture.

Scalability of the solution

Whenever Lambda@Edge is introduced to a CloudFront solution it is important to assess the overall performance and scalability of the solution, particularly for high traffic workloads such as media streaming.

The Lambda@Edge function in this solution is triggered only on origin requests. This combined with enabling Origin Shield on the CloudFront distribution helps minimise the number of times the Lambda function is invoked.

When assessing the concurrent capacity of a Lambda function, the first step is to check the execution duration. This can be achieved by checking the Function duration metric in Amazon CloudWatch:

Amazon CloudWatch line graph showing the average Lambda@Edge function duration over time. The line fluctuates between 1 millisecond and 1.5 milliseconds.

The metrics graph shows the execution time between 1 and 1.5 milliseconds. Rounding up to 2ms, a single Lambda function can theoretically handle 500 requests per second. The default Lambda quota for concurrent executions is 1000 per region, giving a total theoretical capacity of 500,000 requests per second. The default Lambda quota for concurrent executions may be increased from 1000 to “Tens of thousands” if higher concurrency is required. For more information on Lambda quotas see: https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html.

When planning for Lambda concurrency, please be aware that Lambda reserved concurrency and provisioned concurrency is not supported by Lambda@Edge.

Cost of the Solution

The pricing of CloudFront is described here: https://aws.amazon.com/cloudfront/pricing/. Note, both Data Transfer Out costs and Origin Shield Requests costs apply in this example. Origin Shield has been enabled to help reduce the number of origin requests (and so reducing the number of Lambda@Edge invocations).

Lambda@Edge has two pricing components:

  • Request pricing is $0.60 per 1 million requests ($0.0000006 per request).
  • Duration is priced at $0.00005001 for every GB-second used, metered in 1ms granularity. As the function above is sized at 128mb and executes in 2ms on average, the price per invocation is roughly $0.0000000125025 per origin request.

Taking the total of requests + duration gives each Lambda@Edge invocation (for each origin request) a total cost of $0.0000006125025.

Calculating the number of origin requests is based on number of factors. Origin requests will be made for each media segment and manifest updates when there is a cache miss. As such, the adaptive bitrate segment length of the media affects how often origin requests are made. It is worthwhile to measure the number of origin requests on your origin to calculate the cost of the solution.

As an example, in an environment where 100 requests per second are made to the origin, the total Lambda@Edge cost for a 3 hour event would be:

100 (requests per second) x 3600 (seconds in 1 hour) x 3 (hours) x $0.0000006125025 (cost per origin request) = $0.66

Conclusion

In this blog post, we have demonstrated how Lambda@Edge may be used to perform path-based origin routing, allowing for dynamic adding and removal of event based streaming origins.

To learn more about how to build broadcast and direct-to-consumer solutions at scale, check out the AWS Learning Plan and Digital Badge for Media and Entertainment (M&E) for Direct-to-Consumer (D2C) and Broadcast Foundations.

Andrew Lee

Andrew Lee

Andrew is a Senior Media Cloud Architect with AWS Professional Services based in Amsterdam, The Netherlands.