Mein Name ist Tobias Nawa.
Von der Analyse, der Strategie, bis zum DevOps-Setup unterstütze ich Sie beim Erreichen Ihrer Vision.

Mein Name ist Tobias Nawa.
Von der Analyse bis zur Umsetzung unterstütze ich Sie beim Erreichen Ihrer Vision.

Mein Name ist Tobias Nawa.
Von der Analyse, der Strategie, bis zum DevOps-Setup unterstütze ich Sie beim Erreichen Ihrer Vision.

Wie man eine .NET Applikation in Docker für ARM64 bereitstellt

30.08.2023

Die Container-Technologie ist mittlerweile ein fester Bestandteil des DevOps-Ökosystems. Einer der Vorteile von Docker ist die Möglichkeit, Anwendungen unabhängig von der zugrunde liegenden Infrastruktur bereitzustellen. Ein besonderes Augenmerk liegt dabei auf der Unterstützung für ARM64-Architekturen, die in einer Vielzahl von Anwendungen, von IoT-Geräten bis zu Servern, zum Einsatz kommen. Besonders in der Cloud lassen sich durch den Einsatz von ARM-Prozessoren Kosten einsparen. In diesem Beitrag erfahrt ihr, wie ihr eine .NET-Applikation für ARM64 in Docker containerisiert.

Voraussetzungen

Bevor es losgeht, sollten Docker und das .NET SDK in der neuesten Version auf eurem System installiert sein. In diesem Beispiel nutze ich .NET 7. Außerdem ist ein grundlegendes Verständnis für die Arbeit mit der Kommandozeile und Docker von Vorteil.

Die .NET Applikation

Um nicht das einfachste Setup zu wählen, wähle ich als Applikation eine Blazor Hosted WASM app.

Das heißt, dass sie aus drei Projekten besteht:

1. Einem Server, der sich um das Prerendering kümmert und als Webserver agiert
2. Einem Client, der die Blazor WASM Anwendung beinhaltet
3. Einer Shared Library, die geteilte Elemente enthält

Anpassungen für ARM64

In der Server-Applikation muss in die Projektdatei folgende Zeile zur PropertyGroup hinzugefügt werden:

<RuntimeIdentifiers>linux-arm64;osx-arm64</RuntimeIdentifiers>

Hiermit legt ihr die Zielsysteme fest. Ich möchte die Anwendung in diesem Fall auf ARM64 auf Linux veröffentlichen. Und da ich einen Mac nutze, kommt die Konfiguration dafür auch hinzu.

Eventuell sind einige weitere Anpassungen am Code nötig, um die Applikation für die ARM64-Architektur zu optimieren. Meistens beschränken sich diese Änderungen jedoch auf Konfigurationsdateien oder Abhängigkeiten. Bei relativ einfachen Applikationen reicht die genannte Änderung in der Server-Projektdatei.

Dockerfile erstellen

Jetzt ist es an der Zeit, das Dockerfile zu schreiben. Ein Dockerfile ist die schriftliche Anleitung für Docker, wie der Container erstellt werden soll. Für eine ARM64-Applikation könnte das Dockerfile wie folgt aussehen:

FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:7.0-alpine AS build

ARG TARGETARCH
ARG TARGETPLATFORM
ARG BUILDPLATFORM

RUN echo "Building on $BUILDPLATFORM, targeting $TARGETPLATFORM"

WORKDIR /sourceCOPY . .
RUN dotnet restore -a $TARGETARCH Server/
RUN dotnet build -a $TARGETARCH --no-restore Server/
RUN dotnet publish -a $TARGETARCH --no-restore -c Release -o /app /p:UseAppHost=false Server/

FROM --platform=$TARGETARCH mcr.microsoft.com/dotnet/aspnet:7.0-alpine
RUN apk add curlWORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "MeineApp.Server.dll"



Ich nutze einen Multi Stage Build. Das heißt dass ich für das Erstellen und das Bereitstellen der Anwendung zwei getrennte Images nutze. Nur letzteres (ohne SDK, daher kleiner) wird im finalen Image verpackt.

Die wichtigsten Teile sind:

Block 1: Mit --platform=$BUILDPLATFORM sagen wir Docker dass wir für das Build-Image die gleiche Architektur verwenden wollen, wie die des Systems, auf dem wir den Build ausführen. Dies reduziert Inkompatibilität. Außerdem nutze ich Alpine, da im Gegensatz zu Debian, das Zielimage ca 100MB kleiner ist. Kleinere Images lassen sich später schneller ausrollen, was besonders bei skalierenden Anwendungen wichtig ist.

Block 3: Hier wird das Ausgangssystem und das Zielsystem ausgegeben.

Block 4: Hier wird die Anwendung für das Zielsystem erstellt.

Block 5: Jetzt nehmen wir das Basisimage für die Zielarchitektur, mit --platform=$TARGETARCH. Außerdem nutzen wir nur aspnet und kein volles SDK. Das SDK ist zu Ausführen nicht mehr nötig.

Docker Image bauen

Nachdem das Dockerfile fertiggestellt ist, muss das zugehörige Docker-Image erstellt werden. Das geht mit folgendem Befehl:

docker buildx build --platform=linux/arm64 . -t homepage:latest --no-cache

Github Actions

Hier noch für die Github-Nutzer ein Job, der das Image erstellt, mit dem aktuellen Git Tag tagged (z.B. 1.0.2) und auf AWS ECR (eine Docker Registry) pusht.

build-and-push-docker: 
  runs-on: ubuntu-latest 
  steps: 
    - uses: actions/checkout@v3 
    - name: Set output 
      id: vars 
      run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT 
    - name: Set up QEMU 
      uses: docker/setup-qemu-action@v2 
    - name: Set up Docker Buildx 
      uses: docker/setup-buildx-action@v2 
    - uses: actions/setup-dotnet@v3 
      with: 
        dotnet-version: '7.0.x' 
    - name: Configure AWS credentials 
      uses: aws-actions/configure-aws-credentials@v1 
      with: 
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 
        aws-region: eu-central-1 
    - name: Login to Amazon ECR 
      id: login-ecr 
      uses: aws-actions/amazon-ecr-login@v1 
    - name: Build, tag, and push docker image to Amazon ECR 
      env: 
        REGISTRY: ${{ steps.login-ecr.outputs.registry }} 
        REPOSITORY: mein-ecr-repository 
        IMAGE_TAG: ${{ steps.vars.outputs.tag }} 
      run: | 
        docker buildx build --push --platform=linux/arm64 -t ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ env.IMAGE_TAG }} . --no-cache 
    - name: Logout of Amazon ECR 
      run

Best Practices

Es lohnt sich, Tags für ARM64-spezifische Images zu verwenden, damit diese leichter zu finden und zu verwalten sind. Zudem sollten Images regelmäßig aktualisiert werden, um von Sicherheitspatches und Performance-Verbesserungen zu profitieren.

Fazit

Das Containerisieren und Bereitstellen einer .NET-Applikation für ARM64 ist ein relativ einfacher Prozess, der jedoch einiges an Vorbereitung erfordert. Mit den in diesem Beitrag vorgestellten Schritten sollte es möglich sein, eigene .NET-Projekte effizient für ARM64 zu containerisieren und zu deployen.

Ich nutze dieses Vorgehen auch für meine Webseite. Bereitstellen tue ich sie per Terraform und AWS ECS mit Fargate. Um Kosten zu sparen nutze ich eine Mischung aus On-Demand und Spot.

Kontakt

Kontakt

Tobias Nawa

Hummelweg 22

82178 Puchheim


tobias@tobiasnawa.com
Tel. 015156955179