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 basic HTTP health check #4352

Merged
merged 21 commits into from
Feb 10, 2023
Merged
Show file tree
Hide file tree
Changes from 18 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
10 changes: 5 additions & 5 deletions README.samples.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,31 +61,31 @@ See [Hosting ASP.NET Core Images with Docker over HTTPS](https://github.com/dotn
Tags | Dockerfile | OS Version
-----------| -------------| -------------
dotnetapp-alpine-slim-amd64, dotnetapp, latest | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/dotnetapp/Dockerfile.alpine-x64-slim) | Alpine
aspnetapp-alpine-slim-amd64, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.alpine-x64-slim) | Alpine
aspnetapp-alpine-slim-amd64, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.alpine-slim) | Alpine

## Linux arm32 Tags
Tags | Dockerfile | OS Version
-----------| -------------| -------------
dotnetapp-alpine-slim-arm32v7, dotnetapp, latest | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/dotnetapp/Dockerfile.alpine-arm32-slim) | Alpine
aspnetapp-alpine-slim-arm32v7, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.alpine-arm32-slim) | Alpine
aspnetapp-alpine-slim-arm32v7, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.alpine-slim) | Alpine

## Linux arm64 Tags
Tags | Dockerfile | OS Version
-----------| -------------| -------------
dotnetapp-alpine-slim-arm64v8, dotnetapp, latest | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/dotnetapp/Dockerfile.alpine-arm64-slim) | Alpine
aspnetapp-alpine-slim-arm64v8, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.alpine-arm64-slim) | Alpine
aspnetapp-alpine-slim-arm64v8, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.alpine-slim) | Alpine

## Nano Server 2022 amd64 Tags
Tag | Dockerfile
---------| ---------------
dotnetapp-nanoserver-ltsc2022, dotnetapp, latest | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/dotnetapp/Dockerfile)
aspnetapp-nanoserver-ltsc2022, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile)
aspnetapp-nanoserver-ltsc2022, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.nanoserver-slim)

## Nano Server, version 1809 amd64 Tags
Tag | Dockerfile
---------| ---------------
dotnetapp-nanoserver-1809, dotnetapp, latest | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/dotnetapp/Dockerfile)
aspnetapp-nanoserver-1809, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile)
aspnetapp-nanoserver-1809, aspnetapp | [Dockerfile](https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.nanoserver-slim)

