Devexpress Linux

1 view
Skip to first unread message

Kerrie Gingrich

unread,
Aug 5, 2024, 2:36:37 PM8/5/24
to suineappvolcho
TheSDK container tooling provides an easy way to build container images directly from a .NET project. It was introduced last year via a NuGet package for .NET 7. For .NET 8, it comes included with the SDK.

During .NET 8's development, the container tooling has seen many improvements, including support for building rootless images, support for Podman, and better interoperability with container registries (like Docker Hub, quay.io, and the Amazon container registry).


If you look closer at the output, you can see the SDK used mcr.microsoft.com/dotnet/aspnet:8.0.0-rc.2 as the base image, and created an application image named web:latest. That's the name we used to run the container locally.


The SDK defaults to use Microsoft base images. It picks an image considering the properties that are set in the project. In this case, we're packing an ASP.NET application that targets .NET 8, so it picks the aspnet base image for that version. If the project changed to a self-contained application, the SDK would use the runtime-deps base image instead.


Microsoft's default images are Debian based. Microsoft publishes additional images based on other distros as you can see from the tag names on the Microsoft runtime repository. The SDK allows you to choose one of the other distros by setting the ContainerFamily property. This value will be added as a suffix to the base image tag. For example, to use Alpine images you can set:


The SDK isn't limited to using Microsoft images. You can choose a base image using the ContainerBaseImage property. The following example changes the TargetFramework to .NET 7 and uses the corresponding Red Hat UBI .NET image. We're using .NET 7 because UBI images for .NET 8 won't be available until .NET 8 GA (November 2023).


To set the target repository, we need to specify the registry host (ContainerRegistry), the image repository (ContainerRepository), and the image tag (ContainerImageTag). We can add them to the project file, or set them on the command line. The following example is for pushing the application image to quay.io/tmds/web:latest.


The default tag for the application image is latest. To use another, you can either set ContainerImageTag or ContainerImageTags. The latter property can be set to a semi-colon separated list of tags. To pass it on the command-line with bash, some single and double quotes escaping is needed to make bash and MSBuild accept the property value.


A container registry can support different architectures and operating systems. For example, Microsoft's .NET images (like mcr.microsoft.com/dotnet/runtime:8.0) support x64, and arm64 on Windows and Linux and Red Hat's .NET images support x64, arm64, ppc64le, and s390x on Linux.


Similarly, you can target the architectures provided by Red Hat. By default, the SDK includes a native executable (the app host) for the target architecture to start the application. For Microsoft-provided architectures, the SDK will fetch this executable from nuget.org. It is not available for the Red Hat architectures, so we need to disable it. Note that disabling the app host has no effect on how the resulting image works.


Alternatively, if we use ContainerRuntimeIdentifier we don't need to set UseAppHost to false. The application will be published with the app host of the build platform. That executable doesn't work on the target architecture, but that is not an issue because it's not used to start the application in the container.


It's best to explicitly set the runtime identifier to ensure no binaries are used that are meant for the build platform. This might for example happen when a Linux container gets built on a Windows system.


