• Home
  • /
  • Articles
  • /
  • Software Development
  • /
  • Building and Deploying a Microservices Architecture Application
HEADLINES

Building and Deploying a Microservices Architecture Application to Azure Using DevOps

A practical end-to-end guide covering microservice design, ASP.NET Core, Docker, Azure Container Registry, Azure Kubernetes Service, Azure DevOps, CI/CD, middleware-based exception handling, JWT tokenization, security, observability, and production deployment.


  • Techm Studios
  • BY TECHM STUDIOS
  • |
  • Aug 28, 2026
  • |
  • UPDATED: Aug 28, 2026
  • |
  • 0 COMMENTS
Building and Deploying a Microservices Architecture Application
© Simform / Images

Techm Studios R&D Series presents a comprehensive guide on building and deploying a microservices architecture application to Azure using DevOps. This article covers essential topics such as microservice design, ASP.NET Core, Docker, Azure Container Registry, Azure Kubernetes Service, Azure DevOps, CI/CD, middleware-based exception handling, JWT tokenization, security, observability, and production deployment.

Table of Contents

2. Introduction

Microservices architecture decomposes an application into independently deployable services, with each service responsible for a focused business capability. This approach can improve independent scaling, deployment velocity, resilience, and team autonomy when the system boundaries are designed correctly. Microsoft describes microservices as separately deployable services that communicate through well-defined interfaces.

The architecture becomes particularly powerful in a cloud environment because individual services can be packaged as containers, deployed independently, scaled according to demand, and promoted through automated CI/CD pipelines.

This article demonstrates a representative solution in which an API Gateway communicates with several ASP.NET Core microservices. The services are containerized using Docker and deployed to Azure Kubernetes Service (AKS). Images are stored in Azure Container Registry (ACR), while Azure DevOps Pipelines automate build, test, image creation, and deployment.

The implementation also introduces two cross-cutting concerns that are essential in distributed systems:

  • Centralized exception handling through ASP.NET Core middleware.
  • Secure token-based authentication and authorization using JWT bearer tokens.
Important: Microservices should not be introduced simply because they are fashionable. Distributed systems add operational, networking, security, testing, and observability complexity. Start with clear business boundaries and adopt microservices when independent deployment, scaling, ownership, or resilience provides meaningful value.

3. Technology Stack

Component Technology Purpose
Application ASP.NET Core / .NET Build RESTful microservices.
Authentication Microsoft Entra ID / JWT Identity, authentication, and authorization.
Containerization Docker Package applications consistently.
Container Registry Azure Container Registry Store and distribute private container images.
Orchestration Azure Kubernetes Service Run and scale microservices.
CI/CD Azure DevOps Pipelines Build, test, scan, package, and deploy.
Monitoring Azure Monitor / Application Insights Metrics, traces, logs, and diagnostics.
Secrets Azure Key Vault Secure storage of secrets, certificates, and keys.

Azure DevOps provides integrated source control, CI/CD, and package-management capabilities, while Azure Pipelines supports automated build, test, and deployment workflows.

4. Project Structure

A monorepo can be a good starting point because it simplifies shared tooling, development, and repository management. As the organization grows, services can also be separated into repositories where independent ownership and release processes justify that model.

MicroservicesSolution/
│
├── src/
│   ├── ApiGateway/
│   │   ├── Controllers/
│   │   ├── Middleware/
│   │   ├── Services/
│   │   ├── Program.cs
│   │   └── Dockerfile
│   │
│   ├── ProductService/
│   │   ├── Controllers/
│   │   ├── Application/
│   │   ├── Domain/
│   │   ├── Infrastructure/
│   │   ├── Middleware/
│   │   ├── Program.cs
│   │   └── Dockerfile
│   │
│   ├── OrderService/
│   │   ├── Controllers/
│   │   ├── Application/
│   │   ├── Domain/
│   │   ├── Infrastructure/
│   │   ├── Middleware/
│   │   ├── Program.cs
│   │   └── Dockerfile
│   │
│   └── IdentityService/
│
├── tests/
│   ├── ProductService.UnitTests/
│   ├── OrderService.UnitTests/
│   └── IntegrationTests/
│
├── deploy/
│   ├── kubernetes/
│   │   ├── namespace.yaml
│   │   ├── product-deployment.yaml
│   │   ├── product-service.yaml
│   │   ├── order-deployment.yaml
│   │   └── order-service.yaml
│   │
│   └── helm/
│
├── pipelines/
│   └── azure-pipelines.yml
│
└── README.md

