Azure Functions — Blue/Green Deployment with Slots
Staging Slot Deploy, Swap, Smoke Test Pattern
Introduction
Blue-green deployment is a release strategy that eliminates downtime by maintaining two identical production environments. One (blue) serves the current production traffic while the new version (green) is deployed to a staging environment for testing. Once verified, traffic is swapped to the green environment.
Azure Functions Deployment Slots provide this capability out of the box, allowing you to:
- Test changes in a production-equivalent environment
- Warm up the new version before going live
- Instant rollback if issues are detected
- Zero-downtime deployments
Architecture Overview
Before Deployment
┌─────────────────────────────────┐
│ Load Balancer │
│ (Traffic) │
└──────────────┬──────────────────┘
│
┌──────────────▼──────────────────┐
│ PRODUCTION SLOT (Blue) │
│ Function App v1.0 │
│ Traffic: 100% │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ STAGING SLOT (Green) │
│ Function App v2.0 │
│ Traffic: 0% │
└─────────────────────────────────┘
After Successful Swap
┌─────────────────────────────────┐
│ Load Balancer │
│ (Traffic) │
└──────────────┬──────────────────┘
│
┌──────────────▼──────────────────┐
│ PRODUCTION SLOT (Blue) │◀── Now v2.0
│ Function App v2.0 │
│ Traffic: 100% │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ STAGING SLOT (Green) │◀── Now v1.0
│ Function App v1.0 │
│ Traffic: 0% │
└─────────────────────────────────┘
Setting Up Deployment Slots
Create a Staging Slot
# Using Azure CLI
az functionapp deployment slot create \
--name my-function-app \
--resource-group my-rg \
--slot staging
Configure Slot with Different Settings
# Set staging-specific app settings
az functionapp config appsettings set \
--name my-function-app \
--resource-group my-rg \
--slot staging \
--settings \
"AzureWebJobsStorage=staging-storage-connection" \
"WEBSITE_NODE_DEFAULT_VERSION=~18" \
"APPINSIGHTS_INSTRUMENTATIONKEY=staging-instrumentation-key" \
"ENVIRONMENT=Staging"
Using ARM Template
{
"type": "Microsoft.Web/sites/slots",
"name": "[concat(parameters('functionAppName'), '/staging')]",
"apiVersion": "2021-02-01",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]"
],
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]"
}
}
Deployment Strategies
Strategy 1: Direct Swap (Instant)
# Deploy to staging
az functionapp deployment source config-zip \
--name my-function-app \
--resource-group my-rg \
--slot staging \
--src-url "https://storage.blob.core.windows.net/container/v2.0.zip"
# Swap to production
az functionapp deployment slot swap \
--name my-function-app \
--resource-group my-rg \
--slot staging \
--target-slot production
Strategy 2: Staged Swap with Traffic
# Swap with 10% traffic to new version first
az functionapp deployment slot swap \
--name my-function-app \
--resource-group my-rg \
--slot staging \
--target-slot production \
--action-type SwapWithPreview \
--preview-percentage 10
Strategy 3: Manual Routing
# Route percentage of traffic to staging
az functionapp deployment slot auto-swap \
--name my-function-app \
--resource-group my-rg \
--slot staging \
--target-slot production
GitHub Actions for Slot Deployment
Workflow Configuration
name: Deploy to Staging
on:
push:
branches:
- develop
jobs:
deploy-staging:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release
- name: Publish
run: dotnet publish --configuration Release --output ./publish
- name: Deploy to Staging Slot
uses: Azure/functions-action@v1
with:
app-name: my-function-app
slot-name: staging
package: ./publish
publish-profile: ${{ secrets.STAGING_PUBLISH_PROFILE }}
inline-arguments: |
--name my-function-app \
--resource-group my-rg \
--slot staging
Complete CI/CD Pipeline
name: Production Deployment
on:
push:
branches:
- main
paths:
- 'src/**'
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build and Test
run: |
dotnet restore
dotnet build
dotnet test --verbosity minimal
deploy-staging:
needs: build-and-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to Staging
uses: Azure/functions-action@v1
with:
app-name: ${{ secrets.APP_NAME }}
slot-name: staging
package: ./publish
publish-profile: ${{ secrets.STAGING_PUBLISH_PROFILE }}
- name: Wait for Staging
run: sleep 30
smoke-test:
needs: deploy-staging
runs-on: ubuntu-latest
steps:
- name: Run Smoke Tests
run: |
# Test staging endpoint
STAGING_URL="https://my-function-app-staging.azurewebsites.net/api/health"
curl -f "$STAGING_URL" || exit 1
swap-to-production:
needs: smoke-test
runs-on: ubuntu-latest
steps:
- name: Swap Slots
uses: Azure/functions-action@v1
with:
app-name: ${{ secrets.APP_NAME }}
slot-name: staging
target-slot: production
action: swap
Implementing Warm-Up Endpoints
Health Check Endpoint
[FunctionName("HealthCheck")]
public static IActionResult HealthCheck(
[HttpTrigger] HttpTriggerOptions options,
ILogger log)
{
log.LogInformation("Health check called");
// Check dependencies
var healthy = true;
var checks = new Dictionary<string, object>();
try
{
// Check database
// using var conn = new SqlConnection(connectionString);
// await conn.ExecuteScalarAsync("SELECT 1");
checks["database"] = "healthy";
}
catch (Exception ex)
{
checks["database"] = $"unhealthy: {ex.Message}";
healthy = false;
}
try
{
// Check Redis
// var conn = await _redis.GetConnectionAsync();
checks["cache"] = "healthy";
}
catch (Exception ex)
{
checks["cache"] = $"degraded: {ex.Message}";
}
return healthy
? new OkObjectResult(new { status = "healthy", checks })
: new ObjectResult(new { status = "unhealthy", checks })
{ StatusCode = 503 };
}
Warm-Up Endpoint for Slot
[FunctionName("WarmUp")]
public static async Task WarmUp(
[HttpTrigger] HttpTriggerOptions options,
ILogger log)
{
log.LogInformation("Warm-up triggered - initializing function");
// Initialize any lazy-loaded resources
// This forces the first invocation to happen during swap
// Pre-load commonly used data
await _cache.WarmUpAsync();
// Verify connections
await _serviceBusClient.GetPropertiesAsync();
await _blobClient.GetPropertiesAsync();
log.LogInformation("Warm-up complete");
return new OkObjectResult(new { status = "ready" });
}
Configure Auto-Warmup
{
"functionAppConfiguration": {
"autoSwapSlotName": "production",
"warmupPath": "/api/warmup"
}
}
Smoke Testing After Swap
Automated Smoke Tests
[FunctionName("PostDeploymentTests")]
public static async Task PostDeploymentTests(
[TimerTrigger("0 0 * * * *")] TimerInfo timer,
ILogger log)
{
var baseUrl = "https://my-function-app.azurewebsites.net";
var testResults = new List<TestResult>();
// Test 1: Health endpoint
var healthResult = await RunTestAsync(async () =>
{
var client = new HttpClient();
var response = await client.GetAsync($"{baseUrl}/api/health");
return response.IsSuccessStatusCode;
}, "Health Check");
testResults.Add(healthResult);
// Test 2: Process a test message
var processResult = await RunTestAsync(async () =>
{
var client = new HttpClient();
var content = new { testData = "smoke-test" };
var response = await client.PostAsJsonAsync($"{baseUrl}/api/process", content);
return response.IsSuccessStatusCode;
}, "Message Processing");
testResults.Add(processResult);
// Test 3: Verify data in storage
var storageResult = await RunTestAsync(async () =>
{
var blobClient = _blobClient.GetBlobClient("test-result.txt");
return await blobClient.ExistsAsync();
}, "Storage Verification");
testResults.Add(storageResult);
// Alert if any test failed
var failedTests = testResults.Where(t => !t.Passed).ToList();
if (failedTests.Any())
{
log.LogError("Smoke tests failed: {FailedTests}",
string.Join(", ", failedTests.Select(t => t.Name)));
// Send alert
await SendAlertAsync(failedTests);
}
log.LogInformation("Smoke tests completed. Passed: {Passed}/{Total}",
testResults.Count(t => t.Passed), testResults.Count);
}
Rollback Strategy
Automatic Rollback on Failure
name: Deploy with Auto-Rollback
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to Staging
uses: Azure/functions-action@v1
with:
app-name: my-function-app
slot-name: staging
package: ./publish
publish-profile: ${{ secrets.STAGING_PUBLISH_PROFILE }}
- name: Verify Deployment
run: |
# Run quick validation
curl -f "https://my-function-app-staging.azurewebsites.net/api/health" || exit 1
- name: Swap to Production
uses: Azure/functions-action@v1
with:
app-name: my-function-app
slot-name: staging
action: swap
- name: Monitor for Errors
run: |
# Wait 5 minutes and check for errors
sleep 300
# Check Application Insights
az monitor app-insights query \
--app my-function-app \
--query "requests | where timestamp > ago(5m) | where success == false" \
--analytics \
| tee /tmp/errors.txt
if [ -s /tmp/errors.txt ]; then
echo "Errors detected - initiating rollback"
exit 1
fi
- name: Rollback on Failure
if: failure()
uses: Azure/functions-action@v1
with:
app-name: my-function-app
slot-name: production
action: swap
Manual Rollback
# If you need to rollback immediately:
# Swap back to previous version
az functionapp deployment slot swap \
--name my-function-app \
--resource-group my-rg \
--slot production \
--target-slot staging
# Alternative: Deploy previous version to staging, then swap
az functionapp deployment source config-zip \
--name my-function-app \
--resource-group my-rg \
--slot staging \
--src-url "https://storage.blob.core.windows.net/container/v1.0.zip"
Slot-Specific App Settings
Settings That Stick with Slot
# These will stay with the slot (don't swap)
az functionapp config appsettings set \
--name my-function-app \
--slot staging \
--settings \
"AzureWebJobsStorage=staging-connection" \
"APPINSIGHTS_INSTRUMENTATIONKEY=staging-key" \
"ENVIRONMENT=Staging" \
--slot-settings "AzureWebJobsStorage,APPINSIGHTS_INSTRUMENTATIONKEY,ENVIRONMENT"
Settings That Swap
# These will swap between slots
az functionapp config appsettings set \
--name my-function-app \
--slot staging \
--settings \
"FeatureFlag=NewFeature" \
"LogoUrl=https://staging.logo.png"
Best Practices Summary
| Practice | Description |
|---|---|
| Use separate storage | Staging should have its own storage account |
| Configure slot-specific settings | Environment-specific configs stay with slot |
| Implement warm-up endpoint | Pre-warm function before traffic hits |
| Run smoke tests | Verify functionality after swap |
| Enable auto-swap | Configure for automatic swap in production |
| Monitor closely | Watch Application Insights after deployment |
| Have rollback plan | Know how to revert quickly |
Troubleshooting
| Issue | Solution |
|---|---|
| Swap fails | Check app settings compatibility |
| Functions cold after swap | Use warm-up endpoint |
| Tests fail on staging | Ensure staging has correct config |
| Cannot connect to resources | Verify VNet/Private Endpoint |
Azure Integration Hub - Advanced Level