mcpField
C
cdn-setup

cdn-setup

github.com/bagelhole云与 DevOps1.2k328 次安装23 周前更新
代码仓库

关于 cdn-setup

cdn-setup 是面向配置 CDN 的技能,涵盖 CloudFront 与 Cloudflare 的部署、缓存和排查,适用于加速网站及 API 内容交付。

主要功能

  1. 01配置 CloudFront 与 Cloudflare CDN
  2. 02设置缓存规则和源站缓存头
  3. 03按路径清除边缘缓存
  4. 04使用 Terraform 管理配置
  5. 05检查缓存表现并排查问题

适用场景

  1. 01为全球用户加速静态资源
  2. 02通过 CDN 缓存 API 响应
  3. 03部署后清除过期对象缓存

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/bagelhole/devops-security-agent-skills --skill cdn-setup

安装后在 Claude Code 中输入/cdn-setup确认已加载。

详细介绍

CDN 设置

配置内容分发网络,以通过适当的缓存、失效处理和安全措施,实现快速、可靠的全球资源分发。

适用场景

  • 在全球范围内以低延迟提供静态资源(JS、CSS、图片、字体)。
  • 将流量从源服务器分流,以降低计算成本。
  • 在边缘节点添加 TLS 终止和 DDoS 防护。
  • 实施基于地理位置的路由或内容限制。
  • 通过边缘缓存加速 API 响应。

前提条件

  • 可管理 DNS 的域名。
  • 提供待分发内容的源服务器或 S3/R2 存储桶。
  • 已配置 AWS CLI(用于 CloudFront)。
  • 已配置区域的 Cloudflare 账户(用于 Cloudflare CDN)。
  • Terraform 1.5+(用于基础设施即代码示例)。

AWS CloudFront

通过 CLI 创建分配
# Create an S3 origin distribution with OAC (Origin Access Control)
aws cloudfront create-distribution --distribution-config '{
  "CallerReference": "my-site-'$(date +%s)'",
  "Comment": "Production site CDN",
  "Enabled": true,
  "Origins": {
    "Quantity": 1,
    "Items": [{
      "Id": "s3-origin",
      "DomainName": "my-bucket.s3.us-east-1.amazonaws.com",
      "OriginPath": "",
      "S3OriginConfig": {
        "OriginAccessIdentity": ""
      },
      "OriginAccessControlId": "E2QWRUHAPOMQZL"
    }]
  },
  "DefaultCacheBehavior": {
    "TargetOriginId": "s3-origin",
    "ViewerProtocolPolicy": "redirect-to-https",
    "AllowedMethods": {
      "Quantity": 2,
      "Items": ["GET", "HEAD"]
    },
    "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
    "Compress": true
  },
  "DefaultRootObject": "index.html",
  "PriceClass": "PriceClass_100",
  "ViewerCertificate": {
    "ACMCertificateArn": "arn:aws:acm:us-east-1:123456789:certificate/abc-123",
    "SSLSupportMethod": "sni-only",
    "MinimumProtocolVersion": "TLSv1.2_2021"
  },
  "Aliases": {
    "Quantity": 1,
    "Items": ["www.example.com"]
  },
  "CustomErrorResponses": {
    "Quantity": 1,
    "Items": [{
      "ErrorCode": 404,
      "ResponseCode": "200",
      "ResponsePagePath": "/index.html",
      "ErrorCachingMinTTL": 10
    }]
  }
}'
缓存失效处理
# Invalidate specific paths
aws cloudfront create-invalidation \
  --distribution-id E1A2B3C4D5E6F7 \
  --paths "/index.html" "/css/*" "/js/*"

# Invalidate everything (costs apply per path)
aws cloudfront create-invalidation \
  --distribution-id E1A2B3C4D5E6F7 \
  --paths "/*"

# Check invalidation status
aws cloudfront get-invalidation \
  --distribution-id E1A2B3C4D5E6F7 \
  --id I1A2B3C4D5E6F7

# List recent invalidations
aws cloudfront list-invalidations --distribution-id E1A2B3C4D5E6F7
CloudFront Functions(轻量级边缘逻辑)
// URL rewrite function — add index.html to directory requests
function handler(event) {
  var request = event.request;
  var uri = request.uri;

  if (uri.endsWith('/')) {
    request.uri += 'index.html';
  } else if (!uri.includes('.')) {
    request.uri += '/index.html';
  }

  return request;
}
使用 Terraform 配置 CloudFront
# cloudfront.tf
resource "aws_cloudfront_distribution" "site" {
  enabled             = true
  is_ipv6_enabled     = true
  default_root_object = "index.html"
  aliases             = ["www.example.com"]
  price_class         = "PriceClass_100"

  origin {
    domain_name              = aws_s3_bucket.site.bucket_regional_domain_name
    origin_id                = "s3-origin"
    origin_access_control_id = aws_cloudfront_origin_access_control.oac.id
  }

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "s3-origin"

    cache_policy_id          = "658327ea-f89d-4fab-a63d-7e88639e58f6" # CachingOptimized
    origin_request_policy_id = "88a5eaf4-2fd4-4709-b370-b4c650ea3fcf" # CORS-S3Origin

    viewer_protocol_policy = "redirect-to-https"
    compress               = true
  }

  # SPA fallback
  custom_error_response {
    error_code         = 404
    response_code      = 200
    response_page_path = "/index.html"
  }

  # API pass-through (no caching)
  ordered_cache_behavior {
    path_pattern     = "/api/*"
    allowed_methods  = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "api-origin"

    cache_policy_id          = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" # CachingDisabled
    origin_request_policy_id = "b689b0a8-53d0-40ab-baf2-68738e2966ac" # AllViewerExceptHostHeader

    viewer_protocol_policy = "https-only"
  }

  viewer_certificate {
    acm_certificate_arn      = aws_acm_certificate.cert.arn
    ssl_support_method       = "sni-only"
    minimum_protocol_version = "TLSv1.2_2021"
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }
}