5. Designing the Microservices

The first design task is identifying bounded business capabilities rather than technical layers. For example, an e-commerce platform could contain Product, Order, Customer, Payment, Inventory, and Notification services.

5.1 Product Service

[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _service;

    public ProductsController(IProductService service)
    {
        _service = service;
    }

    [HttpGet("{id:guid}")]
    public async Task Get(Guid id)
    {
        var product = await _service.GetAsync(id);

        if (product is null)
            return NotFound();

        return Ok(product);
    }
}

5.2 Order Service

[ApiController]
[Route("api/orders")]
[Authorize]
public class OrdersController : ControllerBase
{
    private readonly IOrderService _service;

    public OrdersController(IOrderService service)
    {
        _service = service;
    }

    [HttpPost]
    public async Task Create(CreateOrderRequest request)
    {
        var customerId = User.FindFirst("sub")?.Value;

        if (string.IsNullOrWhiteSpace(customerId))
            return Unauthorized();

        var order = await _service.CreateAsync(customerId, request);

        return CreatedAtAction(
            nameof(Get),
            new { id = order.Id },
            order);
    }

    [HttpGet("{id:guid}")]
    public async Task Get(Guid id)
    {
        var order = await _service.GetAsync(id);

        if (order is null)
            return NotFound();

        return Ok(order);
    }
}

5.3 Database-per-Service Principle

A strong microservices boundary normally means that a service owns its persistence model. The Order Service should not directly query the Product Service's database. Instead, services communicate through APIs or messaging.

Avoid shared databases: A shared database creates hidden coupling. A schema change in one service can unexpectedly break another service and makes independent deployment much harder.

6. Middleware Exception Strategy

Exceptions should not be handled independently inside every controller. Doing so produces duplicated code and inconsistent HTTP responses. ASP.NET Core provides middleware specifically suited to centralizing exception processing. In production, Microsoft's guidance recommends exception-handler middleware rather than exposing developer exception details.

6.1 Why Middleware?

Middleware executes as part of the HTTP request pipeline. An exception middleware placed early in that pipeline can catch exceptions produced by downstream middleware and application components.

The general pipeline should look like:

HTTP Request
     |
     v
Exception Middleware
     |
     v
Correlation Middleware
     |
     v
Authentication
     |
     v
Authorization
     |
     v
Routing
     |
     v
Controller / Endpoint
     |
     v
Application Service
     |
     v
Repository / External Service

6.2 Custom Exception Types

public class NotFoundException : Exception
{
    public NotFoundException(string message)
        : base(message)
    {
    }
}

public class BusinessException : Exception
{
    public BusinessException(string message)
        : base(message)
    {
    }
}

public class ValidationException : Exception
{
    public ValidationException(string message)
        : base(message)
    {
    }
}

6.3 Exception Middleware

public sealed class ExceptionHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ExceptionHandlingMiddleware> _logger;

    public ExceptionHandlingMiddleware(
        RequestDelegate next,
        ILogger<ExceptionHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception exception)
        {
            await HandleExceptionAsync(context, exception);
        }
    }

    private async Task HandleExceptionAsync(
        HttpContext context,
        Exception exception)
    {
        var traceId = context.TraceIdentifier;

        _logger.LogError(
            exception,
            "Unhandled exception. TraceId: {TraceId}",
            traceId);

        var statusCode = exception switch
        {
            ValidationException => StatusCodes.Status400BadRequest,
            BusinessException => StatusCodes.Status422UnprocessableEntity,
            NotFoundException => StatusCodes.Status404NotFound,
            UnauthorizedAccessException => StatusCodes.Status401Unauthorized,
            _ => StatusCodes.Status500InternalServerError
        };

        context.Response.StatusCode = statusCode;
        context.Response.ContentType = "application/problem+json";

        var response = new
        {
            type = "https://httpstatuses.com/" + statusCode,
            title = GetTitle(statusCode),
            status = statusCode,
            traceId = traceId
        };

        await context.Response.WriteAsJsonAsync(response);
    }

    private static string GetTitle(int statusCode)
    {
        return statusCode switch
        {
            400 => "Validation error",
            401 => "Unauthorized",
            404 => "Resource not found",
            422 => "Business rule violation",
            _ => "An unexpected error occurred"
        };
    }
}

