Why zero‑downtime matters for .NET 8 services
When a retail API receives 12,000 requests per minute, even a five‑second outage can translate into lost revenue and damaged brand trust. .NET 8 introduced native support for HTTP/2 and async pipelines that keep latency low, but the deployment model determines whether those gains survive a code push. Zero‑downtime deployment guarantees that users never see a 502 or 503 while the new container image rolls out.
Prerequisites and system requirements
Before you start, make sure you have:
- Azure CLI 2.55 or newer.
- Kubernetes 1.27+ cluster (any cloud, on‑prem, or edge) that can be connected to Azure Arc.
- .NET SDK 8.0 installed locally.
- Flux v2 installed on the cluster (or the ability to run
flux install).
All components are free in the dev tier, and a modest VM (2 vCPU, 4 GB RAM) is enough for a proof‑of‑concept.
Containerizing a .NET 8 application
The first step is a reproducible Docker image. Use the official Microsoft runtime image to keep the footprint under 150 MB.
cat > Dockerfile <<EOF
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyApp.csproj", "."]
RUN dotnet restore "MyApp.csproj"
COPY . .
RUN dotnet publish "MyApp.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]
EOF Build and push the image to a registry that the Arc‑connected cluster can reach (Azure Container Registry, Docker Hub, etc.).
az acr login --name MyRegistry
docker build -t myregistry.azurecr.io/myapp:2024-09-07 .
docker push myregistry.azurecr.io/myapp:2024-09-07 Registering the cluster with Azure Arc
Azure Arc turns any Kubernetes cluster into a first‑class Azure resource, enabling unified policy and monitoring.
az login
az account set --subscription "MySubscription"
az extension add --name connectedk8s
az k8s connect --resource-group MyRG --name MyArcCluster --location eastus --distribution kubernetes
# Verify connection
az k8s show --resource-group MyRG --name MyArcCluster After the command finishes, the cluster appears in the Azure portal under “Arc‑enabled Kubernetes”.
Setting up GitOps with Flux
Flux watches a Git repository and applies any change to the cluster automatically. Store the manifest files in a dedicated repo, for example github.com/contoso/k8s-gitops.
flux install \
--namespace flux-system \
--network-policy=true \
--components=source-controller,kustomize-controller,helm-controller,notification-controller
# Create a GitRepository source
cat > gitrepo.yaml <<EOF
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
name: myapp-repo
namespace: flux-system
spec:
interval: 1m0s
url: https://github.com/contoso/k8s-gitops
branch: main
EOF
kubectl apply -f gitrepo.yaml
# Create a Kustomization that points to the deployment folder
cat > kustomization.yaml <<EOF
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
name: myapp
namespace: flux-system
spec:
interval: 30s
path: ./deployments
prune: true
sourceRef:
kind: GitRepository
name: myapp-repo
targetNamespace: default
EOF
kubectl apply -f kustomization.yaml From now on, any commit to the deployments folder triggers a reconciliation loop.
Rolling update strategy for zero downtime
Kubernetes offers several strategies, but the safest for .NET web APIs is RollingUpdate with a readiness probe that checks the health endpoint.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myregistry.azurecr.io/myapp:2024-09-07
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 30
periodSeconds: 15 The maxUnavailable: 0 flag guarantees that the old pods stay serving traffic until the new pod passes the readiness check. Combined with a graceful shutdown in Program.cs (e.g., app.Lifetime.ApplicationStopping.Register(() => ...)), you achieve true zero‑downtime.
Testing the deployment pipeline
Push a minor version bump to the repo:
git clone https://github.com/contoso/k8s-gitops.git
cd k8s-gitops/deployments
sed -i 's/:2024-09-07/:2024-09-08/' myapp-deployment.yaml
git commit -am "Bump image tag to 2024-09-08"
git push origin main Flux detects the change within 30 seconds, applies the new manifest, and the rolling update begins. You can watch the rollout with kubectl rollout status deployment/myapp-deployment. A successful rollout shows no drop in kubectl get pods ready count.
Conclusion
By containerizing .NET 8, connecting the cluster to Azure Arc, and driving changes through a GitOps pipeline, you eliminate manual steps that cause outages. The combination of Flux‑managed manifests and a carefully tuned RollingUpdate strategy provides true zero‑downtime for high‑traffic .NET APIs. Adopt this pattern early, and you’ll reap the benefits of faster releases, audit‑ready change history, and consistent environments across on‑prem and cloud.
Author: Mahmut Sarıkaya — sarikayadev.com
Sources
Microsoft Docs – Azure Arc; Flux CD Official Documentation; Docker Hub – .NET 8 images