Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

README.md

Microservices Architecture - Container App Example

⏱️ Estimated Time: 25-35 minutes | 💰 Estimated Cost: ~$50-100/month | ⭐ Complexity: Advanced

A simplified but functional microservices architecture deployed to Azure Container Apps using AZD CLI. This example demonstrates service-to-service communication, container orchestration, and monitoring with a practical 2-service setup.

📚 Learning Approach: This example starts with a minimal 2-service architecture (API Gateway + Backend Service) that you can actually deploy and learn from. After mastering this foundation, we provide guidance for expanding to a full microservices ecosystem.

What You'll Learn

By completing this example, you will:

  • Deploy multiple containers to Azure Container Apps
  • Implement service-to-service communication with internal networking
  • Configure environment-based scaling and health checks
  • Monitor distributed applications with Application Insights
  • Understand microservices deployment patterns and best practices
  • Learn progressive expansion from simple to complex architectures

Architecture

Phase 1: What We're Building (Included in This Example)

graph TD
    Internet[Internet] -- HTTPS --> Gateway[API Gateway<br/>Node.js Container<br/>Routes requests<br/>Health checks<br/>Request logging]
    Gateway -- HTTP internal --> Product[Product Service<br/>Python Container<br/>Product CRUD<br/>In-memory data store<br/>REST API]
    Product --> Insights[Application Insights<br/>Monitoring & Logs]
Loading

Why Start Simple?

  • ✅ Deploy and understand quickly (25-35 minutes)
  • ✅ Learn core microservices patterns without complexity
  • ✅ Working code you can modify and experiment with
  • ✅ Lower cost for learning (~$50-100/month vs $300-1400/month)
  • ✅ Build confidence before adding databases and message queues

Analogy: Think of this like learning to drive. You start with an empty parking lot (2 services), master the basics, then progress to city traffic (5+ services with databases).

Phase 2: Future Expansion (Reference Architecture)

Once you master the 2-service architecture, you can expand to:

Full Architecture (Not Included - For Reference)
├── API Gateway (✅ Included)
├── Product Service (✅ Included)
├── Order Service (🔜 Add next)
├── User Service (🔜 Add next)
├── Notification Service (🔜 Add last)
├── Azure Service Bus (🔜 For async communication)
├── Cosmos DB (🔜 For product persistence)
├── Azure SQL (🔜 For order management)
└── Azure Storage (🔜 For file storage)

See "Expansion Guide" section at the end for step-by-step instructions.

Features Included

Service Discovery: Automatic DNS-based discovery between containers
Load Balancing: Built-in load balancing across replicas
Auto-scaling: Independent scaling per service based on HTTP requests
Health Monitoring: Liveness and readiness probes for both services
Distributed Logging: Centralized logging with Application Insights
Internal Networking: Secure service-to-service communication
Container Orchestration: Automatic deployment and scaling
Zero-Downtime Updates: Rolling updates with revision management

Prerequisites

Required Tools

Before starting, verify you have these tools installed:

  1. Azure Developer CLI (azd) (version 1.0.0 or higher)

    azd version
    # Expected output: azd version 1.0.0 or higher
  2. Azure CLI (version 2.50.0 or higher)

    az --version
    # Expected output: azure-cli 2.50.0 or higher
  3. Docker (for local development/testing - optional)

    docker --version
    # Expected output: Docker version 20.10 or higher

Azure Requirements

  • An active Azure subscription (create a free account)
  • Permissions to create resources in your subscription
  • Contributor role on the subscription or resource group

Knowledge Prerequisites

This is an advanced-level example. You should have:

  • Completed the Simple Flask API example
  • Basic understanding of microservices architecture
  • Familiarity with REST APIs and HTTP
  • Understanding of container concepts

New to Container Apps? Start with the Simple Flask API example first to learn the basics.

Quick Start (Step-by-Step)

Step 1: Clone and Navigate

git clone https://github.com/microsoft/AZD-for-beginners.git
cd AZD-for-beginners/examples/container-app/microservices

✓ Success Check: Verify you see azure.yaml:

ls
# Expected: README.md, azure.yaml, infra/, src/

Step 2: Authenticate with Azure

azd auth login

This opens your browser for Azure authentication. Sign in with your Azure credentials.