6.4 Registering the Middleware

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseMiddleware<ExceptionHandlingMiddleware>();

app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

Exception middleware should be positioned early enough to observe failures from subsequent components. ASP.NET Core's built-in UseExceptionHandler follows the same general principle: exceptions thrown by later middleware can be intercepted and converted into an appropriate error response.

6.5 Recommended Error Response

{
    "type": "https://httpstatuses.com/404",
    "title": "Resource not found",
    "status": 404,
    "traceId": "00-5f3..."
}
Never expose internal exception details in production. Do not return database connection strings, stack traces, SQL statements, internal hostnames, secrets, access tokens, or implementation details. Log detailed information securely on the server while returning a safe response to the client.

7. Tokenization and Authentication Strategy

In a microservices environment, authentication should be centralized while authorization remains explicit at each protected API boundary. A common implementation is OAuth 2.0/OpenID Connect with Microsoft Entra ID issuing access tokens containing claims that downstream APIs validate.

It is important to distinguish authentication tokens from data tokenization. A JWT access token represents identity and authorization claims; it is not a substitute for protecting sensitive business data. If an application stores payment or other highly sensitive information, a separate data-tokenization strategy may be required.

7.1 JWT Bearer Authentication

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        options.Authority =
            builder.Configuration["Authentication:Authority"];

        options.Audience =
            builder.Configuration["Authentication:Audience"];

        options.RequireHttpsMetadata = true;
    });

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseMiddleware<ExceptionHandlingMiddleware>();

app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

7.2 Example Configuration

{
    "Authentication": {
        "Authority": "https://login.microsoftonline.com/<tenant-id>/v2.0",
        "Audience": "api://microservices-api"
    }
}

In production, environment-specific authentication configuration should not be committed as secrets to source control. Use a secure configuration mechanism such as Azure Key Vault or managed identity where appropriate.

7.3 Authorization Policies

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Orders.Read", policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireClaim("scp", "orders.read");
    });

    options.AddPolicy("Orders.Write", policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireClaim("scp", "orders.write");
    });
});

Controllers can then apply explicit policies:

[Authorize(Policy = "Orders.Write")]
[HttpPost]
public async Task Create(CreateOrderRequest request)
{
    // Create order
}

7.4 Token Security Rules

  • Always require HTTPS.
  • Keep access tokens short-lived.
  • Never write access tokens to application logs.
  • Do not put access tokens in URLs.
  • Validate issuer, audience, signature, and expiration.
  • Use scopes or roles for authorization.
  • Do not treat a decoded JWT as proof of authenticity; validate its signature.
  • Use managed identity or workload identity for service-to-service Azure access where possible.
Service-to-service authentication: Prefer workload identities, managed identities, or other short-lived credentials over storing long-lived client secrets in pipeline variables. Microsoft's microservices CI/CD guidance specifically recommends secure federated, short-lived credentials for pipeline authentication to Azure.

8. Containerizing the Services

Each microservice should produce its own container image. This creates a consistent deployment artifact that can be tested in CI and promoted across environments.

8.1 Product Service Dockerfile

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

COPY ["ProductService/ProductService.csproj",
      "ProductService/"]

RUN dotnet restore "ProductService/ProductService.csproj"

COPY . .

WORKDIR "/src/ProductService"

RUN dotnet publish \
    "ProductService.csproj" \
    -c Release \
    -o /app/publish \
    /p:UseAppHost=false

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app

COPY --from=build /app/publish .

EXPOSE 8080

ENTRYPOINT ["dotnet", "ProductService.dll"]

8.2 Build and Run Locally

docker build \
    -t product-service:1.0.0 \
    -f src/ProductService/Dockerfile .

docker run \
    -p 8080:8080 \
    product-service:1.0.0

