Welcome to another insightful session from Code Night New Zealand! In this special presentation, we dive deep into API Ops methodology and explore how to implement automated API deployments using both Azure DevOps and GitHub.
What is API Ops?
Understanding the Evolution: DevOps → GitOps → API Ops
API Ops is the natural evolution of DevOps practices specifically applied to the API development lifecycle. Let’s break down this progression:
DevOps Foundation
DevOps is a methodology that integrates and automates the work of software development and IT operations, binding together practices from different teams:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ DEVELOP │ │ VERIFY │ │ PACKAGE │
│ Planning │───▶│ Testing │───▶│ Release │
│ Coding │ │ Integration │ │ Deploy │
└─────────────┘ └─────────────┘ └─────────────┘
▲ │
│ ┌─────────────┐ │
│ │ CONFIGURE │ │
└────────────────┤ Monitor │◄─────┘
│ Operations │
└─────────────┘
GitOps Evolution
GitOps introduces version control as the single source of truth, with Git being the most popular choice. This creates a systematic approach to infrastructure and application management.
API Ops Definition
API Ops applies DevOps and GitOps practices specifically to the API development lifecycle, encompassing:
- API Design: OpenAPI specifications and contracts
- API Development: Implementation and testing
- API Deployment: Automated provisioning and configuration
- API Management: Policies, monitoring, and governance
- API Operations: Maintenance and continuous improvement
API Ops Applied to Azure API Management
The API Ops Workflow
┌─────────────────┐ ┌─────────────────────────────┐
│ API Developers │ │ API Operators │
│ │ │ │
│ • Design APIs │ │ • Create Policies │
│ • Create Specs │ │ • Configure Backends │
│ • Test APIs │ │ • Setup Monitoring │
└─────────┬───────┘ └─────────────┬───────────────┘
│ │
└──────────┐ ┌──────────┘
▼ ▼
┌─────────────────┐
│ Git Repository │
│ │
│ • API Specs │
│ • Policies │
│ • Configuration │
└─────────┬───────┘
│
▼
┌─────────────────┐
│ Automated PR │
│ Creation │
└─────────┬───────┘
│
▼
┌─────────────────┐
│ Code Review & │
│ Approval │
└─────────┬───────┘
│
▼
┌─────────────────┐
│ Merge to Main │
│ Branch │
└─────────┬───────┘
│
▼
┌─────────────────┐
│ Automated │
│ Deployment │
│ Pipeline │
└─────────┬───────┘
│
▼
┌─────────────────┐
│ API Management │
│ Instance │
└─────────┬───────┘
│
▼
┌─────────────────┐
│ Extract Current │◄──┐
│ State & Compare │ │
└─────────┬───────┘ │
│ │
▼ │
┌─────────┐ │
│Changes? │──────┘
│ Yes/No │
└─────────┘
Key Participants and Responsibilities
API Developers
- Design and create OpenAPI specifications
- Define API contracts and schemas
- Test API implementations
- Submit changes via pull requests
API Operators
- Create and manage API policies
- Configure backends and routing
- Set up monitoring and diagnostics
- Manage API governance and compliance
The API Ops Lifecycle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # Step 1: Check Git Repository
- name: Check Repository Content
condition: repository_empty
action: extract_current_state
# Step 2: Extract from API Management
- name: Extract API Management Configuration
pipeline: extractor
output: pull_request
# Step 3: Developer Changes
- name: Create API Specifications
actor: developers
output: pull_request
# Step 4: Review and Approval
- name: Code Review Process
reviewers: [api_operators, lead_developers]
gates: [spectral_validation, policy_compliance]
# Step 5: Automated Deployment
- name: Publish Changes
pipeline: publisher
environments: [dev, test, prod]
|
Scenario 2: Configuration-First Approach
1
2
3
4
5
6
7
8
| # Operators modify schemas and configurations
- name: Update API Policies
actor: operators
files: [policies/*.xml, backends/*.yml]
# Follow same PR process
- name: Standard Review Process
includes: [validation, approval, merge, deploy]
|
Azure DevOps Implementation
Prerequisites and Setup
Visit the API Ops GitHub Repository and download the latest release:
1
2
3
4
5
6
7
| # Extract the toolkit
unzip apiops-release.zip
# Available pipelines:
├── run-extractor.yml # Extracts current API Management state
├── run-publisher.yml # Publishes changes to API Management
└── run-publisher-with-env.yml # Multi-environment publisher
|
2. Service Connection Configuration
Recommended Approach: Use Workload Identity Federation (no secrets required)
1
2
3
4
5
6
7
8
9
| # Service Connection Settings
connectionType: workloadIdentityFederation
subscriptionId: $(AZURE_SUBSCRIPTION_ID)
resourceGroup: $(RESOURCE_GROUP_NAME)
servicePrincipal: auto-generated
# Required Permissions:
# - API Management Contributor
# - Resource Group Contributor (if creating resources)
|
Alternative: Service Principal with Certificate/Secret
1
2
3
4
| # Only use if Workload Identity Federation is not available
connectionType: servicePrincipal
authenticationType: spnCertificate
# or: spnKey for secret-based authentication
|
3. Repository Permissions
Configure the build service account to create pull requests:
1
2
3
4
| # Grant Contributor access to build service
Project Settings > Repositories > Security
Add: [Project Name] Build Service (organization-name)
Permissions: Contribute, Create branch, Create pull request
|
4. Environment Configuration
Set up approval gates for production deployments:
1
2
3
4
5
6
7
8
9
10
| # Environment Configuration
environments:
- name: Development
approvals: []
- name: Production
approvals:
- type: manual
approvers: [api-team@company.com]
timeout: 24h
|
5. Variable Groups
Create the apim-automation variable group:
1
2
3
4
5
6
| # Required Variables
API_MANAGEMENT_SERVICE_NAME: "your-apim-instance"
APIOPS_RELEASE_VERSION: "latest"
RESOURCE_GROUP_NAME: "your-resource-group"
REPOSITORY_NAME: "your-repo-name"
TARGET_FOLDER_NAME: "apim"
|
Pipeline Configuration Examples
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| trigger: none # Manual execution only
pool:
vmImage: 'ubuntu-latest'
variables:
- group: apim-automation
stages:
- stage: Extract
displayName: 'Extract API Management Configuration'
jobs:
- job: ExtractAPIM
displayName: 'Extract APIM Configuration'
steps:
- task: AzureCLI@2
displayName: 'Run API Ops Extractor'
inputs:
azureSubscription: $(AZURE_SERVICE_CONNECTION)
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# Download and run extractor
wget https://github.com/Azure/apiops/releases/latest/download/extractor.zip
unzip extractor.zip -d extractor
# Set permissions
chmod +x extractor/extractor
# Run extraction
./extractor/extractor \
--sourceApimName $(API_MANAGEMENT_SERVICE_NAME) \
--resourceGroupName $(RESOURCE_GROUP_NAME) \
--fileFolder $(TARGET_FOLDER_NAME) \
--commitAuthorName "API Ops Extractor" \
--commitAuthorEmail "apiops@company.com"
|
Publisher Pipeline (run-publisher.yml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
| trigger:
branches:
include:
- main
paths:
include:
- apim/*
pool:
vmImage: 'ubuntu-latest'
variables:
- group: apim-automation
stages:
- stage: Validate
displayName: 'Validate API Specifications'
jobs:
- job: SpectralValidation
displayName: 'Run Spectral API Linting'
steps:
- task: NodeTool@0
inputs:
versionSpec: '16.x'
- script: |
npm install -g @stoplight/spectral-cli
spectral lint "apim/apis/**/*.json" --ruleset .spectral.yml
displayName: 'Validate OpenAPI Specs'
- stage: DeployDev
displayName: 'Deploy to Development'
dependsOn: Validate
jobs:
- deployment: PublishToDev
environment: Development
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
displayName: 'Publish to Development'
inputs:
azureSubscription: $(AZURE_SERVICE_CONNECTION)
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# Run publisher for development
./publisher/publisher \
--targetApimName $(API_MANAGEMENT_SERVICE_NAME) \
--resourceGroupName $(RESOURCE_GROUP_NAME) \
--fileFolder $(TARGET_FOLDER_NAME) \
--configurationFile configuration.dev.yml
- stage: DeployProd
displayName: 'Deploy to Production'
dependsOn: DeployDev
jobs:
- deployment: PublishToProd
environment: Production
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
displayName: 'Publish to Production'
inputs:
azureSubscription: $(AZURE_SERVICE_CONNECTION)
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# Run publisher for production
./publisher/publisher \
--targetApimName $(API_MANAGEMENT_SERVICE_NAME_PROD) \
--resourceGroupName $(RESOURCE_GROUP_NAME_PROD) \
--fileFolder $(TARGET_FOLDER_NAME) \
--configurationFile configuration.prod.yml
|
GitHub Implementation
Prerequisites and Setup
1
2
3
4
5
6
7
8
| # Download GitHub Actions version
curl -L https://github.com/Azure/apiops/releases/latest/download/apiops-github.zip -o apiops-github.zip
unzip apiops-github.zip
# Available workflows:
├── .github/workflows/
│ ├── extract.yml # Extractor workflow
│ └── publish.yml # Publisher workflow
|
2. Azure Service Principal Setup
Create service principals for each environment:
1
2
3
4
5
6
7
8
9
10
11
| # Create service principal for development
az ad sp create-for-rbac --name "api-ops-dev" \
--role "API Management Service Contributor" \
--scopes /subscriptions/{subscription-id}/resourceGroups/{dev-rg} \
--sdk-auth
# Create service principal for production
az ad sp create-for-rbac --name "api-ops-prod" \
--role "API Management Service Contributor" \
--scopes /subscriptions/{subscription-id}/resourceGroups/{prod-rg} \
--sdk-auth
|
3. GitHub Secrets Configuration
Store the service principal output as GitHub secrets:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # Repository Secrets
AZURE_CREDENTIALS_DEV: |
{
"clientId": "xxx",
"clientSecret": "xxx",
"subscriptionId": "xxx",
"tenantId": "xxx"
}
AZURE_CREDENTIALS_PROD: |
{
"clientId": "xxx",
"clientSecret": "xxx",
"subscriptionId": "xxx",
"tenantId": "xxx"
}
|
4. Environment Configuration
Set up GitHub environments with protection rules:
1
2
3
4
5
6
7
8
9
10
11
12
| # .github/environments/development.yml
name: Development
protection_rules: []
# .github/environments/production.yml
name: Production
protection_rules:
- type: required_reviewers
required_reviewers:
- api-team
- type: wait_timer
wait_timer: 5 # minutes
|
Note: Protection rules require GitHub Enterprise. For GitHub Pro/Team, use manual workflow triggers.
5. Workflow Permissions
Enable GitHub Actions to create pull requests:
1
2
3
4
5
| # Repository Settings > Actions > General
permissions:
contents: write
pull-requests: write
actions: read
|
GitHub Workflows Examples
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
| name: Extract API Management Configuration
on:
workflow_dispatch:
inputs:
apimServiceName:
description: 'APIM Service Name'
required: true
resourceGroupName:
description: 'Resource Group Name'
required: true
jobs:
extract:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS_DEV }}
- name: Download API Ops Extractor
run: |
wget https://github.com/Azure/apiops/releases/latest/download/extractor.zip
unzip extractor.zip -d tools
chmod +x tools/extractor
- name: Run Extractor
run: |
./tools/extractor \
--sourceApimName ${{ github.event.inputs.apimServiceName }} \
--resourceGroupName ${{ github.event.inputs.resourceGroupName }} \
--fileFolder apim \
--commitAuthorName "API Ops Bot" \
--commitAuthorEmail "apiops@company.com"
env:
AZURE_CLIENT_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).clientId }}
AZURE_CLIENT_SECRET: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).clientSecret }}
AZURE_SUBSCRIPTION_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).subscriptionId }}
AZURE_TENANT_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).tenantId }}
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'Extract API Management configuration'
title: 'API Management Configuration Update'
body: |
## API Management Configuration Extract
This PR contains the latest configuration extracted from API Management instance: `${{ github.event.inputs.apimServiceName }}`
### Changes Include:
- API definitions and specifications
- Backend configurations
- Policy definitions
- Product and subscription settings
Please review and merge to sync the repository with the current APIM state.
branch: apiops/extract-config
delete-branch: true
|
Publisher Workflow (.github/workflows/publish.yml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
| name: Publish to API Management
on:
push:
branches:
- main
paths:
- 'apim/**'
pull_request:
branches:
- main
paths:
- 'apim/**'
jobs:
validate:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install Spectral
run: npm install -g @stoplight/spectral-cli
- name: Validate OpenAPI Specifications
run: |
spectral lint "apim/apis/**/*.json" --ruleset .spectral.yml --format junit --output spectral-report.xml
- name: Upload Spectral Report
uses: actions/upload-artifact@v3
if: always()
with:
name: spectral-report
path: spectral-report.xml
deploy-dev:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
environment: Development
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS_DEV }}
- name: Download API Ops Publisher
run: |
wget https://github.com/Azure/apiops/releases/latest/download/publisher.zip
unzip publisher.zip -d tools
chmod +x tools/publisher
- name: Publish to Development
run: |
./tools/publisher \
--targetApimName ${{ vars.APIM_SERVICE_NAME_DEV }} \
--resourceGroupName ${{ vars.RESOURCE_GROUP_NAME_DEV }} \
--fileFolder apim \
--configurationFile configuration.dev.yml
env:
AZURE_CLIENT_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).clientId }}
AZURE_CLIENT_SECRET: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).clientSecret }}
AZURE_SUBSCRIPTION_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).subscriptionId }}
AZURE_TENANT_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).tenantId }}
deploy-prod:
runs-on: ubuntu-latest
needs: deploy-dev
environment: Production
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS_PROD }}
- name: Download API Ops Publisher
run: |
wget https://github.com/Azure/apiops/releases/latest/download/publisher.zip
unzip publisher.zip -d tools
chmod +x tools/publisher
- name: Publish to Production
run: |
./tools/publisher \
--targetApimName ${{ vars.APIM_SERVICE_NAME_PROD }} \
--resourceGroupName ${{ vars.RESOURCE_GROUP_NAME_PROD }} \
--fileFolder apim \
--configurationFile configuration.prod.yml
env:
AZURE_CLIENT_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_PROD).clientId }}
AZURE_CLIENT_SECRET: ${{ fromJson(secrets.AZURE_CREDENTIALS_PROD).clientSecret }}
AZURE_SUBSCRIPTION_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_PROD).subscriptionId }}
AZURE_TENANT_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_PROD).tenantId }}
|
Configuration Management Best Practices
Environment-Specific Configuration Files
API Ops supports environment-specific configurations to handle differences between Dev, Test, and Production environments.
Configuration Structure
apim/
├── apis/
├── backends/
├── policies/
├── products/
├── configuration.dev.yml # Development overrides
├── configuration.test.yml # Test overrides
└── configuration.prod.yml # Production overrides
Development Configuration (configuration.dev.yml)
1
2
3
4
5
6
7
8
9
10
11
12
| # configuration.dev.yml
namedValues:
- name: instrumentation-key
value: "dev-app-insights-key"
backends:
- name: api-todo-backend
url: "https://api-dev.company.com"
diagnostics:
- name: applicationinsights
loggerId: "/subscriptions/{sub-id}/resourceGroups/dev-rg/providers/Microsoft.Insights/components/dev-appinsights"
|
Production Configuration (configuration.prod.yml)
1
2
3
4
5
6
7
8
9
10
11
12
| # configuration.prod.yml
namedValues:
- name: instrumentation-key
value: "prod-app-insights-key"
backends:
- name: api-todo-backend
url: "https://api-prod.company.com"
diagnostics:
- name: applicationinsights
loggerId: "/subscriptions/{sub-id}/resourceGroups/prod-rg/providers/Microsoft.Insights/components/prod-appinsights"
|
Backend Configuration Example
Backend Definition (backends/api-todo-backend.yml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # backends/api-todo-backend.yml
name: api-todo-backend
title: "API Todo Backend Service"
description: "Backend service for Todo API operations"
url: "https://localhost:5000" # Default/template URL
protocol: http
credentials:
authorization:
scheme: bearer
parameter: "{{backend-auth-token}}"
proxy:
url: "{{proxy-url}}"
username: "{{proxy-username}}"
password: "{{proxy-password}}"
tls:
validateCertificateChain: true
validateCertificateName: true
|
Policy Fragment Management
Correlation ID Policy Fragment (policies/correlation-id-fragment.xml)
1
2
3
4
5
6
7
8
9
10
| <!-- policies/correlation-id-fragment.xml -->
<fragment>
<choose>
<when condition="@(!context.Request.Headers.ContainsKey("correlation-id"))">
<set-header name="correlation-id" exists-action="skip">
<value>@(Guid.NewGuid().ToString())</value>
</set-header>
</when>
</choose>
</fragment>
|
API Policy Using Fragment (apis/todo-api/policy.xml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| <!-- apis/todo-api/policy.xml -->
<policies>
<inbound>
<base />
<include-fragment fragment-id="correlation-id-fragment" />
<set-backend-service backend-id="api-todo-backend" />
<rate-limit calls="100" renewal-period="60" />
<quota calls="1000" renewal-period="86400" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
<set-header name="X-Powered-By" exists-action="override">
<value>Azure API Management</value>
</set-header>
</outbound>
<on-error>
<base />
<trace source="api-error">
<message>@($"Error occurred: {context.LastError.Message}")</message>
</trace>
</on-error>
</policies>
|
Advanced Features and Integration
Spectral API Linting Integration
Spectral Configuration (.spectral.yml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| # .spectral.yml
extends: ["@stoplight/spectral/dist/rulesets/oas/index.json"]
rules:
# Enforce API versioning
api-version-required:
description: "API must have version in path"
given: "$.paths.*~"
then:
function: pattern
functionOptions:
match: "^/v[0-9]+/"
# Enforce correlation ID header
correlation-id-header:
description: "All operations must accept correlation-id header"
given: "$.paths.*.*.parameters[?(@.in === 'header')]"
then:
function: schema
functionOptions:
schema:
type: object
properties:
name:
enum: ["correlation-id", "x-correlation-id"]
# Enforce response schemas
response-schema-required:
description: "All success responses must have schema"
given: "$.paths.*.*.responses[?(@property.match(/^2/))]"
then:
field: "content.application/json.schema"
function: truthy
# Security requirements
security-defined:
description: "All operations must have security requirements"
given: "$.paths.*.*"
then:
field: "security"
function: truthy
|
Custom Spectral Rules for API Management
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # .spectral-apim.yml
extends: [".spectral.yml"]
rules:
# API Management specific rules
apim-policy-required:
description: "APIs should have rate limiting policies"
given: "$.paths.*.*"
then:
function: custom-apim-policy-check
apim-backend-required:
description: "APIs must reference a backend service"
given: "$.info"
then:
function: custom-backend-reference-check
apim-product-assignment:
description: "APIs should be assigned to products"
given: "$.info"
then:
function: custom-product-assignment-check
|
Visual Studio Code Extension Integration
For development productivity, use the Azure API Management extension for VS Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // .vscode/settings.json
{
"azure-api-management.subscriptionId": "${env:AZURE_SUBSCRIPTION_ID}",
"azure-api-management.resourceGroup": "${env:RESOURCE_GROUP_NAME}",
"azure-api-management.serviceName": "${env:API_MANAGEMENT_SERVICE_NAME}",
// Auto-format policy files
"files.associations": {
"*.policy.xml": "xml"
},
// Spectral linting
"spectral.enable": true,
"spectral.rulesetFile": ".spectral.yml"
}
|
Monitoring and Observability
Application Insights Integration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| # diagnostics/application-insights.yml
name: applicationinsights
alwaysLog: allErrors
httpCorrelationProtocol: W3C
verbosity: information
logClientIp: true
sampling:
samplingType: fixed
percentage: 100
frontend:
request:
headers: ["correlation-id", "user-agent"]
body:
bytes: 1024
response:
headers: ["content-type", "x-response-time"]
body:
bytes: 1024
backend:
request:
headers: ["correlation-id", "authorization"]
body:
bytes: 1024
response:
headers: ["content-type", "x-response-time"]
body:
bytes: 1024
|
Custom Metrics and Alerts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # monitoring/custom-metrics.yml
customMetrics:
- name: api-response-time
description: "API response time in milliseconds"
unit: milliseconds
aggregationType: average
- name: api-error-rate
description: "API error rate percentage"
unit: percentage
aggregationType: average
alerts:
- name: high-error-rate
description: "Alert when error rate exceeds 5%"
condition: "api-error-rate > 5"
severity: critical
- name: slow-response-time
description: "Alert when response time exceeds 2 seconds"
condition: "api-response-time > 2000"
severity: warning
|
Hands-On Demo: Complete API Deployment
Step 1: Create a New API
Let’s walk through deploying a complete Todo API with versioning and policies.
OpenAPI Specification (apis/todo-api/specification.json)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
| {
"openapi": "3.0.3",
"info": {
"title": "Todo API",
"description": "A simple Todo list API with correlation tracking",
"version": "1.0.0",
"contact": {
"name": "API Team",
"email": "api-team@company.com"
}
},
"servers": [
{
"url": "https://api.company.com/v1",
"description": "Production server"
}
],
"paths": {
"/todos": {
"get": {
"summary": "Get all todos",
"operationId": "getTodos",
"parameters": [
{
"name": "correlation-id",
"in": "header",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "Correlation ID for request tracing"
}
],
"responses": {
"200": {
"description": "List of todos",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Todo"
}
}
}
}
},
"400": {
"description": "Bad request - missing correlation ID"
}
},
"security": [
{
"apiKey": []
}
]
},
"post": {
"summary": "Create a new todo",
"operationId": "createTodo",
"parameters": [
{
"name": "correlation-id",
"in": "header",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateTodoRequest"
}
}
}
},
"responses": {
"201": {
"description": "Todo created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Todo"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Todo": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"title": {
"type": "string"
},
"completed": {
"type": "boolean"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateTodoRequest": {
"type": "object",
"required": ["title"],
"properties": {
"title": {
"type": "string",
"minLength": 1,
"maxLength": 100
}
}
}
},
"securitySchemes": {
"apiKey": {
"type": "apiKey",
"in": "header",
"name": "Ocp-Apim-Subscription-Key"
}
}
}
}
|
Development Backend (configuration.dev.yml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # Development environment backend configuration
backends:
- name: api-todo-backend
url: "https://todo-api-dev.company.com"
protocol: https
credentials:
header:
authorization: "Bearer {{dev-api-token}}"
tls:
validateCertificateChain: true
namedValues:
- name: dev-api-token
value: "dev-bearer-token-value"
secret: true
|
Production Backend (configuration.prod.yml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # Production environment backend configuration
backends:
- name: api-todo-backend
url: "https://todo-api-prod.company.com"
protocol: https
credentials:
header:
authorization: "Bearer {{prod-api-token}}"
tls:
validateCertificateChain: true
namedValues:
- name: prod-api-token
value: "prod-bearer-token-value"
secret: true
|
Step 3: Deploy and Test
Execute the Pipeline
1
2
3
4
5
6
7
8
9
| # For Azure DevOps
az pipelines run --name "API-Ops-Publisher" \
--variables API_MANAGEMENT_SERVICE_NAME=your-apim-instance \
--variables RESOURCE_GROUP_NAME=your-resource-group
# For GitHub Actions
gh workflow run publish.yml \
-f apimServiceName=your-apim-instance \
-f resourceGroupName=your-resource-group
|
Verify Deployment
1
2
3
4
5
6
| # Test the deployed API
curl -X GET "https://your-apim.azure-api.net/todo/v1/todos" \
-H "Ocp-Apim-Subscription-Key: your-subscription-key" \
-H "correlation-id: $(uuidgen)"
# Expected Response: 200 OK with todo list
|
Azure DevOps Advantages
✅ Seamless Azure Integration
- Native service connections with Workload Identity Federation
- Integrated with Azure Active Directory
- Built-in Azure resource management
✅ Enterprise Features
- Robust approval workflows and gates
- Advanced pipeline templates and inheritance
- Comprehensive audit and compliance tracking
✅ Mature DevOps Toolchain
- Integrated work item tracking
- Built-in test management
- Advanced reporting and analytics
GitHub Advantages
✅ Developer-Friendly Experience
- Familiar Git workflow and interface
- Excellent code review capabilities
- Strong community and ecosystem
✅ Advanced Automation
- Powerful GitHub Actions marketplace
- Flexible workflow triggers and conditions
- Easy integration with third-party tools
✅ Cost Effectiveness
- Generous free tier for public repositories
- Competitive pricing for private repositories
- No additional costs for basic CI/CD
Configuration Complexity Comparison
Azure DevOps
1
2
3
4
5
| # Easier setup for Azure-native scenarios
- Service Connection: ✅ One-click setup (with proper permissions)
- Variables: ✅ Centralized variable groups
- Environments: ✅ Built-in approval gates
- Permissions: ⚠️ Requires build service configuration
|
GitHub
1
2
3
4
5
| # More setup required but flexible
- Authentication: ⚠️ Manual service principal creation required
- Secrets: ✅ Easy secret management
- Environments: ⚠️ Protection rules require Enterprise license
- Permissions: ✅ Simple workflow permissions
|
Troubleshooting Common Issues
Azure DevOps Issues
Build Service Permissions Error
1
2
3
4
5
| # Error: TF401027: You need the Git 'CreateBranch' permission
# Solution: Grant contributor access to build service
Project Settings > Repositories > Security
Add: [Project] Build Service ([Organization])
Permissions: ✓ Contribute, ✓ Create branch, ✓ Create pull request
|
Service Connection Authentication Failed
1
2
3
4
5
6
7
| # Error: Forbidden - Invalid credentials
# Solutions:
1. Use Workload Identity Federation (recommended)
2. Verify service principal has proper RBAC roles:
- API Management Service Contributor
- Resource Group Contributor
3. Check subscription and tenant IDs are correct
|
Variable Group Not Found
1
2
3
4
5
6
7
| # Error: Could not find variable group 'apim-automation'
# Solution: Create variable group with required variables:
- API_MANAGEMENT_SERVICE_NAME
- APIOPS_RELEASE_VERSION
- RESOURCE_GROUP_NAME
- REPOSITORY_NAME
- TARGET_FOLDER_NAME
|
GitHub Issues
Workflow Permissions Error
1
2
3
4
5
| # Error: Resource not accessible by integration
# Solution: Enable workflow permissions
Settings > Actions > General > Workflow Permissions:
✓ Read and write permissions
✓ Allow GitHub Actions to create and approve pull requests
|
Authentication with Azure Failed
1
2
3
4
5
6
7
8
| # Error: Azure CLI login failed
# Solution: Verify Azure credentials secret format:
{
"clientId": "service-principal-app-id",
"clientSecret": "service-principal-secret",
"subscriptionId": "azure-subscription-id",
"tenantId": "azure-tenant-id"
}
|
Environment Protection Rules Not Working
1
2
3
| # Issue: No approval required for production deployment
# Solution: GitHub Enterprise required for protection rules
# Workaround: Use workflow_dispatch with manual triggers
|
Common API Ops Issues
Configuration File Syntax Errors
1
2
3
4
5
6
7
| # ❌ Common mistake: Using tabs instead of spaces
backends:
- name: api-backend # Tab character causes YAML parsing error
# ✅ Correct: Use spaces for indentation
backends:
- name: api-backend # 2-space indentation
|
Environment-Specific Values Not Applied
1
2
3
4
5
6
7
8
9
| # Issue: Development URLs appearing in production
# Root Cause: Missing configuration.prod.yml file
# Solution: Create environment-specific configuration files
# Verify configuration files exist:
ls -la apim/
├── configuration.dev.yml ✓
├── configuration.test.yml ✓
└── configuration.prod.yml ✓
|
Spectral Validation Failures
1
2
3
4
5
6
7
8
| # Error: OpenAPI specification validation failed
# Common issues:
1. Missing required headers (correlation-id)
2. No security definitions
3. Missing response schemas
4. Invalid API versioning in paths
# Solution: Update OpenAPI spec to match Spectral rules
|
Best Practices and Recommendations
🔒 Security Best Practices
1. Secrets Management
1
2
3
4
5
6
7
8
9
10
| # ✅ Use Key Vault references for sensitive values
namedValues:
- name: database-connection-string
keyVault:
secretIdentifier: "https://vault.vault.azure.net/secrets/db-conn-string"
# ❌ Never commit secrets in plain text
namedValues:
- name: api-key
value: "super-secret-key-123" # Never do this!
|
2. Least Privilege Access
1
2
3
4
| # Service Principal Permissions (minimum required)
- API Management Service Contributor # For APIM operations
- Key Vault Secrets User # For secret access (if using Key Vault)
- Reader # For resource group access
|
3. Network Security
1
2
3
4
5
6
| # Configure IP restrictions for management access
management:
restrictAccess: true
allowedIPs:
- "10.0.0.0/8" # Corporate network
- "192.168.1.0/24" # VPN network
|
1. Optimize Pipeline Execution
1
2
3
4
5
6
7
8
| # Use pipeline caching for faster builds
- task: Cache@2
inputs:
key: 'apiops | $(Agent.OS) | $(Build.SourceVersion)'
path: $(Pipeline.Workspace)/.apiops-cache
restoreKeys: |
apiops | $(Agent.OS)
apiops
|
2. Parallel Environment Deployments
1
2
3
4
5
6
7
8
| # Deploy non-production environments in parallel
strategy:
matrix:
development:
environment: 'dev'
testing:
environment: 'test'
maxParallel: 2
|
📊 Monitoring and Observability
1. Custom Dashboards
1
2
3
4
5
6
7
8
9
10
11
12
13
| {
"title": "API Ops Deployment Dashboard",
"widgets": [
{
"title": "Deployment Success Rate",
"query": "traces | where customDimensions.pipeline == 'api-ops-publisher' | summarize SuccessRate = avg(toint(customDimensions.success)) by bin(timestamp, 1d)"
},
{
"title": "API Response Times",
"query": "requests | where name contains 'todo-api' | summarize avg(duration) by bin(timestamp, 5m)"
}
]
}
|
2. Automated Testing Integration
1
2
3
4
5
6
7
8
| # Add API testing to pipeline
- task: Newman@4
displayName: 'Run API Tests'
inputs:
collectionFileSource: 'URL'
collectionURL: 'https://api.company.com/postman/todo-api-tests.json'
environmentFileSource: 'URL'
environmentURL: 'https://api.company.com/postman/environments/$(Environment).json'
|
📝 Documentation Standards
1. API Documentation Templates
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| # API Documentation Template
## Overview
Brief description of what this API does.
## Authentication
- **Type**: API Key
- **Header**: `Ocp-Apim-Subscription-Key`
- **Required**: Yes
## Rate Limiting
- **Calls**: 100 per minute
- **Quota**: 10,000 per day
## Headers
- `correlation-id` (required): UUID for request tracing
- `user-agent` (optional): Client identification
## Error Handling
All APIs return standardized error responses:
```json
{
"error": {
"code": "InvalidRequest",
"message": "The correlation-id header is required",
"correlationId": "12345678-1234-1234-1234-123456789012"
}
}
|
## Future Enhancements and Roadmap
### Upcoming API Ops Features
#### **1. Advanced Policy Templates**
```yaml
# Coming soon: Parameterized policy templates
policies:
- name: security-template
parameters:
- name: rateLimitCalls
type: integer
default: 100
- name: authScheme
type: string
default: "bearer"
template: |
<rate-limit calls="{{rateLimitCalls}}" renewal-period="60" />
<validate-jwt header-name="Authorization" scheme="{{authScheme}}">
<!-- JWT validation logic -->
</validate-jwt>
2. Multi-Cloud Support
1
2
3
4
5
6
7
8
9
| # Planned: Support for multiple cloud providers
environments:
- name: azure-prod
provider: azure
apimInstance: "azure-apim-prod"
- name: aws-prod
provider: aws
apiGateway: "aws-gateway-prod"
|
3. Enhanced Observability
1
2
3
4
5
6
7
8
9
| # Roadmap: Built-in observability and analytics
observability:
traces:
enabled: true
samplingRate: 0.1
metrics:
customMetrics: ["response-time", "error-rate"]
alerts:
channels: ["teams", "slack", "email"]
|
Conclusion
API Ops represents a significant evolution in API management, bringing the proven practices of DevOps to the API development lifecycle. Whether you choose Azure DevOps or GitHub, both platforms provide robust capabilities for implementing API Ops with Azure API Management.
Key Takeaways
🎯 Automation First: API Ops eliminates manual configuration drift between environments
🔄 Git as Source of Truth: All API configurations versioned and auditable
🚀 Faster Deployments: Automated pipelines reduce deployment time from hours to minutes
🛡️ Improved Quality: Built-in validation with Spectral ensures API consistency
📊 Better Visibility: Clear audit trail of all API changes and deployments
| Factor | Azure DevOps | GitHub |
|---|
| Azure Integration | ⭐⭐⭐⭐⭐ Native | ⭐⭐⭐ Good |
| Setup Complexity | ⭐⭐⭐⭐ Easy (with permissions) | ⭐⭐ Moderate |
| Enterprise Features | ⭐⭐⭐⭐⭐ Comprehensive | ⭐⭐⭐ Good |
| Developer Experience | ⭐⭐⭐ Good | ⭐⭐⭐⭐⭐ Excellent |
| Cost | ⭐⭐⭐ Moderate | ⭐⭐⭐⭐ Lower |
| Community | ⭐⭐⭐ Corporate | ⭐⭐⭐⭐⭐ Vibrant |
Getting Started Checklist
Phase 1: Foundation (Week 1)
Phase 2: Basic Implementation (Week 2-3)
Phase 3: Production Readiness (Week 4-6)
Phase 4: Advanced Features (Ongoing)
Call to Action
Start your API Ops journey today! Choose the platform that best fits your organization’s needs and begin with a simple pilot project. The investment in automation will pay dividends as your API portfolio grows.
Remember: API Ops is not just about tools - it’s about culture, collaboration, and continuous improvement in your API development lifecycle.
Resources and References
📚 Documentation
🎥 Learning Resources
Ready to transform your API management with API Ops? Start with our step-by-step implementation guide and join the growing community of API Ops practitioners! 🚀
Join the conversation! Share your thoughts and connect with other readers.