Sivamuthu Kumar
Sivamuthu Kumar's Blog

Sivamuthu Kumar's Blog

Deploy .NET 6 API to AWS App Runner using AWS Copilot CLI

Deploy .NET 6 API to AWS App Runner using AWS Copilot CLI

Sivamuthu Kumar's photo
Sivamuthu Kumar

Published on Oct 30, 2021

10 min read

Subscribe to my newsletter and never miss my upcoming articles

In this tutorial blog post, we are going to see how to deploy .NET 6 API to AWS App Runner using AWS Copilot CLI.

What is App Runner?

AWS App Runner is a fully managed service that makes it easy for developers to quickly deploy containerized web applications and APIs at scale and with no prior infrastructure experience required. Start with your source code or a container image. App Runner automatically builds and deploys the web application, and load balances traffic with encryption. App Runner also scales up or down automatically to meet your traffic needs. With App Runner, you have more time to focus on your applications rather than thinking about servers or scaling.

Untitled.png

App Runner Features

The features of App Runner are

  1. Autoscaling: starting and stopping as demand changes, between configurable min and max limits.
  2. Load balancing: the service includes a transparent, non-configurable load balancer. The URL can be pointed to the custom domain.
  3. SSL & Certificates: the deployed services will have HTTPS endpoints for applications with AWS-managed certificates. The certificates will be renewed when it's about to expire.
  4. Build service: you can push your own images or let AWS build them for you from code.

AWS Copilot CLI

The AWS Copilot CLI is a tool for developers to build, release and operate production-ready containerized applications on AWS App Runner, Amazon ECS, and AWS Fargate. From getting started, pushing to staging, and releasing to production, Copilot can help manage the entire lifecycle of your application development.

Setup a .NET 6 Minimal API

Let's get started. This section will create minimal API services using .NET and dockerize the application to deploy in AWS AppRunner using Copilot CLI.

  • Create a new web API project using dotnet

      dotnet new web -n CoffeeService
    
  • The API code is like below that's by default generated by the template.

      var builder = WebApplication.CreateBuilder(args);
      var app = builder.Build();
    
      app.MapGet("/", () => "Hello World!");
    
      app.Run();
    

That's cool on seeing this minimal API (like how it's easy as developing Node.js code). Let's run locally and see whether we are getting this "Hello World" response.

  • Run the code

      cd CoffeeService && dotnet run
    
  • Execute the URL and check the response

      ➜  ~  curl http://localhost:5023/
      Hello World!
    

Containerize the API

Create a file named Dockerfile in the directory containing the .csproj and open it in a text editor. Copy the contents below in the Dockerfile. In this Dockerfile, we are building the project and running it in the aspnet:6.0 runtime image.

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["CoffeeService.csproj", "./"]
RUN dotnet restore "CoffeeService.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "CoffeeService.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "CoffeeService.csproj" -c Release -o /app/publish

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 5023
ENV ASPNETCORE_URLS=http://+:5023

WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "CoffeeService.dll"]
  • Build the docker image

      docker build . -t coffee-service:latest
    
  • Run the docker image

      docker run -p 5023:5023 coffee-service:latest
    
  • Execute the URL and check the response

      ➜  ~  curl http://localhost:5023/
      Hello World!
    

Great. The .NET 6 minimal API is created and run from local and in the docker container. Now let's deploy into the AWS AppRunner using AWS Copilot CLI

Deploy into AWS AppRunner using AWS Copilot CLI

Run copilot init to set up the AWS AppRunner application for this service. The initialization tool is going to ask you the questions.

  1. Use existing application or create a new application
  2. Application name
  3. Workload type

    You can choose one of the workload types. In this demo, we are setting up the AWS App Runner.

    • Request-Driven Web Service - (App Runner)
    • Load Balanced Web Service - (Internet to ECS on Fargate)
    • Backend Service - (ECS on Fargate)
    • Worker Service - (Events to SQS to ECS on Fargate)
    • Scheduled Job - (Scheduled event to State Machine to Fargate)
  4. Service name and path of the Dockerfile to build
  5. Once the ECR is set up, you can set up the environment to deploy.
Welcome to the Copilot CLI! We're going to walk you through some questions
to help you get set up with a containerized application on AWS. An application is a collection of
containerized services that operate together.

Use existing application: No
Application name: coffee-shop
Workload type: Request-Driven Web Service
Service name: coffee-service
Dockerfile: CoffeeService/Dockerfile
Ok great, we'll set up a Request-Driven Web Service named coffee-service in application coffee-shop listening on port 5023.

✔ Created the infrastructure to manage services and jobs under application coffee-shop..

✔ The directory copilot will hold service manifests for application coffee-shop.

✔ Wrote the manifest for service coffee-service at copilot/coffee-service/manifest.yml
Your manifest contains configurations like your container size and port (:5023).

✔ Created ECR repositories for service coffee-service..

All right, you're all set for local development.
Deploy: Yes

✔ Linked account 495775103319 and region us-east-1 to application coffee-shop..

✔ Proposing infrastructure changes for the coffee-shop-test environment.
- Creating the infrastructure for the coffee-shop-test environment.      [create complete]  [79.6s]
  - An IAM Role for AWS CloudFormation to manage resources               [create complete]  [16.2s]
  - An ECS cluster to group your services                                [create complete]  [9.0s]
  - Enable long ARN formats for the authenticated AWS principal          [create complete]  [4.6s]
  - An IAM Role to describe resources in your environment                [create complete]  [13.9s]
  - A security group to allow your containers to talk to each other      [create complete]  [5.9s]
  - An Internet Gateway to connect to the public internet                [create complete]  [16.1s]
  - Private subnet 1 for resources with no internet access               [create complete]  [19.3s]
  - Private subnet 2 for resources with no internet access               [create complete]  [19.6s]
  - Public subnet 1 for resources that can access the internet           [create complete]  [19.6s]
  - Public subnet 2 for resources that can access the internet           [create complete]  [19.6s]
  - A Virtual Private Cloud to control networking of your AWS resources  [create complete]  [16.1s]
✔ Created environment test in region us-east-1 under application coffee-shop.
Environment test is already on the latest version v1.6.1, skip upgrade.
[+] Building 0.7s (18/18) FINISHED
 => [internal] load build definition from Dockerfile                                                                                      0.0s
 => => transferring dockerfile: 588B                                                                                                      0.0s
 => [internal] load .dockerignore                                                                                                         0.0s
 => => transferring context: 374B                                                                                                         0.0s
 => [internal] load metadata for mcr.microsoft.com/dotnet/aspnet:6.0                                                                      0.5s
 => [internal] load metadata for mcr.microsoft.com/dotnet/sdk:6.0                                                                         0.5s
 => [internal] load build context                                                                                                         0.0s
 => => transferring context: 1.63kB                                                                                                       0.0s
 => [build 1/7] FROM mcr.microsoft.com/dotnet/sdk:6.0@sha256:96ce062b7e664999048b86198385fea1ddaff31d8d2ab5f7c42c0077678afeac             0.0s
 => [base 1/4] FROM mcr.microsoft.com/dotnet/aspnet:6.0@sha256:ed9b7dc3e8278a56be619b278762689565e1e21f61da51551fe028dc1d3a536f           0.0s
 => CACHED [base 2/4] WORKDIR /app                                                                                                        0.0s
 => CACHED [base 3/4] WORKDIR /app                                                                                                        0.0s
 => CACHED [build 2/7] WORKDIR /src                                                                                                       0.0s
 => CACHED [build 3/7] COPY [CoffeeService.csproj, ./]                                                                                    0.0s
 => CACHED [build 4/7] RUN dotnet restore "CoffeeService.csproj"                                                                          0.0s
 => CACHED [build 5/7] COPY . .                                                                                                           0.0s
 => CACHED [build 6/7] WORKDIR /src/.                                                                                                     0.0s
 => CACHED [build 7/7] RUN dotnet build "CoffeeService.csproj" -c Release -o /app/build                                                   0.0s
 => CACHED [publish 1/1] RUN dotnet publish "CoffeeService.csproj" -c Release -o /app/publish                                             0.0s
 => CACHED [base 4/4] COPY --from=publish /app/publish .                                                                                  0.0s
 => exporting to image                                                                                                                    0.0s
 => => exporting layers                                                                                                                   0.0s
 => => writing image sha256:74811373b140080ac1cf2f55c1e15a8dcdd9059f9a82d98a97a4f94a4e40e559                                              0.0s
 => => naming to 495775103319.dkr.ecr.us-east-1.amazonaws.com/coffee-shop/coffee-service                                                  0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Login Succeeded
Using default tag: latest
The push refers to repository [495775103319.dkr.ecr.us-east-1.amazonaws.com/coffee-shop/coffee-service]
8cb51c566055: Pushed
...
e8b689711f21: Pushed
latest: digest: sha256:daeea40ed5b765e0ca27232964c529f3d6bb18eb8ccced8828a39c3153991c6a size: 1993
✔ Proposing infrastructure changes for stack coffee-shop-test-coffee-service
- Creating the infrastructure for stack coffee-shop-test-coffee-service           [create complete]    [246.5s]
  - An IAM Role for App Runner to use on your behalf to pull your image from ECR  [create complete]    [10.2s]
  - An IAM role to control permissions for the containers in your service         [create in progress]  [238.4s]
  - An App Runner service to run and manage your containers                       [create complete]    [225.4s]
✔ Deployed service coffee-service.
Recommended follow-up action:
    You can access your service at https://ptydisq8gp.us-east-1.awsapprunner.com over the internet.

Access the service at the URL.

➜ curl https://ptydisq8gp.us-east-1.awsapprunner.com
Hello World!

Copilot is doing the heavy lifting for us - the developers. So we can focus on the application. Copilot will build, push, and launch your container on AWS to ECS, Fargate, and AppRunner.

Setting up Automated Pipeline

The key principle of the devops process is "Ship small, Ship often." The process of deploying small features on a regular cadence is crucial to DevOps. As the team becomes more agile, we need to automate application releases as multiple developers; multiple services teams push the code into the source code repository.