Before pushing an image to Azure, test it locally and run automated vulnerability scanning as part of the CI process.

9. Preparing the Azure Environment

The Azure resources used in this reference architecture are:

  • Resource Group
  • Azure Container Registry
  • Azure Kubernetes Service
  • Microsoft Entra ID
  • Azure Key Vault
  • Azure Monitor / Application Insights
  • Optional Azure Application Gateway or another ingress solution

9.1 Create a Resource Group

az login

az group create \
    --name rg-microservices-prod \
    --location westeurope

Select the Azure region based on latency, compliance, availability, service availability, and data-residency requirements rather than copying the example region blindly.

10. Creating Azure Container Registry

az acr create \
    --resource-group rg-microservices-prod \
    --name acrmicroservicesprod \
    --sku Premium

Authenticate Docker against the registry:

az acr login --name acrmicroservicesprod

Tag the image:

docker tag product-service:1.0.0 \
    acrmicroservicesprod.azurecr.io/product-service:1.0.0

Push it:

docker push \
    acrmicroservicesprod.azurecr.io/product-service:1.0.0

In a production pipeline, use immutable image tags such as a Git commit SHA instead of repeatedly overwriting a generic latest tag. This makes rollback and auditability significantly easier.

11. Creating Azure Kubernetes Service

az aks create \
    --resource-group rg-microservices-prod \
    --name aks-microservices-prod \
    --node-count 3 \
    --enable-managed-identity \
    --generate-ssh-keys

Connect kubectl to the cluster:

az aks get-credentials \
    --resource-group rg-microservices-prod \
    --name aks-microservices-prod

kubectl get nodes
The exact AKS configuration should be adapted for production requirements. Consider availability zones, node pools, autoscaling, networking, workload identity, ingress, private clusters, policies, resource limits, and upgrade strategy.

12. Kubernetes Deployment

12.1 Namespace

apiVersion: v1
kind: Namespace
metadata:
  name: microservices

12.2 Product Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: product-service
  namespace: microservices
spec:
  replicas: 2

  selector:
    matchLabels:
      app: product-service

  template:
    metadata:
      labels:
        app: product-service

    spec:
      containers:
        - name: product-service
          image: acrmicroservicesprod.azurecr.io/product-service:1.0.0

          ports:
            - containerPort: 8080

          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"

          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 10

          livenessProbe:
            httpGet:
              path: /health/live
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 20

12.3 Kubernetes Service

apiVersion: v1
kind: Service
metadata:
  name: product-service
  namespace: microservices

spec:
  selector:
    app: product-service

  ports:
    - port: 80
      targetPort: 8080

  type: ClusterIP

12.4 Apply the Configuration

kubectl apply -f deploy/kubernetes/namespace.yaml

kubectl apply \
    -f deploy/kubernetes/product-deployment.yaml

kubectl apply \
    -f deploy/kubernetes/product-service.yaml

Verify the deployment:

kubectl get deployments -n microservices
kubectl get pods -n microservices
kubectl get services -n microservices

13. Azure DevOps CI/CD

CI/CD is particularly important for microservices because the architecture creates more independently deployable artifacts. Microsoft's guidance emphasizes independent service deployment, automated validation, quality gates, and the ability to deploy a new service version alongside an existing version.

A practical pipeline should perform:

  • Source checkout.
  • Dependency restore.
  • Compilation.
  • Unit tests.
  • Integration tests.
  • Static analysis.
  • Security and dependency scanning.
  • Docker image build.
  • Container image scanning.
  • Push image to ACR.
  • Deploy to development.
  • Run smoke tests.
  • Promote to staging.
  • Require approval where appropriate.
  • Deploy to production.

13.1 Recommended Pipeline Flow

Developer Commit
       |
       v
Pull Request
       |
       v
    Build
       |
       v
Unit Tests
       |
       v
Security Scan
       |
       v
Docker Build
       |
       v
Container Scan
       |
       v
Push to ACR
       |
       v
Deploy DEV
       |
       v
Integration / Smoke Tests
       |
       v
Deploy STAGING
       |
       v
Approval / Quality Gate
       |
       v
Deploy PRODUCTION
       |
       v
    Monitor
       |
       v
