Skip to content

Commit 1fb7f49

Browse files
Initial working commit of new appgw-apim infrastructure
1 parent e046722 commit 1fb7f49

File tree

10 files changed

+1168
-13
lines changed

10 files changed

+1168
-13
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Application Gateway & API Management (VNet Internal) Infrastructure
2+
3+
This architecture provides secure ingress through Azure Application Gateway (WAF_v2) to Azure API Management (APIM) deployed in a Virtual Network using Internal mode (private IP only). No Private Endpoints are used; traffic stays on private networking after the gateway, and APIM cannot be accessed aside from traversing Application Gateway.
4+
5+
<!-- TODO: Generate diagram -->
6+
<!-- <img src="./Azure Application Gateway + APIM (Internal).svg" alt="Diagram showing Application Gateway routing to APIM in VNet Internal mode. Optional Container Apps shown behind APIM. Telemetry to Azure Monitor." title="Application Gateway + APIM (Internal)" width="1000" /> -->
7+
8+
## 🎯 Objectives
9+
10+
1. Expose APIs publicly via Application Gateway while keeping APIM private (VNet Internal)
11+
2. Maintain private networking end-to-end without Private Endpoints
12+
3. Enable optional backends with Azure Container Apps (ACA)
13+
4. Provide observability via Log Analytics and Application Insights
14+
15+
## 💡 Why Developer SKU?
16+
17+
- Significant cost savings for learning, demos, and dev/test:
18+
- Developer is a fraction of Premium costs (often >90% cheaper)
19+
- No SLA and single-instance only, which is acceptable for the purpose of this repo
20+
- Trade-offs:
21+
- Longer deployment times compared to v2/Premium SKUs (APIM creation can be slow)
22+
23+
We choose the Developer SKU here to dramatically lower costs for experimentation. If you need SLAs, scaling, or production-grade features, use Premium/Premiumv2 as those are the only SKUs that support VNet *injection*.
24+
25+
## ⚙️ Configuration
26+
27+
Adjust the user-defined parameters in this lab's Jupyter Notebook's Initialize notebook variables section.
28+
29+
Key parameters:
30+
- `apimSku`: Defaults to `Developer`
31+
- `useACA`: Enable to provision a private ACA environment and sample apps
32+
33+
## ▶️ Execution
34+
35+
1. Execute this lab's Jupyter Notebook step-by-step or via Run All.
36+
37+
👟 Expected Run All runtime: longer than v2 SKUs for APIM creation. We will measure and update this value after testing.
38+
39+
- TODO: Measure actual runtime on Developer SKU and update this section accordingly.
40+
41+
## 🧪 Testing
42+
43+
Because we use a self-signed certificate for Application Gateway TLS termination (for convenience in this sample), testing can be done with curl by ignoring certificate warnings and sending the Host header:
44+
45+
```
46+
curl -v -k -H "Host: api.apim-samples.contoso.com" https://<APPGW_PUBLIC_IP>/status-0123456789abcdef
47+
```
48+
49+
A 200 status from the health endpoint indicates success through App Gateway to APIM.
50+
51+
## 🔐 Security Notes
52+
53+
- Self-signed certificates are for demos only. Use managed or trusted certs for production.
54+
- This sample uses RBAC-enabled Key Vault with minimal role assignments for App Gateway certificate retrieval.
55+
- **Azure limitation**: APIM must be created with public access enabled initially. You can disable public access in a subsequent update if desired.
56+
- Review and harden NSGs/WAF policy as needed for your environment.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"### 🗑️ Clean up resources\n",
8+
"\n",
9+
"When you're finished with the lab, you should remove all your deployed resources from Azure to avoid extra charges and keep your Azure subscription uncluttered."
10+
]
11+
},
12+
{
13+
"cell_type": "code",
14+
"execution_count": null,
15+
"metadata": {},
16+
"outputs": [],
17+
"source": [
18+
"from apimtypes import INFRASTRUCTURE\n",
19+
"from infrastructures import cleanup_infra_deployments\n",
20+
"\n",
21+
"indexes = [1]\n",
22+
"\n",
23+
"cleanup_infra_deployments(INFRASTRUCTURE.APPGW_APIM, indexes)"
24+
]
25+
}
26+
],
27+
"metadata": {
28+
"kernelspec": {
29+
"display_name": ".venv (3.14.0)",
30+
"language": "python",
31+
"name": "python3"
32+
},
33+
"language_info": {
34+
"codemirror_mode": {
35+
"name": "ipython",
36+
"version": 3
37+
},
38+
"file_extension": ".py",
39+
"mimetype": "text/x-python",
40+
"name": "python",
41+
"nbconvert_exporter": "python",
42+
"pygments_lexer": "ipython3",
43+
"version": "3.14.0"
44+
}
45+
},
46+
"nbformat": 4,
47+
"nbformat_minor": 2
48+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"### 🛠️ Configure Infrastructure Parameters & Create the Infrastructure\n",
8+
"\n",
9+
"Set your desired parameters for the APPGW-APIM infrastructure deployment.\n",
10+
"\n",
11+
"❗️ **Modify entries under _User-defined parameters_**.\n",
12+
"\n",
13+
"**Note:** This infrastructure includes Application Gateway with API Management using VNet injection."
14+
]
15+
},
16+
{
17+
"cell_type": "code",
18+
"execution_count": null,
19+
"metadata": {},
20+
"outputs": [],
21+
"source": [
22+
"from apimtypes import APIM_SKU, INFRASTRUCTURE\n",
23+
"from utils import InfrastructureNotebookHelper\n",
24+
"from console import print_ok\n",
25+
"\n",
26+
"# ------------------------------\n",
27+
"# USER CONFIGURATION\n",
28+
"# ------------------------------\n",
29+
"\n",
30+
"rg_location = 'eastus2' # Azure region for deployment\n",
31+
"index = 1 # Infrastructure index (use different numbers for multiple environments)\n",
32+
"apim_sku = APIM_SKU.DEVELOPER # Options: 'DEVELOPER'\n",
33+
"\n",
34+
"\n",
35+
"\n",
36+
"# ------------------------------\n",
37+
"# SYSTEM CONFIGURATION\n",
38+
"# ------------------------------\n",
39+
"\n",
40+
"inb_helper = InfrastructureNotebookHelper(rg_location, INFRASTRUCTURE.APPGW_APIM, index, apim_sku)\n",
41+
"inb_helper.create_infrastructure()\n",
42+
"\n",
43+
"print_ok('All done!')"
44+
]
45+
},
46+
{
47+
"cell_type": "markdown",
48+
"metadata": {},
49+
"source": [
50+
"### 🗑️ Clean up resources\n",
51+
"\n",
52+
"When you're finished experimenting, it's advisable to remove all associated resources from Azure to avoid unnecessary cost.\n",
53+
"Use the [clean-up notebook](clean-up.ipynb) for that."
54+
]
55+
}
56+
],
57+
"metadata": {
58+
"kernelspec": {
59+
"display_name": ".venv (3.14.0)",
60+
"language": "python",
61+
"name": "python3"
62+
},
63+
"language_info": {
64+
"codemirror_mode": {
65+
"name": "ipython",
66+
"version": 3
67+
},
68+
"file_extension": ".py",
69+
"mimetype": "text/x-python",
70+
"name": "python",
71+
"nbconvert_exporter": "python",
72+
"pygments_lexer": "ipython3",
73+
"version": "3.14.0"
74+
}
75+
},
76+
"nbformat": 4,
77+
"nbformat_minor": 2
78+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""
2+
Reusable script to create Application Gateway + APIM (Internal) infrastructure.
3+
"""
4+
5+
import sys
6+
import argparse
7+
8+
# APIM Samples imports
9+
import azure_resources as az
10+
from apimtypes import APIM_SKU, API, GET_APIOperation, BACKEND_XML_POLICY_PATH, INFRASTRUCTURE
11+
from infrastructures import AppGwApimInfrastructure
12+
import utils
13+
14+
15+
def create_infrastructure(location: str, index: int, apim_sku: APIM_SKU, no_aca: bool = False) -> None:
16+
# Check if infrastructure already exists to determine messaging
17+
infrastructure_exists = az.does_resource_group_exist(az.get_infra_rg_name(INFRASTRUCTURE.APPGW_APIM, index))
18+
19+
# Create custom APIs for APPGW-APIM with optional Container Apps backends
20+
custom_apis = _create_appgw_specific_apis(not no_aca)
21+
22+
infra = AppGwApimInfrastructure(location, index, apim_sku, infra_apis = custom_apis)
23+
result = infra.deploy_infrastructure(infrastructure_exists)
24+
25+
raise SystemExit(0 if result.success else 1)
26+
27+
def _create_appgw_specific_apis(use_aca: bool = True) -> list[API]:
28+
"""
29+
Create APPGW-APIM specific APIs with optional Container Apps backends.
30+
31+
Args:
32+
use_aca (bool): Whether to include Azure Container Apps backends. Defaults to true.
33+
34+
Returns:
35+
list[API]: List of AppGw-specific APIs.
36+
"""
37+
38+
# If Container Apps is enabled, create the ACA APIs in APIM
39+
if use_aca:
40+
pol_backend = utils.read_policy_xml(BACKEND_XML_POLICY_PATH)
41+
pol_aca_backend_1 = pol_backend.format(backend_id = 'aca-backend-1')
42+
pol_aca_backend_2 = pol_backend.format(backend_id = 'aca-backend-2')
43+
pol_aca_backend_pool = pol_backend.format(backend_id = 'aca-backend-pool')
44+
45+
# API 1: Hello World (ACA Backend 1)
46+
api_hwaca_1_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 1')
47+
api_hwaca_1 = API('hello-world-aca-1', 'Hello World (ACA 1)', '/aca-1', 'This is the ACA API for Backend 1', pol_aca_backend_1, [api_hwaca_1_get])
48+
49+
# API 2: Hello World (ACA Backend 2)
50+
api_hwaca_2_get = GET_APIOperation('This is a GET for Hello World on ACA Backend 2')
51+
api_hwaca_2 = API('hello-world-aca-2', 'Hello World (ACA 2)', '/aca-2', 'This is the ACA API for Backend 2', pol_aca_backend_2, [api_hwaca_2_get])
52+
53+
# API 3: Hello World (ACA Backend Pool)
54+
api_hwaca_pool_get = GET_APIOperation('This is a GET for Hello World on ACA Backend Pool')
55+
api_hwaca_pool = API('hello-world-aca-pool', 'Hello World (ACA Pool)', '/aca-pool', 'This is the ACA API for Backend Pool', pol_aca_backend_pool, [api_hwaca_pool_get])
56+
57+
return [api_hwaca_1, api_hwaca_2, api_hwaca_pool]
58+
59+
return []
60+
61+
def main():
62+
"""
63+
Main entry point for command-line usage.
64+
"""
65+
66+
parser = argparse.ArgumentParser(description = 'Create APPGW-APIM infrastructure (VNet Internal)')
67+
parser.add_argument('--location', default = 'eastus2', help = 'Azure region (default: eastus2)')
68+
parser.add_argument('--index', type = int, help = 'Infrastructure index')
69+
parser.add_argument('--sku', choices = ['Developer', 'Basicv2', 'Standardv2'], default = 'Developer', help = 'APIM SKU (default: Developer)')
70+
parser.add_argument('--no-aca', action = 'store_true', help = 'Disable Azure Container Apps')
71+
args = parser.parse_args()
72+
73+
# Convert SKU string to enum using the enum's built-in functionality
74+
try:
75+
apim_sku = APIM_SKU(args.sku)
76+
except ValueError:
77+
print(f"Error: Invalid SKU '{args.sku}'. Valid options are: {', '.join([sku.value for sku in APIM_SKU])}")
78+
sys.exit(1)
79+
80+
create_infrastructure(args.location, args.index, apim_sku, args.no_aca)
81+
82+
if __name__ == '__main__':
83+
main()

0 commit comments

Comments
 (0)