1 min read
Accelerating Content Delivery with Azure CDN
I wrote “2021-09-28-azure-cdn” to share practical, production-minded guidance on this topic.
CDN Offerings
| Provider | Features | Use Case |
|---|---|---|
| Standard Microsoft | Basic caching, rules engine | General purpose |
| Standard Verizon | Advanced analytics | Media streaming |
| Standard Akamai | Large file optimization | Software distribution |
| Premium Verizon | Advanced rules, real-time analytics | Enterprise |
Creating a CDN Profile and Endpoint
from azure.mgmt.cdn import CdnManagementClient
from azure.mgmt.cdn.models import (
Profile,
Sku,
Endpoint,
DeepCreatedOrigin,
QueryStringCachingBehavior
)
from azure.identity import DefaultAzureCredential
class CDNManager:
def __init__(self, subscription_id: str, resource_group: str):
self.credential = DefaultAzureCredential()
self.client = CdnManagementClient(self.credential, subscription_id)
self.resource_group = resource_group
def create_profile(self, profile_name: str, sku: str = "Standard_Microsoft") -> Profile:
"""Create a CDN profile."""
profile = Profile(
location="global",
sku=Sku(name=sku)
)
return self.client.profiles.begin_create(
self.resource_group,
profile_name,
profile
).result()
def create_endpoint(self, profile_name: str, endpoint_name: str,
origin_hostname: str,
origin_path: str = None) -> Endpoint:
"""Create a CDN endpoint."""
endpoint = Endpoint(
location="global",
origins=[
DeepCreatedOrigin(
name="origin1",
host_name=origin_hostname,
http_port=80,
https_port=443,
origin_host_header=origin_hostname
)
],
origin_path=origin_path,
is_http_allowed=True,
is_https_allowed=True,
query_string_caching_behavior=QueryStringCachingBehavior.IGNORE_QUERY_STRING,
is_compression_enabled=True,
content_types_to_compress=[
"text/plain",
"text/html",
"text/css",
"text/javascript",
"application/javascript",
"application/json",
"application/xml"
]
)
return self.client.endpoints.begin_create(
self.resource_group,
profile_name,
endpoint_name,
endpoint
).result()
def purge_content(self, profile_name: str, endpoint_name: str,
paths: list):
"""Purge cached content."""
return self.client.endpoints.begin_purge_content(
self.resource_group,
profile_name,
endpoint_name,
{"content_paths": paths}
).result()
def load_content(self, profile_name: str, endpoint_name: str,
paths: list):
"""Pre-load content into cache."""
return self.client.endpoints.begin_load_content(
self.resource_group,
profile_name,
endpoint_name,
{"content_paths": paths}
).result()
# Usage
cdn_manager = CDNManager("subscription-id", "cdn-rg")
# Create profile
cdn_manager.create_profile("mycdn-profile")
# Create endpoint for blob storage
cdn_manager.create_endpoint(
"mycdn-profile",
"static-content",
"mystorageaccount.blob.core.windows.net",
"/public"
)
Configuring Custom Domains
from azure.mgmt.cdn.models import (
CustomDomain,
CustomDomainHttpsParameters,
CdnManagedHttpsParameters,
CertificateType,
ProtocolType,
MinimumTlsVersion
)
def add_custom_domain(cdn_manager: CDNManager, profile_name: str,
endpoint_name: str, custom_domain: str):
"""Add a custom domain to CDN endpoint."""
# Custom domain name (without protocol)
domain_name = custom_domain.replace(".", "-")
custom_domain_obj = CustomDomain(
host_name=custom_domain
)
# Create custom domain
result = cdn_manager.client.custom_domains.begin_create(
cdn_manager.resource_group,
profile_name,
endpoint_name,
domain_name,
custom_domain_obj
).result()
return result
def enable_https(cdn_manager: CDNManager, profile_name: str,
endpoint_name: str, domain_name: str):
"""Enable HTTPS with CDN-managed certificate."""
https_params = CdnManagedHttpsParameters(
certificate_source="Cdn",
protocol_type=ProtocolType.SERVER_NAME_INDICATION,
minimum_tls_version=MinimumTlsVersion.TLS12,
certificate_source_parameters={
"certificate_type": CertificateType.DEDICATED
}
)
return cdn_manager.client.custom_domains.begin_enable_custom_https(
cdn_manager.resource_group,
profile_name,
endpoint_name,
domain_name,
https_params
).result()
# Add custom domain
add_custom_domain(cdn_manager, "mycdn-profile", "static-content", "cdn.example.com")
# Enable HTTPS
enable_https(cdn_manager, "mycdn-profile", "static-content", "cdn-example-com")
Rules Engine Configuration
from azure.mgmt.cdn.models import (
DeliveryRule,
DeliveryRuleCondition,
UrlFileExtensionMatchConditionParameters,
UrlPathMatchConditionParameters,
DeliveryRuleCacheExpirationAction,
CacheExpirationActionParameters,
HeaderAction,
HeaderActionParameters,
UrlRedirectAction,
UrlRedirectActionParameters
)
def configure_caching_rules(cdn_manager: CDNManager, profile_name: str,
endpoint_name: str):
"""Configure caching rules for different content types."""
rules = []
# Rule 1: Cache static assets for 30 days
rules.append(DeliveryRule(
name="CacheStaticAssets",
order=1,
conditions=[
{
"name": "UrlFileExtension",
"parameters": UrlFileExtensionMatchConditionParameters(
operator="Equal",
match_values=["css", "js", "png", "jpg", "jpeg", "gif", "svg", "woff", "woff2"]
)
}
],
actions=[
DeliveryRuleCacheExpirationAction(
parameters=CacheExpirationActionParameters(
cache_behavior="Override",
cache_type="All",
cache_duration="30.00:00:00"
)
)
]
))
# Rule 2: Don't cache API responses
rules.append(DeliveryRule(
name="NoCacheAPI",
order=2,
conditions=[
{
"name": "UrlPath",
"parameters": UrlPathMatchConditionParameters(
operator="BeginsWith",
match_values=["/api/"]
)
}
],
actions=[
DeliveryRuleCacheExpirationAction(
parameters=CacheExpirationActionParameters(
cache_behavior="BypassCache"
)
)
]
))
# Rule 3: Add security headers
rules.append(DeliveryRule(
name="SecurityHeaders",
order=3,
conditions=[],
actions=[
HeaderAction(
parameters=HeaderActionParameters(
header_action="Append",
header_name="X-Content-Type-Options",
value="nosniff"
)
),
HeaderAction(
parameters=HeaderActionParameters(
header_action="Append",
header_name="X-Frame-Options",
value="DENY"
)
),
HeaderAction(
parameters=HeaderActionParameters(
header_action="Append",
header_name="Strict-Transport-Security",
value="max-age=31536000; includeSubDomains"
)
)
]
))
# Rule 4: Redirect HTTP to HTTPS
rules.append(DeliveryRule(
name="HttpsRedirect",
order=4,
conditions=[
{
"name": "RequestScheme",
"parameters": {"operator": "Equal", "match_values": ["HTTP"]}
}
],
actions=[
UrlRedirectAction(
parameters=UrlRedirectActionParameters(
redirect_type="Found",
destination_protocol="Https"
)
)
]
))
# Update endpoint with rules
endpoint = cdn_manager.client.endpoints.get(
cdn_manager.resource_group,
profile_name,
endpoint_name
)
endpoint.delivery_policy = {"rules": rules}
return cdn_manager.client.endpoints.begin_update(
cdn_manager.resource_group,
profile_name,
endpoint_name,
endpoint
).result()
CDN Integration with Storage
from azure.storage.blob import BlobServiceClient, generate_blob_sas, BlobSasPermissions
from datetime import datetime, timedelta
class CDNStorageIntegration:
def __init__(self, storage_connection: str, cdn_hostname: str):
self.blob_service = BlobServiceClient.from_connection_string(storage_connection)
self.cdn_hostname = cdn_hostname
def get_cdn_url(self, container: str, blob_name: str) -> str:
"""Get CDN URL for a blob."""
return f"https://{self.cdn_hostname}/{container}/{blob_name}"
def upload_with_cache_headers(self, container: str, blob_name: str,
data: bytes, content_type: str,
cache_control: str = "public, max-age=86400"):
"""Upload blob with cache-control headers."""
container_client = self.blob_service.get_container_client(container)
blob_client = container_client.get_blob_client(blob_name)
blob_client.upload_blob(
data,
content_settings={
"content_type": content_type,
"cache_control": cache_control
},
overwrite=True
)
return self.get_cdn_url(container, blob_name)
def upload_versioned(self, container: str, blob_name: str,
data: bytes, version: str) -> str:
"""Upload with version in filename for cache busting."""
# Add version to filename
name_parts = blob_name.rsplit('.', 1)
if len(name_parts) == 2:
versioned_name = f"{name_parts[0]}.{version}.{name_parts[1]}"
else:
versioned_name = f"{blob_name}.{version}"
return self.upload_with_cache_headers(
container,
versioned_name,
data,
"application/octet-stream",
"public, max-age=31536000, immutable" # 1 year, immutable
)
# Usage
cdn_storage = CDNStorageIntegration(
"your-storage-connection",
"mycdn.azureedge.net"
)
# Upload static assets
with open("styles.css", "rb") as f:
url = cdn_storage.upload_versioned(
"static",
"styles.css",
f.read(),
"v1.2.3"
)
print(f"CDN URL: {url}")
Monitoring and Analytics
from azure.monitor.query import MetricsQueryClient, LogsQueryClient
from datetime import datetime, timedelta
def get_cdn_metrics(metrics_client: MetricsQueryClient,
endpoint_resource_id: str,
hours: int = 24) -> dict:
"""Get CDN endpoint metrics."""
end_time = datetime.utcnow()
start_time = end_time - timedelta(hours=hours)
metrics = metrics_client.query_resource(
endpoint_resource_id,
metric_names=[
"ByteHitRatio",
"RequestCount",
"ResponseSize",
"OriginLatency"
],
timespan=(start_time, end_time),
granularity=timedelta(hours=1)
)
results = {}
for metric in metrics.metrics:
values = []
for series in metric.timeseries:
for data in series.data:
values.append({
"timestamp": data.timestamp,
"average": data.average,
"total": data.total
})
results[metric.name] = values
return results
def generate_cdn_report(metrics: dict) -> str:
"""Generate CDN performance report."""
report = []
report.append("=== CDN Performance Report ===\n")
if "ByteHitRatio" in metrics:
avg_hit_ratio = sum(v["average"] or 0 for v in metrics["ByteHitRatio"]) / max(len(metrics["ByteHitRatio"]), 1)
report.append(f"Cache Hit Ratio: {avg_hit_ratio:.1f}%")
if "RequestCount" in metrics:
total_requests = sum(v["total"] or 0 for v in metrics["RequestCount"])
report.append(f"Total Requests: {total_requests:,}")
if "ResponseSize" in metrics:
total_bytes = sum(v["total"] or 0 for v in metrics["ResponseSize"])
report.append(f"Data Transferred: {total_bytes / (1024**3):.2f} GB")
if "OriginLatency" in metrics:
avg_latency = sum(v["average"] or 0 for v in metrics["OriginLatency"]) / max(len(metrics["OriginLatency"]), 1)
report.append(f"Average Origin Latency: {avg_latency:.0f} ms")
return "\n".join(report)
Best Practices
- Enable Compression: Compress text-based content
- Set Cache Headers: Use appropriate cache-control headers
- Version Assets: Include version in filenames for cache busting
- Use HTTPS: Always enable HTTPS with modern TLS
- Monitor Hit Ratio: Aim for >90% cache hit ratio
- Purge Strategically: Purge only what’s necessary
Azure CDN significantly improves application performance by reducing latency and offloading traffic from origin servers.\n\n## Takeaways\n\nAdd a concise, personal takeaway and recommended next steps here.\n