Rollback if required

14. Azure Pipeline YAML

The following example illustrates a simplified Azure DevOps pipeline. Production implementations should add environment approvals, security scanning, test reporting, deployment strategies, and organization-specific governance.

trigger:
  branches:
    include:
      - main

pool:
  vmImage: ubuntu-latest

variables:
  buildConfiguration: Release
  serviceName: product-service
  acrName: acrmicroservicesprod
  imageRepository: product-service
  tag: $(Build.SourceVersion)

stages:

# ---------------------------------------------
# BUILD AND TEST
# ---------------------------------------------
- stage: Build
  displayName: Build and Test

  jobs:
  - job: BuildAndTest

    steps:
    - checkout: self

    - task: UseDotNet@2
      displayName: Install .NET SDK
      inputs:
        packageType: sdk
        version: '10.x'

    - script: |
        dotnet restore
      displayName: Restore dependencies

    - script: |
        dotnet build \
          --configuration $(buildConfiguration) \
          --no-restore
      displayName: Build solution

    - script: |
        dotnet test \
          --configuration $(buildConfiguration) \
          --no-build \
          --logger trx
      displayName: Run tests

    - task: PublishTestResults@2
      displayName: Publish test results
      condition: succeededOrFailed()
      inputs:
        testResultsFormat: VSTest
        testResultsFiles: '**/*.trx'

# ---------------------------------------------
# CONTAINER BUILD
# ---------------------------------------------
- stage: Container
  displayName: Build Container
  dependsOn: Build
  condition: succeeded()

  jobs:
  - job: BuildContainer

    steps:
    - task: Docker@2
      displayName: Build and Push Container
      inputs:
        containerRegistry: AzureContainerRegistryConnection
        repository: $(imageRepository)
        command: buildAndPush
        Dockerfile: src/ProductService/Dockerfile
        tags: |
          $(tag)

# ---------------------------------------------
# DEPLOY DEVELOPMENT
# ---------------------------------------------
- stage: DeployDev
  displayName: Deploy Development
  dependsOn: Container
  condition: succeeded()

  jobs:
  - deployment: Deploy
    environment: development

    strategy:
      runOnce:
        deploy:
          steps:

          - task: KubernetesManifest@1
            displayName: Deploy to AKS
            inputs:
              action: deploy
              kubernetesServiceConnection: AKS-Development
              namespace: microservices
              manifests: |
                deploy/kubernetes/product-deployment.yaml
                deploy/kubernetes/product-service.yaml
              containers: |
                acrmicroservicesprod.azurecr.io/product-service:$(tag)

Azure Pipelines supports CI/CD workflows for applications across different languages and platforms.

14.1 Service Connections

Configure secure Azure DevOps service connections rather than embedding Azure credentials directly inside YAML.

Typical connections include:

  • Azure Resource Manager connection.
  • Azure Container Registry connection.
  • AKS/Kubernetes connection where required.

Prefer workload identity federation or other short-lived authentication mechanisms where supported rather than long-lived secrets.

15. Security Strategy

15.1 Identity

  • Use Microsoft Entra ID for centralized identity.
  • Use OAuth 2.0/OpenID Connect for user-facing authentication.
  • Validate JWTs at protected API boundaries.
  • Use scopes and roles for authorization.
  • Use managed/workload identity for Azure resources where possible.

15.2 Secrets

Secrets should never be committed to Git.

# Never do this

ConnectionStrings__Database=Server=...;Password=SuperSecret123

Instead, use Azure Key Vault and an identity-based access mechanism.

15.3 Container Security

  • Use minimal runtime images.
  • Run containers as non-root where possible.
  • Scan dependencies.
  • Scan container images.
  • Keep base images patched.
  • Generate and retain software bills of materials where required.
  • Use immutable image tags.
  • Do not place secrets inside Docker images.

Microsoft recommends securing container delivery with practices such as image signing, software-bill-of-materials attestations, and vulnerability scanning as appropriate to the environment.

15.4 Network Security

  • Use HTTPS externally.
  • Restrict internal service access.
  • Use Kubernetes NetworkPolicies where appropriate.
  • Consider private AKS and private endpoints for sensitive environments.
  • Use an ingress controller or Application Gateway for controlled external access.