You can retrieve a list of all available tags for dotnet/samples at https://mcr.microsoft.com/v2/dotnet/samples/tags/list.
<!--End of generated tags-->
Expand Down
10 changes: 5 additions & 5 deletions manifest.samples.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
},
"platforms": [
{
"dockerfile": "samples/aspnetapp/Dockerfile.alpine-x64-slim",
"dockerfile": "samples/aspnetapp/Dockerfile.alpine-slim",
"os": "linux",
"osVersion": "alpine",
"tags": {
Expand All @@ -108,7 +108,7 @@
},
{
"architecture": "arm",
"dockerfile": "samples/aspnetapp/Dockerfile.alpine-arm32-slim",
"dockerfile": "samples/aspnetapp/Dockerfile.alpine-slim",
"os": "linux",
"osVersion": "alpine",
"tags": {
Expand All @@ -127,7 +127,7 @@
},
{
"architecture": "arm64",
"dockerfile": "samples/aspnetapp/Dockerfile.alpine-arm64-slim",
"dockerfile": "samples/aspnetapp/Dockerfile.alpine-slim",
"os": "linux",
"osVersion": "alpine",
"tags": {
Expand All @@ -145,7 +145,7 @@
]
},
{
"dockerfile": "samples/aspnetapp",
"dockerfile": "samples/aspnetapp/Dockerfile.nanoserver-slim",
"os": "windows",
"osVersion": "nanoserver-1809",
"tags": {
Expand All @@ -166,7 +166,7 @@
]
},
{
"dockerfile": "samples/aspnetapp",
"dockerfile": "samples/aspnetapp/Dockerfile.nanoserver-slim",
"os": "windows",
"osVersion": "nanoserver-ltsc2022",
"tags": {
Expand Down
30 changes: 30 additions & 0 deletions samples/aspnetapp/Dockerfile.alpine-slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:7.0-alpine AS build
WORKDIR /source

# copy csproj and restore as distinct layers
COPY aspnetapp/*.csproj .
RUN dotnet restore --use-current-runtime /p:PublishReadyToRun=true

# copy everything else and build app
COPY aspnetapp/. .
RUN dotnet publish -c Release -o /app --use-current-runtime --no-restore /p:PublishTrimmed=true /p:PublishReadyToRun=true /p:PublishSingleFile=true

# final stage/image
FROM mcr.microsoft.com/dotnet/runtime-deps:7.0-alpine
WORKDIR /app
COPY --from=build /app .

# This port needs to match the port being used
richlander marked this conversation as resolved.
Show resolved Hide resolved
HEALTHCHECK CMD wget -qO- -t1 http://localhost:80/healthz || exit 1
ENTRYPOINT ["./aspnetapp"]

# See: https://github.com/dotnet/announcements/issues/20
# Uncomment to enable globalization APIs (or delete)
# ENV \
# DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \
# LC_ALL=en_US.UTF-8 \
# LANG=en_US.UTF-8
# RUN apk add --no-cache \
# icu-data-full \
# icu-libs
26 changes: 26 additions & 0 deletions samples/aspnetapp/Dockerfile.nanoserver-slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# escape=`

# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /source

# copy csproj and restore as distinct layers
COPY aspnetapp/*.csproj .
RUN dotnet restore --use-current-runtime /p:PublishReadyToRun=true

# copy everything else and build app
COPY aspnetapp/. .
RUN dotnet publish -c Release -o /app --use-current-runtime --no-restore /p:PublishTrimmed=true /p:PublishReadyToRun=true /p:PublishSingleFile=true

# final stage/image
FROM mcr.microsoft.com/windows/nanoserver:ltsc2022
WORKDIR /app
COPY --from=build /app .
HEALTHCHECK CMD curl -sf --show-error http://localhost:80/healthz || exit 1
ENV `
# Configure web servers to bind to port 80 when present
ASPNETCORE_URLS=http://+:80 `
# Enable detection of running in a container
DOTNET_RUNNING_IN_CONTAINER=true

ENTRYPOINT ["aspnetapp"]
121 changes: 99 additions & 22 deletions samples/aspnetapp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,26 @@ If want to skip ahead, you can try a pre-built version with the following comman
docker run --rm -it -p 8000:80 mcr.microsoft.com/dotnet/samples:aspnetapp
```

You can also call an endpoint that the app exposes:

```bash
$ curl http://localhost:8000/Environment
{"runtimeVersion":".NET 7.0.2","osVersion":"Linux 5.15.79.1-microsoft-standard-WSL2 #1 SMP Wed Nov 23 01:01:46 UTC 2022","osArchitecture":"X64","user":"root","processorCount":16,"totalAvailableMemoryBytes":67430023168,"memoryLimit":9223372036854771712,"memoryUsage":100577280}
```

You can see the app running via `docker ps`.

```bash
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d79edc6bfcb6 mcr.microsoft.com/dotnet/samples:aspnetapp "./aspnetapp" 35 seconds ago Up 34 seconds (healthy) 0.0.0.0:8080->80/tcp nice_curran
```

You may notice that the sample includes a [health check](https://docs.docker.com/engine/reference/builder/#healthcheck), which is indicated in the "STATUS" column.

## Build an ASP.NET Core image

You can build and run a .NET-based container image using the following instructions:
You can build and run an image using the following instructions:

```console
docker build --pull -t aspnetapp .
Expand All @@ -33,10 +50,10 @@ Now listening on: http://[::]:80
Application started. Press Ctrl+C to shut down.
```

After the application starts, navigate to `http://localhost:8000` in your web browser.

> Note: The `-p` argument maps port 8000 on your local machine to port 80 in the container (the form of the port mapping is `host:container`). See the [Docker run reference](https://docs.docker.com/engine/reference/commandline/run/) for more information on command-line parameters. In some cases, you might see an error because the host port you select is already in use. Choose a different port in that case.

After the application starts, navigate to `http://localhost:8000` in your web browser.

You can also view the ASP.NET Core site running in the container on another machine. This is particularly useful if you are wanting to view an application running on an ARM device like a Raspberry Pi on your network. In that scenario, you might view the site at a local IP address such as `http://192.168.1.18:8000`.

In production, you will typically start your container with `docker run -d`. This argument starts the container as a service, without any console interaction. You then interact with it through other Docker commands or APIs exposed by the containerized application.
Expand All @@ -45,35 +62,64 @@ We recommend that you do not use `--rm` in production. It cleans up container re

> Note: See [Establishing docker environment](../establishing-docker-environment.md) for more information on correctly configuring Dockerfiles and `docker build` commands.

## Build an image for Windows Nano Server
## Build an image with `HEALTHCHECK`

The following example demonstrates targeting Windows Nano Server (x64) explicitly (you must have [Windows containers enabled](https://docs.docker.com/docker-for-windows/#switch-between-windows-and-linux-containers)):
The sample uses [ASP.NET Core Health Check middleware](https://learn.microsoft.com/aspnet/core/host-and-deploy/health-checks). You can direct Docker, Kubernetes, or other systems to use the ASP.NET Core `healthz` endpoint.

```console
docker build --pull -t aspnetapp:nanoserver -f Dockerfile.nanoserver-x64 .
docker run --rm -it -p 8000:80 aspnetapp:nanoserver
```
The [`HEALTHCHECK`](https://docs.docker.com/engine/reference/builder/#healthcheck) directive is implemented in the [`Dockerfile.alpine-slim`](Dockerfile.alpine-slim) and [`Dockerfile.nanoserver`](Dockerfile.nanoserver-slim). You can build those via the same pattern.

You can view in the app in your browser in the same way as demonstrated earlier.
```bash
$ docker build --pull -t aspnetapp -f Dockerfile.alpine-slim .
$ docker run --rm -it -p 8000:80 aspnetapp
```

You can use `docker images` to see the images you've built:
In another terminal:

```console
> docker images aspnetapp
REPOSITORY TAG IMAGE ID CREATED SIZE
aspnetapp latest b2f0ecb7bdf9 About an hour ago 353MB
aspnetapp nanoserver d4b7586827f2 About an hour ago 353MB
```bash
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b143cf4ac0d1 aspnetapp "./aspnetapp" 8 seconds ago Up 7 seconds (health: starting) 0.0.0.0:8000->80/tcp fervent_lichterman
```

## Build an image for Windows Server Core
After a while, you will see that the app is no longer "starting".
richlander marked this conversation as resolved.
Show resolved Hide resolved

You can also look at health status with `docker inspect`. The following pattern uses `jq`, which makes it much easier to drill in on the interesting data.

```bash
$ docker inspect b143cf4ac0d1 | jq .[-1].State.Health
richlander marked this conversation as resolved.
Show resolved Hide resolved
{
"Status": "healthy",
"FailingStreak": 0,
"Log": [
{
"Start": "2023-01-26T23:39:06.424631566Z",
"End": "2023-01-26T23:39:06.589344994Z",
"ExitCode": 0,
"Output": "Healthy"
},
{
"Start": "2023-01-26T23:39:36.597795818Z",
"End": "2023-01-26T23:39:36.70857373Z",
"ExitCode": 0,
"Output": "Healthy"
}
]
}
```

The instructions for Windows Server Core are very similar to Windows Nano Server. There are three different sample Dockerfile files provided for Windows Server Core, which can all be used with the same approach as the Nano Server ones.
The same thing can be accomplished with PowerShell.

In addition, one of the samples enables using IIS as the Web Server instead of Kestrel. The following example demonstrates using that Dockerfile.
```powershell
> $healthLog = docker inspect 92648775bce8 | ConvertFrom-Json
> $healthLog[0].State.Health.Log

```console
docker build -t aspnetapp -f .\Dockerfile.windowsservercore-iis-x64 .
docker run --rm -it -p:8080:80 aspnetapp
Start End ExitCode Output
----- --- -------- ------
2023-01-28T10:14:54.589686-08:00 2023-01-28T10:14:54.6137922-08:00 0 Healthy
2023-01-28T10:15:24.6264335-08:00 2023-01-28T10:15:24.6602762-08:00 0 Healthy
2023-01-28T10:15:54.6766598-08:00 2023-01-28T10:15:54.703489-08:00 0 Healthy
2023-01-28T10:16:24.7192354-08:00 2023-01-28T10:16:24.74409-08:00 0 Healthy
2023-01-28T10:16:54.7499988-08:00 2023-01-28T10:16:54.7750448-08:00 0 Healthy
```

## Build an image for Alpine, Debian or Ubuntu
Expand Down Expand Up @@ -113,6 +159,37 @@ aspnetapp latest 8c5d1952e3b7 10 hours ago

You can run these images in the same way as is done above, with Alpine.

## Build an image for Windows Nano Server

The following example demonstrates targeting Windows Nano Server (x64) explicitly (you must have [Windows containers enabled](https://docs.docker.com/docker-for-windows/#switch-between-windows-and-linux-containers)):

```console
docker build --pull -t aspnetapp:nanoserver -f Dockerfile.nanoserver-x64 .
docker run --rm -it -p 8000:80 aspnetapp:nanoserver
```

You can view in the app in your browser in the same way as demonstrated earlier.

You can use `docker images` to see the images you've built:

```console
> docker images aspnetapp
REPOSITORY TAG IMAGE ID CREATED SIZE
aspnetapp latest b2f0ecb7bdf9 About an hour ago 353MB
aspnetapp nanoserver d4b7586827f2 About an hour ago 353MB
```

## Build an image for Windows Server Core

The instructions for Windows Server Core are very similar to Windows Nano Server. There are three different sample Dockerfile files provided for Windows Server Core, which can all be used with the same approach as the Nano Server ones.

In addition, one of the samples enables using IIS as the Web Server instead of Kestrel. The following example demonstrates using that Dockerfile.

```console
docker build -t aspnetapp -f .\Dockerfile.windowsservercore-iis-x64 .
docker run --rm -it -p:8080:80 aspnetapp
```

## Build an image for ARM32 and ARM64

By default, distro-specific .NET tags target x64, such as `6.0-alpine` or `6.0-focal`. You need to use an architecture-specific tag if you want to target ARM. Note that .NET is only supported on Alpine on ARM64 and x64, and not ARM32.
Expand Down
25 changes: 25 additions & 0 deletions samples/aspnetapp/aspnetapp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddHealthChecks();

var app = builder.Build();
app.MapHealthChecks("/healthz");

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
Expand All @@ -24,9 +26,32 @@
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");


CancellationTokenSource cancellation = new();
app.Lifetime.ApplicationStopping.Register( () =>
{
cancellation.Cancel();
});

app.MapGet("/Environment", () =>
{
return new EnvironmentInfo();
});

// This API demonstrates how to use task cancellation
// to support graceful container shutdown via SIGTERM.
// The method itself is an example and not useful.
app.MapGet("/Delay/{value}", async (int value) =>
{
try
{
await Task.Delay(value, cancellation.Token);
}
catch(TaskCanceledException)
{
}

return new {Delay = value};
});

app.Run();