✓ Success Check: You should see:

Logged in to Azure.

Step 3: Initialize the Environment

azd init

Prompts you'll see:

  • Environment name: Enter a short name (e.g., microservices-dev)
  • Azure subscription: Select your subscription
  • Azure location: Choose a region (e.g., eastus, westeurope)

✓ Success Check: You should see:

SUCCESS: New project initialized!

Step 4: Deploy Infrastructure and Services

azd up

What happens (takes 8-12 minutes):

  1. Creates Container Apps environment
  2. Creates Application Insights for monitoring
  3. Builds API Gateway container (Node.js)
  4. Builds Product Service container (Python)
  5. Deploys both containers to Azure
  6. Configures networking and health checks
  7. Sets up monitoring and logging

✓ Success Check: You should see:

SUCCESS: Your application was deployed to Azure in X minutes Y seconds.
Endpoint: https://api-gateway-<unique-id>.azurecontainerapps.io

⏱️ Time: 8-12 minutes

Step 5: Test the Deployment

# Get the gateway endpoint
GATEWAY_URL=$(azd env get-values | grep API_GATEWAY_URL | cut -d '=' -f2 | tr -d '"')

# Test API Gateway health
curl $GATEWAY_URL/health

# Expected output:
# {"status":"healthy","service":"api-gateway","timestamp":"2025-11-19T10:30:00Z"}

Test product service through gateway:

# List products
curl $GATEWAY_URL/api/products

# Expected output:
# [
#   {"id":1,"name":"Laptop","price":999.99,"stock":50},
#   {"id":2,"name":"Mouse","price":29.99,"stock":200},
#   {"id":3,"name":"Keyboard","price":79.99,"stock":150}
# ]

✓ Success Check: Both endpoints return JSON data without errors.


🎉 Congratulations! You've deployed a microservices architecture to Azure!

Project Structure

All implementation files are included—this is a complete, working example:

microservices/
│
├── README.md                         # This file
├── azure.yaml                        # AZD configuration
├── .gitignore                        # Git ignore patterns
│
├── infra/                           # Infrastructure as Code (Bicep)
│   ├── main.bicep                   # Main orchestration
│   ├── abbreviations.json           # Naming conventions
│   ├── core/                        # Shared infrastructure
│   │   ├── container-apps-environment.bicep  # Container environment + registry
│   │   └── monitor.bicep            # Application Insights + Log Analytics
│   └── app/                         # Service definitions
│       ├── api-gateway.bicep        # API Gateway container app
│       └── product-service.bicep    # Product Service container app
│
└── src/                             # Application source code
    ├── api-gateway/                 # Node.js API Gateway
    │   ├── app.js                   # Express server with routing
    │   ├── package.json             # Node dependencies
    │   └── Dockerfile               # Container definition
    └── product-service/             # Python Product Service
        ├── main.py                  # Flask API with product data
        ├── requirements.txt         # Python dependencies
        └── Dockerfile               # Container definition

What Each Component Does:

Infrastructure (infra/):

  • main.bicep: Orchestrates all Azure resources and their dependencies
  • core/container-apps-environment.bicep: Creates the Container Apps environment and Azure Container Registry
  • core/monitor.bicep: Sets up Application Insights for distributed logging
  • app/*.bicep: Individual container app definitions with scaling and health checks

API Gateway (src/api-gateway/):

  • Public-facing service that routes requests to backend services
  • Implements logging, error handling, and request forwarding
  • Demonstrates service-to-service HTTP communication

Product Service (src/product-service/):

  • Internal service with product catalog (in-memory for simplicity)
  • REST API with health checks
  • Example of backend microservice pattern

Services Overview

API Gateway (Node.js/Express)

Port: 8080
Access: Public (external ingress)
Purpose: Routes incoming requests to appropriate backend services

Endpoints:

  • GET / - Service information
  • GET /health - Health check endpoint
  • GET /api/products - Forward to product service (list all)
  • GET /api/products/:id - Forward to product service (get by ID)

Key Features:

  • Request routing with axios
  • Centralized logging
  • Error handling and timeout management
  • Service discovery via environment variables
  • Application Insights integration

Code Highlight (src/api-gateway/app.js):

// Internal service communication
app.get('/api/products', async (req, res) => {
  const response = await axios.get(`${PRODUCT_SERVICE_URL}/products`);
  res.json(response.data);
});

Product Service (Python/Flask)

Port: 8000
Access: Internal only (no external ingress)
Purpose: Manages product catalog with in-memory data

Endpoints:

  • GET / - Service information
  • GET /health - Health check endpoint
  • GET /products - List all products
  • GET /products/<id> - Get product by ID

Key Features:

  • RESTful API with Flask
  • In-memory product store (simple, no database needed)
  • Health monitoring with probes
  • Structured logging
  • Application Insights integration

Data Model:

{
  "id": 1,
  "name": "Laptop",
  "description": "High-performance laptop",
  "price": 999.99,
  "stock": 50
}

Why Internal Only? The product service is not exposed publicly. All requests must go through the API Gateway, which provides:

  • Security: Controlled access point
  • Flexibility: Can change backend without affecting clients
  • Monitoring: Centralized request logging

Understanding Service Communication

How Services Talk to Each Other

In this example, the API Gateway communicates with the Product Service using internal HTTP calls:

// API Gateway (src/api-gateway/app.js)
const PRODUCT_SERVICE_URL = process.env.PRODUCT_SERVICE_URL;

// Make internal HTTP request
const response = await axios.get(`${PRODUCT_SERVICE_URL}/products`);

Key Points:

  1. DNS-Based Discovery: Container Apps automatically provides DNS for internal services

    • Product Service FQDN: product-service.internal.<environment>.azurecontainerapps.io
    • Simplified as: http://product-service (Container Apps resolves it)
  2. No Public Exposure: Product Service has external: false in Bicep

    • Only accessible within the Container Apps environment
    • Cannot be reached from the internet
  3. Environment Variables: Service URLs are injected at deployment time

    • Bicep passes the internal FQDN to the gateway
    • No hardcoded URLs in application code

Analogy: Think of this like office rooms. The API Gateway is the reception desk (public-facing), and the Product Service is an office room (internal only). Visitors must go through reception to reach any office.

Deployment Options

Full Deployment (Recommended)

# Deploy infrastructure and both services
azd up

This deploys:

  1. Container Apps environment
  2. Application Insights
  3. Container Registry
  4. API Gateway container
  5. Product Service container

Time: 8-12 minutes

Deploy Individual Service

# Deploy only one service (after initial azd up)
azd deploy api-gateway

# Or deploy product service
azd deploy product-service

Use Case: When you've updated code in one service and want to redeploy only that service.

Update Configuration

# Change scaling parameters
azd env set GATEWAY_MAX_REPLICAS 30

# Redeploy with new configuration
azd up

Configuration

Scaling Configuration

Both services are configured with HTTP-based autoscaling in their Bicep files:

API Gateway:

  • Min replicas: 2 (always at least 2 for availability)
  • Max replicas: 20
  • Scale trigger: 50 concurrent requests per replica

Product Service:

  • Min replicas: 1 (can scale to zero if needed)
  • Max replicas: 10
  • Scale trigger: 100 concurrent requests per replica

Customize Scaling (in infra/app/*.bicep):

scale: {
  minReplicas: 1
  maxReplicas: 10
  rules: [
    {
      name: 'http-scale-rule'
      http: {
        metadata: {
          concurrentRequests: '100'  // Adjust this
        }
      }
    }
  ]
}

Resource Allocation

API Gateway:

  • CPU: 1.0 vCPU
  • Memory: 2 GiB
  • Reason: Handles all external traffic

Product Service:

  • CPU: 0.5 vCPU
  • Memory: 1 GiB
  • Reason: Lightweight in-memory operations

Health Checks

Both services include liveness and readiness probes:

probes: [
  {
    type: 'Liveness'
    httpGet: {
      path: '/health'
      port: 8080
    }
    initialDelaySeconds: 10
    periodSeconds: 30
  }
  {
    type: 'Readiness'
    httpGet: {
      path: '/health'
      port: 8080
    }
    initialDelaySeconds: 5
    periodSeconds: 10
  }
]

What This Means:

  • Liveness: If health check fails, Container Apps restarts the container
  • Readiness: If not ready, Container Apps stops routing traffic to that replica

Monitoring & Observability

View Service Logs

# View logs using azd monitor
azd monitor --logs

# Or use Azure CLI for specific Container Apps:
# Stream logs from API Gateway
az containerapp logs show --name api-gateway --resource-group $RG_NAME --follow

# View recent product service logs
az containerapp logs show --name product-service --resource-group $RG_NAME --tail 100

Expected Output:

[api-gateway] API Gateway listening on port 8080
[api-gateway] Product Service URL: http://product-service
[api-gateway] GET /api/products 200 - 45ms
[product-service] Retrieved 5 products

Application Insights Queries

Access Application Insights in Azure Portal, then run these queries:

Find Slow Requests:

requests
| where timestamp > ago(1h)
| where duration > 1000  // Requests taking >1 second
| summarize count() by name, cloud_RoleName
| order by count_ desc

Track Service-to-Service Calls:

dependencies
| where timestamp > ago(1h)
| where type == "Http"
| project timestamp, name, target, duration, success
| order by timestamp desc

Error Rate by Service:

exceptions
| where timestamp > ago(24h)
| summarize errorCount = count() by cloud_RoleName, type
| order by errorCount desc

Request Volume Over Time:

requests
| where timestamp > ago(1h)
| summarize requestCount = count() by bin(timestamp, 5m), cloud_RoleName
| render timechart

Access Monitoring Dashboard

# Get Application Insights details
azd env get-values | grep APPLICATIONINSIGHTS

# Open Azure Portal monitoring
az monitor app-insights component show \
  --app $(azd env get-values | grep APPLICATIONINSIGHTS_CONNECTION_STRING | cut -d '=' -f2) \
  --resource-group $(azd env get-values | grep AZURE_RESOURCE_GROUP | cut -d '=' -f2) \
  --query "appId" -o tsv

Live Metrics

  1. Navigate to Application Insights in Azure Portal
  2. Click "Live Metrics"
  3. See real-time requests, failures, and performance
  4. Test by running: curl $(azd env get-values | grep API_GATEWAY_URL | cut -d '=' -f2 | tr -d '"')/api/products

Practical Exercises

[Note: See full exercises above in the "Practical Exercises" section for detailed step-by-step exercises including deployment verification, data modification, autoscaling tests, error handling, and adding a third service.]

Cost Analysis

Estimated Monthly Costs (For This 2-Service Example)

Resource Configuration Estimated Cost
API Gateway 2-20 replicas, 1 vCPU, 2GB RAM $30-150
Product Service 1-10 replicas, 0.5 vCPU, 1GB RAM $15-75
Container Registry Basic tier $5
Application Insights 1-2 GB/month $5-10
Log Analytics 1 GB/month $3
Total $58-243/month

Cost Breakdown by Usage:

  • Light traffic (testing/learning): ~$60/month
  • Moderate traffic (small production): ~$120/month
  • High traffic (busy periods): ~$240/month

Cost Optimization Tips

  1. Scale to Zero for Development:

    scale: {
      minReplicas: 0  // Save $30-40/month when not in use
      maxReplicas: 10
    }
  2. Use Consumption Plan for Cosmos DB (when you add it):

    • Pay only for what you use
    • No minimum charge
  3. Set Application Insights Sampling:

    appInsights.defaultClient.config.samplingPercentage = 50; // Sample 50% of requests
  4. Clean Up When Not Needed:

    azd down

Free Tier Options

For learning/testing, consider:

  • Use Azure free credits (first 30 days)
  • Keep to minimum replicas
  • Delete after testing (no ongoing charges)

Cleanup

To avoid ongoing charges, delete all resources:

azd down --force --purge

Confirmation Prompt:

? Total resources to delete: 6, are you sure you want to continue? (y/N)

Type y to confirm.

What Gets Deleted:

  • Container Apps Environment
  • Both Container Apps (gateway & product service)
  • Container Registry
  • Application Insights
  • Log Analytics Workspace
  • Resource Group

✓ Verify Cleanup:

az group list --query "[?starts_with(name,'rg-microservices')]" --output table

Should return empty.


Expansion Guide: From 2 to 5+ Services

Once you've mastered this 2-service architecture, here's how to expand:

Phase 1: Add Database Persistence (Next Step)

Add Cosmos DB for Product Service:

  1. Create infra/core/cosmos.bicep:

    resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = {
      name: name
      location: location
      kind: 'GlobalDocumentDB'
      properties: {
        databaseAccountOfferType: 'Standard'
        locations: [{ locationName: location, failoverPriority: 0 }]
      }
    }
  2. Update product service to use Cosmos DB instead of in-memory data

  3. Estimated additional cost: ~$25/month (serverless)

Phase 2: Add Third Service (Order Management)

Create Order Service:

  1. New folder: src/order-service/ (Python/Node.js/C#)
  2. New Bicep: infra/app/order-service.bicep
  3. Update API Gateway to route /api/orders
  4. Add Azure SQL Database for order persistence

Architecture becomes:

API Gateway → Product Service (Cosmos DB)
           → Order Service (Azure SQL)

Phase 3: Add Async Communication (Service Bus)

Implement Event-Driven Architecture:

  1. Add Azure Service Bus: infra/core/servicebus.bicep
  2. Product Service publishes "ProductCreated" events
  3. Order Service subscribes to product events
  4. Add Notification Service to process events

Pattern: Request/Response (HTTP) + Event-Driven (Service Bus)

Phase 4: Add User Authentication

Implement User Service:

  1. Create src/user-service/ (Go/Node.js)
  2. Add Azure AD B2C or custom JWT authentication
  3. API Gateway validates tokens
  4. Services check user permissions

Phase 5: Production Readiness

Add These Components:

  • Azure Front Door (global load balancing)
  • Azure Key Vault (secret management)
  • Azure Monitor Workbooks (custom dashboards)
  • CI/CD Pipeline (GitHub Actions)
  • Blue-Green Deployments
  • Managed Identity for all services

Full Production Architecture Cost: ~$300-1,400/month


Learn More

Related Documentation

Next Steps in This Course

Comparison: When to Use What

Single Container App (Simple Flask API example):

  • ✅ Simple applications
  • ✅ Monolithic architecture
  • ✅ Fast to deploy
  • ❌ Limited scalability
  • Cost: ~$15-50/month

Microservices (This example):

  • ✅ Complex applications
  • ✅ Independent scaling per service
  • ✅ Team autonomy (different services, different teams)
  • ❌ More complex to manage
  • Cost: ~$60-250/month

Kubernetes (AKS):

  • ✅ Maximum control and flexibility
  • ✅ Multi-cloud portability
  • ✅ Advanced networking
  • ❌ Requires Kubernetes expertise
  • Cost: ~$150-500/month minimum

Recommendation: Start with Container Apps (this example), move to AKS only if you need Kubernetes-specific features.


Frequently Asked Questions

Q: Why only 2 services instead of 5+?
A: Educational progression. Master the fundamentals (service communication, monitoring, scaling) with a simple example before adding complexity. The patterns you learn here apply to 100-service architectures.

Q: Can I add more services myself?
A: Absolutely! Follow the expansion guide above. Each new service follows the same pattern: create src folder, create Bicep file, update azure.yaml, deploy.

Q: Is this production-ready?
A: It's a solid foundation. For production, add: managed identity, Key Vault, persistent databases, CI/CD pipeline, monitoring alerts, and backup strategy.

Q: Why not use Dapr or other service mesh?
A: Keep it simple for learning. Once you understand native Container Apps networking, you can layer on Dapr for advanced scenarios.

Q: How do I debug locally?
A: Run services locally with Docker:

cd src/api-gateway
docker build -t local-gateway .
docker run -p 8080:8080 -e PRODUCT_SERVICE_URL=http://localhost:8000 local-gateway

Q: Can I use different programming languages?
A: Yes! This example shows Node.js (gateway) + Python (product service). You can mix any languages that run in containers.

Q: What if I don't have Azure credits?
A: Use Azure free tier (first 30 days with new accounts) or deploy for short testing periods and delete immediately.


🎓 Learning Path Summary: You've learned to deploy a multi-service architecture with automatic scaling, internal networking, centralized monitoring, and production-ready patterns. This foundation prepares you for complex distributed systems and enterprise microservices architectures.

📚 Course Navigation: