Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add container optimizations #147

Merged
merged 9 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/aca/99-optimize-containers/Backend.Api.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 5000

ENV ASPNETCORE_URLS=http://+:5000
simonkurtz-MSFT marked this conversation as resolved.
Show resolved Hide resolved

USER app
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG configuration=Release
WORKDIR /src
COPY ["TasksTracker.TasksManager.Backend.Api/TasksTracker.TasksManager.Backend.Api.csproj", "TasksTracker.TasksManager.Backend.Api/"]
RUN dotnet restore "TasksTracker.TasksManager.Backend.Api/TasksTracker.TasksManager.Backend.Api.csproj"
COPY . .
WORKDIR "/src/TasksTracker.TasksManager.Backend.Api"
RUN dotnet build "TasksTracker.TasksManager.Backend.Api.csproj" -c $configuration -o /app/build

FROM build AS publish
ARG configuration=Release
RUN dotnet publish "TasksTracker.TasksManager.Backend.Api.csproj" -c $configuration -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "TasksTracker.TasksManager.Backend.Api.dll"]
24 changes: 24 additions & 0 deletions docs/aca/99-optimize-containers/Backend.Api.Dockerfile.chiseled
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled AS base
WORKDIR /app
EXPOSE 5000

ENV ASPNETCORE_URLS=http://+:5000

USER app
FROM mcr.microsoft.com/dotnet/sdk:8.0-jammy AS build
ARG configuration=Release
WORKDIR /src
COPY ["TasksTracker.TasksManager.Backend.Api/TasksTracker.TasksManager.Backend.Api.csproj", "TasksTracker.TasksManager.Backend.Api/"]
RUN dotnet restore "TasksTracker.TasksManager.Backend.Api/TasksTracker.TasksManager.Backend.Api.csproj"
COPY . .
WORKDIR "/src/TasksTracker.TasksManager.Backend.Api"
RUN dotnet build "TasksTracker.TasksManager.Backend.Api.csproj" -c $configuration -o /app/build

FROM build AS publish
ARG configuration=Release
RUN dotnet publish "TasksTracker.TasksManager.Backend.Api.csproj" -c $configuration -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "TasksTracker.TasksManager.Backend.Api.dll"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM mcr.microsoft.com/dotnet/nightly/runtime-deps:8.0-jammy-chiseled-aot AS base
WORKDIR /app
EXPOSE 5000

ENV ASPNETCORE_URLS=http://+:5000

USER app
FROM mcr.microsoft.com/dotnet/nightly/sdk:8.0-jammy-aot AS build
ARG configuration=Release
WORKDIR /src
COPY ["TasksTracker.TasksManager.Backend.Api/TasksTracker.TasksManager.Backend.Api.csproj", "TasksTracker.TasksManager.Backend.Api/"]
RUN dotnet restore "TasksTracker.TasksManager.Backend.Api/TasksTracker.TasksManager.Backend.Api.csproj"
COPY . .
WORKDIR "/src/TasksTracker.TasksManager.Backend.Api"
RUN dotnet build "TasksTracker.TasksManager.Backend.Api.csproj" -c $configuration -o /app/build

FROM build AS publish
ARG configuration=Release
RUN dotnet publish "TasksTracker.TasksManager.Backend.Api.csproj" -c $configuration -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "TasksTracker.TasksManager.Backend.Api.dll"]
158 changes: 158 additions & 0 deletions docs/aca/99-optimize-containers/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---
canonical_url: 'https://azure.github.io/aca-dotnet-workshop'
---

# Module 99 - Container Optimization

!!! info "Module Duration"
45 minutes

## Objective

In this module, we will accomplish two objectives:

1. Learn how to reduce container footprints.
1. Build & deploy updated, optimized images to Azure.

## Module Sections

--8<-- "snippets/restore-variables.md"

### 1. Optimizing Containers

Azure Container Apps makes it simply to quickly become effective with containers. But even a managed container platform requires hygiene and can benefit greatly from smaller containers.

In this module, we will look into the benefits of optimized containers such as:

- Smaller images to store and transfer to and from the container registry.
- Potentially less *Common Vulnerabilities and Exposures (CVEs)*.
- No bloat and unnecessary components such as shells, package managers, etc.

While available prior to .NET 8, the general availability introduction of .NET 8 in November 2023 came with an expanded focus on container optimization and a move away from general-purpose containers.

Please ensure you have the Docker daemon ready. Running *Docker Desktop* does it.

#### 1.1 The Status Quo

Let's focus on our first project, the Backend API. This is an ASP.NET Core application.

Our original `Dockerfile` looks like this:

=== "Backend.Api Dockerfile"
```Dockerfile
--8<-- "docs/aca/99-optimize-containers/Backend.Api.Dockerfile"
```

```shell
cd ~\TasksTracker.ContainerApps
```

```shell
docker build -t backend-api-status-quo -f .\TasksTracker.TasksManager.Backend.Api\Dockerfile .

docker image list
```

This yields a sizable image at **222 MB**!

![Backend API Status Quo](../../assets/images/99-optimize-containers/backend-api-status-quo.png)

This image is comprised of two images, 452 packages, and has 19 vulnerabilities.

![Backend API Status Quo Image Stats](../../assets/images/99-optimize-containers/backend-api-status-quo-image-stats.png)

#### 1.2. Chiseled Images

Microsoft and Ubuntu's creator, Canonical, collaborated on the concept of a [chiseled image for .NET](https://learn.microsoft.com/en-us/dotnet/core/docker/container-images#scenario-based-images){target=_blank}. Take a general-purpose base image and start chiseling away until you are left with an image that contains nothing more than the bare necessities to run your workload. No shell, no package manager, no bloat.

=== "Backend.Api Dockerfile.chiseled"
```Dockerfile hl_lines="1 8"
--8<-- "docs/aca/99-optimize-containers/Backend.Api.Dockerfile.chiseled"
```

Create a new file, `Dockerfile.chiseled` in the Backend Api root directory, then build the image again:

```shell
docker build -t backend-api-chiseled -f .\TasksTracker.TasksManager.Backend.Api\Dockerfile.chiseled .

docker image list
```

Our image now stands at a much smaller **115 MB** - a drop of 107 MB and a size just 51.8% of the status quo image!

![Backend API Chiseled](../../assets/images/99-optimize-containers/backend-api-chiseled.png)

This image is comprised of one image, 331 packages, and has five vulnerabilities.

![Backend API Status Quo Image Stats](../../assets/images/99-optimize-containers/backend-api-chiseled-image-stats.png)

#### 1.3 Ahead-of-time (AOT) Compilation

[Ahead-of-time (AOT) compilation](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot){target_blank} was first introduced with .NET 7. AOT compiles the application to native code instead of Intermediate Language (IL). This means that we must have foresight as to what platform will be hosting the application. Our process is simplified by the fact that containers in Azure Container Apps are only Linux-hosted. By using native code, we will bypass the just-in-time (JIT) compiler when the container executes, which means we will have faster startup and a smaller memory footprint. It also means these images can run in environments where JIT compilation may not be permitted.

=== "Backend.Api Dockerfile.chiseled.aot"
```Dockerfile hl_lines="1 8"
--8<-- "docs/aca/99-optimize-containers/Backend.Api.Dockerfile.chiseled.aot"
```

Create a new file, `Dockerfile.chiseled.aot` in the Backend Api root directory, then build the image again:

```shell
docker build -t backend-api-chiseled-aot -f .\TasksTracker.TasksManager.Backend.Api\Dockerfile.chiseled.aot .

docker image list
```

!!! note "Nightly Images"

As the name implies, these images are produced nightly. They are not yet images that are versioned and stable in the registry. Your mileage may vary.

Another massive reduction takes the image down to a mere **16 MB** - a total drop of 206 MB and a size just 7.2% of the status quo image!

![Backend API Chiseled AOT](../../assets/images/99-optimize-containers/backend-api-chiseled-aot.png)

This image is comprised of one image, just 23 packages, and has nine vulnerabilities.
Notably, the four additional vulnerabilities are in the `openssl 3.0.2` package in this image.

![Backend API Status Quo Image Stats](../../assets/images/99-optimize-containers/backend-api-chiseled-aot-image-stats.png)

#### 1.4 Deploying the new Status Quo

While the image is vastly reduced, what hasn't changed is the functionality of the API. Whether you are executing it locally or deploying to Azure, the Backend API will continue to function as it always has. However, now it has less vulnerabilities, less time to transfer from the registry, less startup time, and less of a memory footprint. Furthermore, 16 MB is the uncompressed image. With compression, we are likely to continue dropping in size.

Let's update our existing Backend API container app with a new build and revision:

```shell hl_lines="6"
## Build Backend Service on ACR and Push to ACR

az acr build `
--registry $AZURE_CONTAINER_REGISTRY_NAME `
--image "tasksmanager/$BACKEND_API_NAME" `
--file 'TasksTracker.TasksManager.Backend.Api/Dockerfile.chiseled.aot' .

# Update Backend API App container app and create a new revision
az containerapp update `
--name $BACKEND_API_NAME `
--resource-group $RESOURCE_GROUP `
--revision-suffix v$TODAY-6 `
--set-env-vars "ApplicationInsights__InstrumentationKey=secretref:appinsights-key"
```

Verify that the application continues to work:

```shell
$FRONTEND_UI_BASE_URL
```

#### 2. Wash/Rinse/Repeat for Frontend UI & Backend Service

As all three projects use ASP.NET Core, you can follow this same exercise for the other two projects and see how much you are able to reduce!

--8<-- "snippets/persist-state.md:module99"

## Review

In this module, we have accomplished two objectives:

1. Learned how to reduce container footprints.
1. Built & deployed updated, optimized images to Azure.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ nav:
- Deploy Infrastructure using Bicep via GitHub Actions: aca/10-aca-iac-bicep/ci-cd-git-action.md
- Deploy infrastructure using Azure Devops Pipeline: aca/10-aca-iac-bicep/ci-cd-azdo.md
- Module 11 - Integration with Azure Container Apps landing zone accelerator: aca/11-aca-landing-zone/index.md
- Module 99 - Optimize Containers: aca/99-optimize-containers/index.md
- About The Authors: aca/12-about-the-authors/index.md
- Appendix:
- Debug and Launch Dapr Applications in VSCode: aca/13-appendix/01-run-debug-dapr-app-vscode.md
Expand Down
13 changes: 12 additions & 1 deletion snippets/persist-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,15 @@
git commit -m "Add Module 8"
```

--8<-- [end:module8]
--8<-- [end:module8]

--8<-- [start:module99]

- Navigate to the root and persist the module to Git.

```shell
git add .
git commit -m "Add Module 99"
```

--8<-- [end:module99]