AWS Copilot tool can help you in setting up the pipeline to automate application releases. You can run these commands to create an automated pipeline that builds and deploys the application on git push.

aws-apprunner-dotnet6-demo git:(main)  copilot pipeline init
1st stage: test
Repository URL: git@github.com:ksivamuthu/aws-apprunner-coffee-shop
✔ Wrote the pipeline manifest for aws-apprunner-coffee-shop at 'copilot/pipeline.yml'
The manifest contains configurations for your CodePipeline resources, such as your pipeline stages and build steps.
Update the file to add additional stages, change the branch to be tracked, or add test commands or manual approval actions.
✔ Wrote the buildspec for the pipeline's build stage at 'copilot/buildspec.yml'
The buildspec contains the commands to build and push your container images to your ECR repositories.
Update the build phase to unit test your services before pushing the images.

Required follow-up actions:
- Commit and push the buildspec.yml, pipeline.yml, and .workspace files of your copilot directory to your repository.
- Run `copilot pipeline update` to create your pipeline.

The copilot buildspec, pipeline yaml files are committed and pushed. The code build pipelines are set up to deploy automatically in the test environment.

Untitled 1.png

Now, it's ready to bring more developers to start rocking. They don't need to install a copilot to deploy the machine's service as they are developing. The deployment steps are automated.

Let's push more code to see changes that get automatically deployed.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello AWS AppRunner !!");
app.MapGet("/api/coffee", () => new List<dynamic> {
    new { CoffeeId = "cappucino", CoffeeName = "Cappucino" },
    new { CoffeeId = "latte", CoffeeName = "Latte" },
    new { CoffeeId = "mocha", CoffeeName = "Mocha" },
    new { CoffeeId = "americano", CoffeeName = "Americano" },
    new { CoffeeId = "macchiato", CoffeeName = "Macchiato" },
    new { CoffeeId = "frappe", CoffeeName = "Frappe" },
    new { CoffeeId = "corretto", CoffeeName = "Corretto" },
    new { CoffeeId = "affogato", CoffeeName = "Affogato" },
    new { CoffeeId = "filtercoffee", CoffeeName = "Filter Coffee" },
});

app.Run();
➜ aws-apprunner-dotnet6-demo git:(main) curl https://ptydisq8gp.us-east-1.awsapprunner.com
Hello AWS AppRunner !!

➜ aws-apprunner-dotnet6-demo git:(main) curl -s https://ptydisq8gp.us-east-1.awsapprunner.com/api/coffee | jq '.[0]'
{
  "coffeeId": "cappucino",
  "coffeeName": "Cappucino"
}

Test the Autoscaling

AWS App Runner automatically scales compute resources (instances) up or down for your App Runner application. Automatic scaling provides adequate request handling when incoming traffic is high and reduces your cost when traffic slows down. You can configure a few parameters to adjust autoscaling behavior for your service.

  • Settings – Here's what you can configure:

    • Max concurrency – The maximum number of concurrent requests that an instance processes. When the number of concurrent requests exceeds this quota, App Runner scales up the service.
    • Max size – The maximum number of instances that your service scales up to. At most this number of instances are actively serving traffic for your service.
    • Min size – The minimum number of instances that App Runner provisions for your service. The service always has at least this number of provisioned instances. Some of them actively serve traffic. The rest of them (provisioned and inactive instances) stand by as a cost-effective compute capacity reserve, which is ready to be quickly activated. You pay for the memory usage of all provisioned instances. You pay for the CPU usage of only the active subset.

      App Runner temporarily doubles the number of provisioned instances during deployments to maintain the same old and new code capacity.

Untitled 2.png

Let's create load testing with 100 concurrent requests and check the number of instances.

hey -z 1m -c 100 https://ptydisq8gp.us-east-1.awsapprunner.com/api/coffee

Untitled 3.png

You can see the "active instances" count get increased based on the concurrent requests for autoscaling.

Conclusion

AWS App Runner is a fully managed service that makes it easy for developers to quickly deploy containerized web applications and APIs at scale. It provides seamless "code-to-deploy" workflow for Node.js and Python runtime today and other runtimes using Dockerfile. Copilot also helps you to have your own customizable pipelines with just a few commands for your apps running AWS AppRunner.

I presented a talk on AWS App Runners at the IndyAWS meetup. This session covers

  • AWS App Runner's features,
  • The key challenges AWS App Runner solves,
  • How to configure and integrate AWS App Runner with your source control to deploy your code in seconds
  • Live demo using AWS Copilot CLI, a tool that supports AWS App Runner.
  • Want a deeper dive? Download the presentation slides and youtube video of the talk

I'm Siva - working as Sr. Software Architect at Computer Enterprises Inc from Orlando. I'm an AWS Community builder, Auth0 Ambassador and I am going to write a lot about Cloud, Containers, IoT, and Devops. If you are interested in any of that, make sure to follow me if you haven’t already. Please follow me @ksivamuthu Twitter or check out my blogs at blog.sivamuthukumar.com!

Did you find this article valuable?

Support Sivamuthu Kumar by becoming a sponsor. Any amount is appreciated!

Learn more about Hashnode Sponsors
 
Share this
Proudly part of