To set these values in the .NET project file, you can use RuntimeIdentifier (as we've done in the previous section) to set the -r argument, and the --sc argument can be set by adding a property SelfContained and setting it to true.


When you run the command, you'll see the SDK has changed from using the aspnet base image to the runtime-deps image. That makes sense: because ASP.NET is now included with the self-contained application, the base image no longer needs to provide it.


If you want to target Alpine Linux, instead of using the runtime identifier linux-x64 we need to use linux-musl-x64. The binaries for linux-musl-x64 are compatible with Linux distributions that use the musl C library instead of the glibc C library. The SDK does not (yet) pick a proper base image tag for this rid so we also need to set ContainerFamily.


.NET 8's built-in SDK container tooling does not support building container images for console applications. These are the .NET projects that use the Microsoft.NET.SDK as can be seen at the top of the project file: . To containerize a console application, you need to use the container support from the NuGet package, and invoke the PublishContainer target.


In the following sections we'll cover how you can customize the application image further. We'll look at adding container labels, setting environment variables, adding ports, controlling the working directory and user, and customizing the entrypoint and command used to start the application.


Container images can include metadata through labels. These labels can be added from the project file as shown in the following example which adds a build host label that gets set to the machine name.


The following table shows the labels added by the SDK, what properties determine their value, and the property that can be used to disable adding the label (by setting it to false). Some values are initialized from well-known properties, and can be overridden through container-specific properties.


Container images describe the ports they expose. Note that these ports are image metadata, and they do not limit external access to specific ports on a container image. Ports can be added through ContainerPort as shown in the following example:


The SDK will expose the ports from the base image, and automatically add ports based on the well-known ASP.NET Core environment variables ASPNETCORE_URLS, ASPNETCORE_HTTP_PORTS and ASPNETCORE_HTTPS_PORTS.


The working directory is also where the .NET application gets published to. This fulfills ASP.NET Core's default behavior which is to use the working directory as the content root (which is used to look up AppSettings and Razor files).


Since .NET 8, Microsoft .NET Linux container images include a non-root user named app. Like with previous .NET versions, the base image runs as the root user. If you build an application image through a Dockerfile, you can choose to make it run under the app user by adding an USER $APP_UID instruction. The $APP_UID used here is an environment variable set by the base image. It contains the app user's user id (uid).


In practice this means that with Microsoft images for .NET 8 or Red Hat images for any .NET version, the application image runs as a non-root user by default. With Microsoft base images for earlier versions of .NET, the application image will run as the root user by default.


The .NET application that is added to the image is owned by the root user. Consequently, the non-root user that runs the application in the container has no permissions to change the application files. Note that this includes the appsettings.json file that comes with an ASP.NET Core application.


When a .NET image runs on the Red Hat OpenShift container platform, OpenShift will run it under a random user ID and with a group ID (gid) of 0. The random uid is an additional security measure preventing the uid to map to another user on the host. Unlike the uid of 0 (which means root), a group id of 0 has no privileges attached to it. To accommodate working on OpenShift, the Red Hat base image set the HOME environment to the rootless user's home directory, and this directory is configured with write permissions for its group (gid 0).


Container images have an entrypoint (ENTRYPOINT) and a command (CMD). The entrypoint controls the fixed command line that gets executed when you run a container, and the command controls the default arguments that are passed.


For the first command line, the container will execute the image entrypoint and pass it the image command. For the second command line, the container will execute the image entrypoint and pass it the command line arguments instead of the image command.


The entrypoint can be overwritten as well (through the --entrypoint argument). Also, both values can be controlled on Kubernetes/OpenShift as part of the pod spec. The common practice is to not override these values. For configuration, instead of arguments, environment variables and ConfigMaps are used. This makes the distinction between using an entrypoint or a command unimportant to the Kubernetes consumer.


This message indicates that the Red Hat images have an entrypoint. This entrypoint is a helper script that performs initialization before starting the command. If you have a base image with such a helper script, it's best to preserve that logic by setting ContainerAppCommandInstruction to DefaultArgs. This causes the .NET application to be started using an image command instead of the image entrypoint. If the base image has an entrypoint has an entrypoint which isn't a helper script, you probably want to override it by setting ContainerAppCommandInstruction to Entrypoint so the .NET application starts.


The SDK container tooling allows to fully customize both the entrypoint and the command through the ContainerEntrypoint and ContainerDefaultArgs item groups. If you want to take full control you can set ContainerAppCommandInstruction to None. This stops the SDK from adding an instruction to start the .NET application.


The .NET 8 built-in support for building container images provides a convenient way to containerize .NET applications without going through the hassle of writing Dockerfiles. In this article, we've covered the ins and outs of the tooling. It's a good time to adopt this feature thanks to all the improvements made as part of the .NET 8 release. To learn more, you can find the official documentation at learn.microsoft.com.

3a8082e126
Reply all
Reply to author
Forward
0 new messages