Skip to content

CDN Architecture

This document summarizes requirements, architecture, environments, and use cases of the Central CDN (content delivery network).

Requirements

The requirements for the Central CDN are:

  • It must be the central entry point for all Statista 4.0-based systems.
  • It must have an event-driven architecture with no manual steps.
  • It must be easy for new slices and their entry points to be added to the CDN.
  • It must have a high availability, 99.9999…%.

The only manual steps around the CDN may be the creation of pull requests and their approvals when it comes to a new slice being added to the Central CDN.

Basics: Requests and Responses

The following figure shows the overall architecture regarding the basic request/response handling:

graph LR

  user@{ label: "User", constraint: 'on', h: 48 }

  subgraph "CDN Account"
    waf@{ icon: "logos:aws-waf", label: 'WAF', h: 48 }
    cfn@{ icon: "logos:aws-cloudfront", label: 'Cloudfront', h: 48 }
  end

  subgraph "Account 1"
    app1@{ icon: "logos:aws-lambda", label: 'App 1 (Lambda)', h: 48 }
  end

  subgraph "Account 2"
    app2@{ icon: "logos:aws-fargate", label: 'App 2 (Fargate)', h: 48 }
  end

  user <-- Request/Response --> waf <--> cfn <-- Request/Response --> app1 & app2

The Resources Explained

CloudFront and Entry Points

The Central CDN is the central entry point for all requests that shall hit Statista 4.0 applications.

“Behind” the central entry point there exist the various application entry points. These are the first contacts of the systems that are owned by Statista’s app owners. We use AWS CloudFront to implement the central CDN. The CloudFront distribution manages the requests and thereby takes care of the caching of responses and the forwarding to the entry points.

From CloudFront's point of view, the application entry points are origins. We expect the origins to be mostly HTTP servers or Lambda function URLs, but theoretically an origin can also be an S3 bucket, a MediaStore container, a MediaPackage channel, or an Application Load Balancer.

As an additional security layer, e.g. to shield against DDoS attacks, a Web Application Firewall (WAF) web ACL is associated with the CloudFront distribution.

How It Works

Requests and Responses

When a client - usually a browser or an application - sends a request to the CloudFront distribution URL, this is what happens:

  1. The DNS system resolves the URL to an IP address that points to the nearest CloudFront edge location.
  2. The edge location checks if the content is already in its cache.
    • If that is the case (“cache hit”), the request can immediately be answered without forwarding it to an application entry point.
  3. In the case of a “cache miss”, the edge location forwards the request to the appropriate origin, i.e. one of the defined application entry points, based on the cache behavior settings.
  4. The application computes a response which is sent back as the response to the edge location.
  5. The edge location forwards the response to the client.

The CloudFront distribution adds HSTS security headers to the response that is sent back to the client (see also securityheaders.com). CORS headers stay in the ownership of the slices as these know best which backend URLs their frontends need to access.

Architecture Enhanced: Application Updates

The next figure is an enhancement of the one before, additionally showing those resources that are necessary for the handling of updates that result from application deployments:

sequenceDiagram
    participant Application Repository

    create participant Deployment
    Application Repository ->> Deployment: triggers
    Deployment ->> CDN Parameter Store: writes entry-point configuration
    create participant CDN Deployment
    CDN Parameter Store ->> CDN Deployment: triggers
    CDN Deployment ->> CDN Parameter Store: read
    activate CDN Parameter Store
    CDN Parameter Store -->> CDN Deployment: returns
    deactivate CDN Parameter Store
    CDN Deployment -->> Cloudformation: updates origins

The Resources Explained

Parameter Store

Data around the central CDN is stored in the AWS Systems Manager Parameter Store. It is the place to store configuration, especially for the origins, i.e., the end points of the applications.

Lambda Function

The Lambda function deploymentTrigger is triggered via an EventBridge rule whenever an entry in the Parameter Store changes. It updates the CloudFront distribution by triggering the CDK-based workflow that adds all currently known origins to the CloudFront distribution. This causes a re-deployment of the CloudFront distribution, using the updated value of the entry in the Parameter Store.

CDK Code and GitHub

The complete environment is created using AWS CDK which resides in a specific GitHub repo called central-cdn. GitHub workflows are used to initially create the environment and to update the CloudFront distribution whenever necessary.

The CDK code is capable of creating environments in different AWS accounts, e.g. stage and prod.

How It Works

Version update coming from one of the slices

Workflow

The deployment workflow of each application runs a standardized set of steps to inform the central CDN about a new release or even about its initial existence. These steps are:

  1. Write the value of a parameter in the Parameter Store of the central CDN AWS account. The key of that parameter is /sliceOrigins/<sliceName>/<systemName>, the value is the exact origin, e.g. the URL of the system entry point. (Using this structure of parameter keys makes it easy to define new unique keys even if a slice owns more than one application. Also, it is easy for the algorithm that updates the CloudFront distribution to collect all origins.)
  2. An EventBridge rule defines that whenever a value of a parameter whose name starts with /sliceOrigins is modified, the Lambda function deploymentTrigger is triggered.
  3. The deploymentTrigger starts a predefined workflow in the GitHub repo central-cdn by sending a corresponding POST request to the GitHub API.
  4. The workflow in GitHub runs a CDK application.
    1. That application reads all entries that are located in the Parameter Store under /sliceOrigins and defines a CloudFront origin for each entry. It then (re-)deploys the complete CDN stack, including the CloudFront distribution.
    2. The (re-)deployment enters all origin entries in the CloudFront distribution and triggers a deployment of the CloudFront distribution.

