Zero‑Downtime Deployments for .NET 8 Containers with Azure Arc, GitOps, and Kubernetes

Mahmut Sarıkaya 4 dk okuma 18 Görüntülenme 0
Zero‑Downtime Deployments for .NET 8 Containers with Azure Arc, GitOps, and Kubernetes

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

Etiketler: #Zero downtime deployment #.NET 8 #Azure Arc #GitOps #Kubernetes
Paylaş:
M

Yazar

Mahmut Sarıkaya

yazılım Geliştirici

Yorumlar

Henüz yorum yok. İlk yorumu siz yapın!

Yorum Bırakın

3 + 1 =