Deploying a Single Page Application (SPA) on AWS: A Beginner's Guide. Part 7. AWS App Runner
Tags: aws, serverless, containers, restapi
Published: 2023-12-04 07:25:01

## Introduction

In this part of the series, the focus is on running your backend application in a production environment. The goal is to provide the necessary tooling to fulfil the Observable, Seamlessly Updatable, and Failure-Tolerant criteria of a Cloud-Ready App, as described in the FROSST criteria from the [Cloud Strategy - A Decision-based Approach to Successful Cloud Migration - The Architect Elevator](https://architectelevator.com/book/cloudstrategy/) Book.

## Before CloudFormation (CFN) Template

Before diving into the CloudFormation template, it's essential to set up observability. This can be done by creating an observability configuration using the AWS CLI:

```bash
aws apprunner create-observability-configuration --observability-configuration-name rest-api --trace-configuration Vendor=AWSXRAY

```

After creating the configuration, retrieve the ARN from the `ObservabilityConfigurationArn` field in the output, as shown in the JSON snippet:

```json
{
    "ObservabilityConfiguration": {
        "ObservabilityConfigurationArn": "arn:aws:apprunner:eu-central-1:1111111111:observabilityconfiguration/rest-api/1/xxxxxx",
        "ObservabilityConfigurationName": "rest-api",
        "TraceConfiguration": {
            "Vendor": "AWSXRAY"
        },
        "ObservabilityConfigurationRevision": 1,
        "Latest": true,
        "Status": "ACTIVE",
        "CreatedAt": "2023-10-12T19:11:23.425000+02:00"
    }
}

```

## App Runner CloudFormation (CFN) Template to Support Failure Tolerance

Let's dive into the CloudFormation template:

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: Backend deployed viaAWS App Runner
Parameters:
  Environment:
    Type: String
    Default: production
    Description: A name for the environment that this cloudformation will be part of.
                 Used to locate other resources in the same environment.
  ImageUrl:
    Type: String
    Description: The url of a docker image that contains the application process that
                 will handle the traffic for this service. Should be on ECR private
  ObservabilityConfigurationArnParam:
    Type: String
    Description: Create it via aws cli aws apprunner create-observability-configuration --observability-configuration-name rest-api --trace-configuration Vendor=AWSXRAY

Resources: 
  # Backend
  AppRunnerRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2008-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - build.apprunner.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess

  Backend:
    Type: AWS::AppRunner::Service
    Properties:
      ServiceName: !Sub ${AWS::StackName}-restapi-${Environment}
      ObservabilityConfiguration:
        ObservabilityEnabled: true
        ObservabilityConfigurationArn: !Ref ObservabilityConfigurationArnParam
      InstanceConfiguration:
        Cpu: 0.25 vCPU
        Memory: 0.5 GB
      SourceConfiguration:
        AuthenticationConfiguration:
          AccessRoleArn: !GetAtt AppRunnerRole.Arn
        ImageRepository:
          ImageRepositoryType: ECR
          ImageIdentifier: !Ref ImageUrl
          ImageConfiguration:
            Port: 8080
      HealthCheckConfiguration:
          HealthyThreshold: 1
          Interval: 10
          Path: /api/greeting
          Protocol: HTTP
          Timeout: 5
          UnhealthyThreshold: 5
      Tags:
        - Key: environment
          Value: !Ref Environment

  # Api Gateway
  ApiGatewayRestApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: !Sub ${AWS::StackName}-gateway-${Environment}
  ApiGatewayResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      ParentId:
        !GetAtt ApiGatewayRestApi.RootResourceId
      PathPart: '{proxy+}'
      RestApiId:
        !Ref ApiGatewayRestApi
  ApiGatewayMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: ANY
      ResourceId:
        !Ref ApiGatewayResource
      RestApiId:
        !Ref ApiGatewayRestApi
      AuthorizationType: NONE
      RequestParameters:
        method.request.path.proxy: true
      Integration:
        RequestParameters:
          integration.request.path.proxy: method.request.path.proxy
        Type: HTTP_PROXY
        IntegrationHttpMethod: ANY
        Uri: !Sub "https://${Backend.ServiceUrl}/{proxy}"
  ApiGatewayDeployment:
    Type: 'AWS::ApiGateway::Deployment'
    DependsOn:
      - ApiGatewayMethod
    Properties:
      RestApiId:
        !Ref ApiGatewayRestApi
      StageName: !Ref Environment

Outputs:
  AppRunnerServiceArn:
    Description: AppRunnerServiceArn
    Value: !GetAtt Backend.ServiceArn
  AppRunnerServiceId:
    Description: AppRunnerServiceId
    Value: !GetAtt Backend.ServiceId
  AppRunnerServiceUrl:
    Description: AppRunnerServiceUrl
    Value: !GetAtt Backend.ServiceUrl
  IntegrationAPI:
    Description: URL For CDN
    Value: !Sub "https://${ApiGatewayRestApi}.execute-api.${AWS::Region}.amazonaws.com/${Environment}"

```

This AWS CloudFormation template defines the infrastructure for deploying a backend application using AWS App Runner and exposing it via AWS API Gateway. Let's break down its key components:

### Parameters

- `Environment`: Specifies the name of the environment (e.g., production or development) that this CloudFormation stack is part of.

- `ImageUrl`: The URL of a Docker image containing the application process to handle traffic. This image should be stored in a private Amazon ECR registry.

- `ObservabilityConfigurationArnParam`: ARN for the observability configuration, which can be created using the AWS CLI.

### Resources

- **AppRunnerRole (IAM Role)**: This role allows the App Runner service to assume it and interact with other AWS services. It also attaches the necessary managed policy to access ECR.

- **Backend (AWS App Runner Service)**: Defines the App Runner service, specifying its name, observability configuration, instance configuration (CPU and memory), source configuration (image repository details), health check configuration, and tags.

- **ApiGatewayRestApi (API Gateway REST API)**: Creates the entry point for the API Gateway.

- **ApiGatewayResource (API Gateway Resource)**: Configures a wildcard proxy resource to forward all requests to the App Runner service.

- **ApiGatewayMethod (API Gateway Method)**: Defines a method that allows any HTTP method and integrates with the backend via HTTP\_PROXY.

- **ApiGatewayDeployment (API Gateway Deployment)**: Deploys the API Gateway to a specified stage, making it accessible externally.

### Outputs

- **AppRunnerServiceArn** : The ARN of the App Runner service.

- **AppRunnerServiceId** : The ID of the App Runner service.

- **AppRunnerServiceUrl** : The URL of the App Runner service.

- **IntegrationAPI** : The external URL for accessing the API through the API Gateway.

This template sets up a backend service using AWS App Runner, utilizing a Docker image from ECR, and configures an API Gateway to handle and forward HTTP requests to this backend. It also supports observability through AWS X-Ray and offers outputs for accessing both the backend service and the API Gateway endpoint. Parameterizing the environment allows for different configurations for various deployment stages.

## Logs, Monitoring, and Tracing to Support Observability

For comprehensive observability, you can access data about your backend by visiting the App Runner console. Here, you can view logs, monitor metrics, and trace information.

## Seamlessly Updatable

To make updates to your backend, follow these steps:

1. Modify the code by adding the word "new" to a string template in your application.
```java
    @RestController
    public class GreetingController {
        private static final String template = "Hello, new %s!";
```

2. Build the backend:
 ```bash
    $ ./gradlew bootBuildImage --imageName=spring-rest-api
```

3. Publish the new version to the private Elastic Container Registry (ECR):
 ```bash
    aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin 11111111.dkr.ecr.eu-central-1.amazonaws.com
    docker tag spring-rest-api:latest 111111111.dkr.ecr.eu-central-1.amazonaws.com/ias:spring-rest-api
    docker push 111111111.dkr.ecr.eu-central-1.amazonaws.com/ias:spring-rest-api
```

4. Trigger an App Runner update:
 ```bash
    aws apprunner start-deployment --service-arn arn:aws:apprunner:eu-central-1:1111111111:service/apprunner-restapi-production/2222222222222222222
```

You can check for logs in the App Runner console.

After querying the API Gateway endpoint, you should receive a "new" response:

```bash
curl https://xxxxxxxxxxx.execute-api.eu-central-1.amazonaws.com/production/api/greeting

```

Response:

```json
{"id":22,"content":"Hello, new World!"}

```

## Summary

This article has provided a comprehensive guide to deploying a backend application using AWS App Runner and exposing it through AWS API Gateway. It covered the creation of an AWS CloudFormation template, which defines the essential infrastructure, parameters, and resources. The template ensures observability through AWS X-Ray and supports seamless updates by demonstrating code changes and deployment triggers using a sample Spring Boot application.