16. Logging, Monitoring, and Distributed Tracing

A distributed system without observability is difficult to operate. A request can travel through an API Gateway, several microservices, databases, and external APIs. A single application log is rarely sufficient to identify the actual failure.

16.1 Structured Logging

logger.LogInformation(
    "Creating order {OrderId} for customer {CustomerId}",
    orderId,
    customerId);

Avoid logging sensitive information such as passwords, access tokens, payment credentials, or confidential personal information.

16.2 Correlation ID Middleware

public sealed class CorrelationIdMiddleware
{
    private readonly RequestDelegate _next;

    public CorrelationIdMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        const string headerName = "X-Correlation-ID";

        var correlationId =
            context.Request.Headers[headerName].FirstOrDefault()
            ?? Guid.NewGuid().ToString();

        context.Response.Headers[headerName] = correlationId;

        using (context.RequestServices
            .GetRequiredService<ILoggerFactory>()
            .CreateLogger("Correlation")
            .BeginScope(new Dictionary<string, object>
            {
                ["CorrelationId"] = correlationId
            }))
        {
            await _next(context);
        }
    }
}

16.3 Health Checks

builder.Services.AddHealthChecks();

var app = builder.Build();

app.MapHealthChecks("/health/live");
app.MapHealthChecks("/health/ready");

Kubernetes can use these endpoints to determine whether a container should receive traffic and whether it needs to be restarted.

16.4 Continuous Monitoring

DevOps does not end when deployment completes. Continuous monitoring should cover application health, infrastructure, performance, failures, and customer-impacting behavior. Microsoft identifies continuous monitoring as a core DevOps practice.

Useful metrics include:

  • Request rate.
  • HTTP 4xx and 5xx rates.
  • Request latency.
  • CPU and memory utilization.
  • Pod restart count.
  • Database latency.
  • Queue depth.
  • Dependency failures.
  • Authentication failures.

17. Deployment Strategies

17.1 Rolling Deployment

Kubernetes can replace old instances gradually with new ones. This is appropriate for many routine service updates.

17.2 Blue-Green Deployment

Run the existing and new versions simultaneously, validate the new version, and then switch traffic.

17.3 Canary Deployment

Send a small percentage of traffic to the new version first. If health and business metrics remain acceptable, gradually increase traffic.

Microsoft identifies rolling, blue-green, and canary approaches as common strategies for updating services in a microservices environment.

Recommended production pattern: Start with rolling deployments, then introduce blue-green or canary deployments when the application's risk profile and operational maturity justify the additional complexity.

18. Testing Strategy

Microservices require testing at several levels because failures can occur both inside a service and between services.

Test Purpose
Unit Tests Validate individual business components.
Integration Tests Validate service/database interactions.
Contract Tests Ensure service APIs remain compatible.
API Tests Validate HTTP behavior, authentication, and authorization.
End-to-End Tests Validate critical business journeys.
Load Tests Determine behavior under expected and peak load.
Security Tests Identify vulnerabilities and configuration weaknesses.

18.1 Test the Exception Middleware

[Fact]
public async Task UnknownException_Returns500()
{
    var response = await TestClient.GetAsync(
        "/api/test/throw");

    Assert.Equal(
        HttpStatusCode.InternalServerError,
        response.StatusCode);
}

18.2 Test Authentication

  • Missing token returns 401.
  • Invalid token returns 401.
  • Expired token returns 401.
  • Valid token without required scope returns 403.
  • Valid token with the correct scope succeeds.

19. Troubleshooting Common Deployment Problems

Problem: Pod is stuck in ImagePullBackOff

kubectl describe pod <pod-name> -n microservices

Check:

  • Image name.
  • Image tag.
  • ACR permissions.
  • AKS-to-ACR integration.
  • Registry availability.

Problem: Pod repeatedly restarts

kubectl logs <pod-name> -n microservices

kubectl describe pod <pod-name> -n microservices

Common causes include configuration errors, failed health checks, unavailable dependencies, incorrect connection strings, or application startup exceptions.

Problem: API returns 401

Check:

  • JWT issuer.
  • Audience.
  • Signing keys.
  • Token expiration.
  • HTTPS configuration.
  • Authorization header.