resource "aws_cloudfront_origin_access_control" "oac" {
  name                              = "s3-oac"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

Cloudflare CDN

区域设置
# Add a zone
curl -X POST "https://api.cloudflare.com/client/v4/zones" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"example.com","jump_start":true}'

# Get zone ID
ZONE_ID=$(curl -s "https://api.cloudflare.com/client/v4/zones?name=example.com" \
  -H "Authorization: Bearer $CF_API_TOKEN" | jq -r '.result[0].id')
缓存规则(替代 Page Rules)
# Create a cache rule for static assets
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/phases/http_request_cache_settings/entrypoint" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "rules": [
      {
        "expression": "(http.request.uri.path.extension in {\"css\" \"js\" \"png\" \"jpg\" \"woff2\" \"svg\"})",
        "action": "set_cache_settings",
        "action_parameters": {
          "cache": true,
          "browser_ttl": { "mode": "override_origin", "default": 2592000 },
          "edge_ttl": { "mode": "override_origin", "default": 86400 }
        },
        "description": "Cache static assets aggressively"
      }
    ]
  }'
清除缓存
# Purge everything
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"purge_everything":true}'

# Purge specific URLs
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"files":["https://example.com/style.css","https://example.com/app.js"]}'

# Purge by prefix
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"prefixes":["https://example.com/images/"]}'

源站的缓存标头

nginx 缓存标头
# Immutable hashed assets (fingerprinted filenames)
location ~* \.(js|css)$ {
    if ($uri ~* "\.[a-f0-9]{8,}\.(js|css)$") {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    expires 7d;
    add_header Cache-Control "public, must-revalidate";
}

# Images and fonts
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|woff2|ttf)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
}

# HTML — always revalidate
location ~* \.html$ {
    expires -1;
    add_header Cache-Control "no-cache, must-revalidate";
}

# API responses — no caching
location /api/ {
    add_header Cache-Control "no-store, no-cache";
    add_header Vary "Authorization, Accept";
}
Cache-Control 速查表
标头含义
public, max-age=31536000, immutable缓存 1 年,永不重新验证(带哈希的资源)
public, max-age=86400, must-revalidate缓存 1 天,之后检查内容是否为最新
private, max-age=600仅浏览器缓存,缓存 10 分钟(用户专属内容)
no-cache提供内容前始终向源站重新验证
no-store永不缓存(敏感数据)
s-maxage=3600CDN 缓存 1 小时;对共享缓存而言,此值优先于 max-age

缓存预热

# Warm cache for critical pages after deployment
#!/bin/bash
URLS=(
  "https://www.example.com/"
  "https://www.example.com/products"
  "https://www.example.com/about"
  "https://www.example.com/css/main.abc123.css"
  "https://www.example.com/js/app.def456.js"
)

for url in "${URLS[@]}"; do
  curl -s -o /dev/null -w "%{http_code} %{time_total}s %{url_effective}\n" "$url"
done

监控缓存性能

# Check cache status from response headers
curl -sI https://www.example.com/style.css | grep -i -E "cf-cache|x-cache|age|cache-control"

# Expected headers:
# cf-cache-status: HIT (Cloudflare)
# x-cache: Hit from cloudfront (CloudFront)
# age: 3600 (seconds since cached)

# CloudFront cache hit ratio
aws cloudwatch get-metric-statistics \
  --namespace AWS/CloudFront \
  --metric-name CacheHitRate \
  --dimensions Name=DistributionId,Value=E1A2B3C4D5E6F7 \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 300 \
  --statistics Average

故障排查

症状原因解决方法
cf-cache-status: DYNAMIC没有匹配的缓存规则,或 Cache-Control 禁止缓存设置 s-maxage,或为该路径创建缓存规则
缓存命中率低于 50%TTL 较短,或 URL 基数较高(查询字符串)增加 TTL;从缓存键中移除不必要的查询字符串
部署后内容仍然过期旧对象仍缓存在边缘节点执行失效处理;使用内容哈希文件名可彻底避免此问题
通过 CDN 访问时出现 CORS 错误CDN 移除了 Vary 标头,或缓存了错误的标头添加 Vary: Origin,并配置源站请求策略以转发 Origin
CDN 返回 502 错误源站宕机或超时检查源站运行状况;增加 CDN 源站超时设置
出现混合内容警告CDN 使用 HTTPS 提供内容,但源站链接使用 HTTP设置 viewer-protocol-policy: redirect-to-https;修正源站 URL
失效处理成本高每次部署都清除 /*使用指纹文件名;仅对 index.html 执行失效处理

相关技能

评论

支持 Markdown