Any newly defined or modified slice entry points are now reachable from “outside”.

Permissions

In order for the above steps to work, several permissions need to be granted.

  • The application owner’s deployment workflow gets write permissions to all entries of the Parameter Store whose keys start with /sliceOrigins/<sliceName>/. (Since the Parameter Store entries reside in the CDN's AWS account which is different from the slice’s AWS account, the deployment workflow needs to assume a role that is created during the approval process for this write permission. See section 'Approvals for a new application” on more details how this is done.)
  • The deploymentTrigger Lambda function needs to have permissions to run the workflow in the central-cdn repo. For that, we define a GitHub App with a small amount of permissions and save the app’s private key and ID in the Parameter Store. When being triggered, the Lambda creates a JWT, using the private key and the ID of the GitHub App. It sends a REST call to GitHub that requests a short-lived app installation access token and gets back such a token. Then it uses this installation access token to access the GitHub API. The advantage of this way is that short-lived tokens are used to authenticate against the GitHub API.
  • The workflow in the central-cdn repo gets permissions to read all /sliceOrigins/ entries and to create and modify all AWS resources of the central CDN stack. (This can be done in the CDK code that deploys the central CDN stack. There will be one initial manual step necessary to enable the GitHub workflow to write into the central CDN’s AWS accounts, though.)

Discussion

How can we buffer multiple requests from different slices coming within a short time frame? Queuing? Does the deploymentTrigger need to read from a queue?

Possible solution: The GitHub deployment workflow shall only start once (using grouping). Even if the deploymentTrigger Lambda requests more workflow starts, this is ignored, and a new workflow starts only if the previous one is finished. This new workflow then takes into account all requests that meanwhile have accumulated.

New Slice/Team Workflow

The following figure shows the handling of new slices being added to the overall system:

sequenceDiagram
    actor App Team
    actor CDN Team
    participant GitHub Workflow
    participant CDN AWS Account

    create participant Pull Request
    App Team -->> Pull Request: creates in central-cdn repo
    Note right of Pull Request: "config.json<br />[{"basePath": "/app/"}]"
    CDN Team -->> Pull Request: merges
    Pull Request -->> GitHub Workflow: triggers
    GitHub Workflow -->> CDN AWS Account: creates roles

How It Works

Approval for a new slice

When a Statista slice or “app owner” starts the development of their first Statista 4.0 application, they need to register once to the central CDN. This is done as follows:

  1. A responsible person from the slice creates a branch in the PIT-Shared/central-cdn repo, branching from the latest main version.
  2. In that branch, the application’s (unique) base path and the slice’s AWS account ID are added to a specific JSON file within the directory origin-config.
  3. The application’s responsible person creates a pull request.
  4. The pull request is checked and approved by the core team of the central CDN.
  5. The pull request is merged to main by the core team.
  6. Triggered by this merge/push event, a GitHub workflow creates a specific AWS IAM role in the CDN’s AWS account for each JSON file origin-config directory. This role allows the slice's AWS account to write to the entry of the Parameter Store whose name is given in the JSON config file.

Once these steps have been performed, the slice can run their standardized workflow that comes with the Statista 4.0 Remix code. That workflow assumes the role that was created in the last step above and is thereby granted the necessary permissions.

The GitHub workflow that creates these IAM roles in turn has to have the permission to do so.

Environments and Deployment

Types of Environments

We should distinguish between AWS accounts and environments: We have two AWS accounts, stage and prod in which the environments stage and prod run, respectively. So far, so easy.

In addition, it may be helpful to have temporary development environments that have the same technical setup as stage and prod. Even if it may not often be necessary and many changes can be applied directly to the stage environment, at least it should be possible to have additional dev environments. Experience from previous projects shows that it is a good idea to aim at having dev environments. And when this is taken into account early in the development, it is almost no additional effort.

These dev environments run in the AWS stage account.

Source Code

All source code that is necessary to deploy and run the central CDN is held in the repo central-cdn in Statista’s GitHub organization PIT-Shared.

Branches and Workflows

Only the main branch is relevant for the environments stage and prod: Whenever a pull request is merged to main, this triggers a GitHub workflow that creates a new stage release. At defined points in time - tbd, e.g. once per day - a GitHub cron job triggers a workflow that releases the current state of the main branch to prod.

Other branches exist during development. All branches are finally merged to the main branch.

Backups

All data in the Parameter Store will be backed up. This is done using a Lambda function in prod that reads all data in prod (using AWS SDK) and writes it into a JSON file. The JSON file gets encrypted, and the encrypted file is put into an S3 bucket in a different AWS account. This account could be stage or a completely different AWS account. The same is done with the Parameter Store values in stage.