Authorization: Bearer eyJ...

Problem: API returns 403

A 403 generally means the caller was authenticated but did not satisfy the authorization policy. Inspect the scopes, roles, and claims expected by the API.

Problem: Changes are deployed to the wrong version

Avoid mutable image tags such as latest for production. Use a unique Git commit SHA or release version:

product-service:8c3e2d1
product-service:2026.08.28.15

20. Production Readiness Checklist

  • Every service has a clearly defined business responsibility.
  • Services can be deployed independently.
  • Each service owns its persistence boundary.
  • APIs are versioned where compatibility requires it.
  • HTTPS is enforced.
  • Authentication is centralized and authorization is enforced at APIs.
  • JWT issuer and audience validation are enabled.
  • Access tokens are not written to logs.
  • Secrets are stored outside source control.
  • Azure Key Vault or equivalent secret management is configured.
  • Managed/workload identities are used where appropriate.
  • Docker images are scanned.
  • Dependency vulnerabilities are scanned.
  • Images use immutable version tags.
  • Kubernetes readiness probes are configured.
  • Kubernetes liveness probes are configured.
  • CPU and memory requests/limits are defined.
  • Autoscaling requirements have been evaluated.
  • Centralized exception middleware is implemented.
  • Production error responses do not expose internal details.
  • Correlation IDs are available.
  • Centralized logging is configured.
  • Application and infrastructure monitoring is configured.
  • CI executes automated tests.
  • CD deploys through controlled environments.
  • Production deployments have appropriate approval gates.
  • Rollback procedures have been tested.
  • Database migration strategy is defined.
  • Disaster recovery and backup requirements are defined.
  • Security and compliance requirements are documented.

21. Final Thoughts @ Techm Studios R&D Series Desk

Building a microservices application is more than splitting a monolithic application into multiple projects. A successful implementation requires clear domain boundaries, independently deployable services, reliable communication patterns, automated testing, secure authentication, centralized exception handling, containerization, observability, and a disciplined deployment process.

Azure provides the infrastructure required to implement this model at scale. A representative implementation can use ASP.NET Core for the services, Docker for packaging, Azure Container Registry for image storage, AKS for orchestration, Azure DevOps for CI/CD, Microsoft Entra ID for identity, Azure Key Vault for secrets, and Azure Monitor for operational visibility.

The middleware exception strategy ensures that errors are handled consistently across services instead of being duplicated throughout controllers. A token-based security strategy protects APIs while allowing services to make authorization decisions based on validated identity claims.

The DevOps pipeline then connects development to production by automatically compiling code, executing tests, building immutable container images, publishing them to ACR, deploying them to AKS, validating the deployment, and promoting approved versions through environments.

The most important principle is to treat the platform as a complete system: architecture, source control, testing, security, deployment, monitoring, and recovery must work together. Microsoft's Azure architecture guidance similarly emphasizes CI/CD, continuous monitoring, independent service deployment, and appropriate deployment strategies as important parts of operating microservices successfully.

22. References

Technical note: The commands and configurations in this article are reference examples rather than a universal production template. Azure service availability, pricing, Kubernetes versions, .NET versions, organizational security requirements, and identity configuration should be validated before production deployment.

© 2026 Techm Studios R&D Series & DevOps Technical Guide

Also Read: RabbitMQ with ASP.NET Core Tutorial

SPONSORED ADS

COMMENTS
LEAVE A REPLY
SPONSORED ADS
POPULAR
AI-Generated Fashion Design

AI-generated fashion design is revolutionizing the industry, pushing the boundaries of creativity and innovation in style.


SPONSORED JOBS

TECHM JOBS
POPULAR
Journalist Jobs, South Africa

Browse most trending Journalist jobs in South Africa. Apply today

Photographer Jobs, South Africa

Browse most trending Photographer jobs in South Africa. Apply today

Graphical Designer Jobs, United Kingdom

Browse most trending Fashion jobs in United Kingdom. Apply today


TECHM PRODUCTS


TOP POSTS
STAY IN TOUCH
SPONSORED ADS
Subscribe to Updates

Get the latest creative news from FooBar about art, design and business.

Subscribe