8.1 Caddy Admin API概述

8.1.1 API简介

Caddy Admin API是一个强大的RESTful API,允许您在运行时动态管理Caddy配置。

主要特性: - 实时配置更新 - 无需重启服务 - 完整的配置管理 - 监控和诊断 - 安全的访问控制

8.1.2 API端点

# 默认API地址
http://localhost:2019

# 主要端点
GET    /config/          # 获取完整配置
POST   /config/          # 更新完整配置
PATCH  /config/          # 部分更新配置
DELETE /config/          # 删除配置
GET    /config/apps/     # 获取应用配置
POST   /load             # 加载配置
POST   /stop             # 停止服务

8.1.3 启用Admin API

# Caddyfile配置
{
    admin localhost:2019
    # 或禁用API
    # admin off
}

example.com {
    respond "Hello World"
}
{
    "admin": {
        "listen": "localhost:2019"
    },
    "apps": {
        "http": {
            "servers": {
                "srv0": {
                    "listen": [":80"],
                    "routes": [
                        {
                            "match": [{"host": ["example.com"]}],
                            "handle": [{
                                "handler": "static_response",
                                "body": "Hello World"
                            }]
                        }
                    ]
                }
            }
        }
    }
}

8.2 JSON配置格式

8.2.1 配置结构

{
    "admin": {
        "listen": "localhost:2019",
        "enforce_origin": false,
        "origins": ["localhost:2019", "127.0.0.1:2019"]
    },
    "logging": {
        "logs": {
            "default": {
                "level": "INFO",
                "writer": {
                    "output": "stdout"
                }
            }
        }
    },
    "storage": {
        "module": "file_system",
        "root": "/var/lib/caddy"
    },
    "apps": {
        "http": {},
        "tls": {},
        "pki": {}
    }
}

8.2.2 HTTP应用配置

{
    "apps": {
        "http": {
            "http_port": 80,
            "https_port": 443,
            "grace_period": "5s",
            "servers": {
                "srv0": {
                    "listen": [":80", ":443"],
                    "routes": [
                        {
                            "match": [
                                {
                                    "host": ["example.com"],
                                    "path": ["/api/*"]
                                }
                            ],
                            "handle": [
                                {
                                    "handler": "reverse_proxy",
                                    "upstreams": [
                                        {"dial": "localhost:8080"}
                                    ]
                                }
                            ]
                        }
                    ],
                    "automatic_https": {
                        "disable": false,
                        "disable_redirects": false
                    }
                }
            }
        }
    }
}

8.2.3 TLS配置

{
    "apps": {
        "tls": {
            "automation": {
                "policies": [
                    {
                        "subjects": ["example.com", "*.example.com"],
                        "issuers": [
                            {
                                "module": "acme",
                                "ca": "https://acme-v02.api.letsencrypt.org/directory",
                                "email": "admin@example.com",
                                "agreed": true,
                                "challenges": {
                                    "dns": {
                                        "provider": {
                                            "name": "cloudflare",
                                            "api_token": "{env.CLOUDFLARE_API_TOKEN}"
                                        }
                                    }
                                }
                            }
                        ]
                    }
                ]
            },
            "certificates": {
                "load_files": [
                    {
                        "certificate": "/path/to/cert.pem",
                        "key": "/path/to/key.pem"
                    }
                ]
            }
        }
    }
}

8.3 API操作实例

8.3.1 获取配置

# 获取完整配置
curl http://localhost:2019/config/ | jq

# 获取特定路径配置
curl http://localhost:2019/config/apps/http/servers

# 获取路由配置
curl http://localhost:2019/config/apps/http/servers/srv0/routes

8.3.2 更新配置

# 添加新路由
curl -X POST http://localhost:2019/config/apps/http/servers/srv0/routes \
  -H "Content-Type: application/json" \
  -d '{
    "match": [{"host": ["api.example.com"]}],
    "handle": [{
      "handler": "reverse_proxy",
      "upstreams": [{"dial": "localhost:3000"}]
    }]
  }'

# 更新现有路由
curl -X PUT http://localhost:2019/config/apps/http/servers/srv0/routes/0 \
  -H "Content-Type: application/json" \
  -d '{
    "match": [{"host": ["updated.example.com"]}],
    "handle": [{
      "handler": "static_response",
      "body": "Updated response"
    }]
  }'

8.3.3 删除配置

# 删除特定路由
curl -X DELETE http://localhost:2019/config/apps/http/servers/srv0/routes/0

# 删除整个服务器配置
curl -X DELETE http://localhost:2019/config/apps/http/servers/srv0

8.4 配置管理脚本

8.4.1 Python管理脚本

#!/usr/bin/env python3
import requests
import json
import sys

class CaddyAPI:
    def __init__(self, base_url="http://localhost:2019"):
        self.base_url = base_url
        self.session = requests.Session()
    
    def get_config(self, path=""):
        """获取配置"""
        url = f"{self.base_url}/config/{path}"
        response = self.session.get(url)
        response.raise_for_status()
        return response.json()
    
    def update_config(self, path, config):
        """更新配置"""
        url = f"{self.base_url}/config/{path}"
        response = self.session.post(url, json=config)
        response.raise_for_status()
        return response.json() if response.content else None
    
    def delete_config(self, path):
        """删除配置"""
        url = f"{self.base_url}/config/{path}"
        response = self.session.delete(url)
        response.raise_for_status()
    
    def add_route(self, server, route_config):
        """添加路由"""
        path = f"apps/http/servers/{server}/routes"
        return self.update_config(path, route_config)
    
    def add_upstream(self, server, route_index, upstream):
        """添加上游服务器"""
        path = f"apps/http/servers/{server}/routes/{route_index}/handle/0/upstreams"
        return self.update_config(path, upstream)
    
    def reload_config(self, config_file):
        """重新加载配置文件"""
        with open(config_file, 'r') as f:
            config = json.load(f)
        
        url = f"{self.base_url}/load"
        response = self.session.post(url, json=config)
        response.raise_for_status()

# 使用示例
if __name__ == "__main__":
    api = CaddyAPI()
    
    # 获取当前配置
    config = api.get_config()
    print(json.dumps(config, indent=2))
    
    # 添加新路由
    new_route = {
        "match": [{"host": ["test.example.com"]}],
        "handle": [{
            "handler": "static_response",
            "body": "Test response"
        }]
    }
    api.add_route("srv0", new_route)
    print("Route added successfully")

8.4.2 Bash管理脚本

#!/bin/bash

# Caddy API管理脚本
CADDY_API="http://localhost:2019"

# 获取配置
get_config() {
    local path="${1:-}"
    curl -s "$CADDY_API/config/$path" | jq .
}

# 添加路由
add_route() {
    local server="$1"
    local host="$2"
    local upstream="$3"
    
    local route_config=$(cat <<EOF
{
  "match": [{"host": ["$host"]}],
  "handle": [{
    "handler": "reverse_proxy",
    "upstreams": [{"dial": "$upstream"}]
  }]
}
EOF
)
    
    curl -X POST "$CADDY_API/config/apps/http/servers/$server/routes" \
         -H "Content-Type: application/json" \
         -d "$route_config"
}

# 删除路由
delete_route() {
    local server="$1"
    local route_index="$2"
    
    curl -X DELETE "$CADDY_API/config/apps/http/servers/$server/routes/$route_index"
}

# 重新加载配置
reload_config() {
    local config_file="$1"
    
    curl -X POST "$CADDY_API/load" \
         -H "Content-Type: application/json" \
         -d @"$config_file"
}

# 健康检查
health_check() {
    if curl -s "$CADDY_API/config/" > /dev/null; then
        echo "Caddy API is healthy"
        return 0
    else
        echo "Caddy API is not responding"
        return 1
    fi
}

# 主函数
main() {
    case "$1" in
        "get")
            get_config "$2"
            ;;
        "add-route")
            add_route "$2" "$3" "$4"
            ;;
        "delete-route")
            delete_route "$2" "$3"
            ;;
        "reload")
            reload_config "$2"
            ;;
        "health")
            health_check
            ;;
        *)
            echo "Usage: $0 {get|add-route|delete-route|reload|health}"
            echo "  get [path]                    - Get configuration"
            echo "  add-route server host upstream - Add route"
            echo "  delete-route server index     - Delete route"
            echo "  reload config-file           - Reload configuration"
            echo "  health                       - Health check"
            exit 1
            ;;
    esac
}

main "$@"

8.5 动态配置管理

8.5.1 配置模板系统

#!/usr/bin/env python3
import json
import os
from string import Template

class CaddyConfigTemplate:
    def __init__(self, template_dir="templates"):
        self.template_dir = template_dir
    
    def load_template(self, template_name):
        """加载配置模板"""
        template_path = os.path.join(self.template_dir, f"{template_name}.json")
        with open(template_path, 'r') as f:
            return Template(f.read())
    
    def generate_config(self, template_name, variables):
        """生成配置"""
        template = self.load_template(template_name)
        config_str = template.substitute(variables)
        return json.loads(config_str)
    
    def create_site_config(self, domain, upstream, ssl_email):
        """创建站点配置"""
        variables = {
            'domain': domain,
            'upstream': upstream,
            'ssl_email': ssl_email
        }
        return self.generate_config('site', variables)

# 模板文件: templates/site.json
site_template = '''
{
    "apps": {
        "http": {
            "servers": {
                "srv0": {
                    "listen": [":80", ":443"],
                    "routes": [
                        {
                            "match": [{"host": ["$domain"]}],
                            "handle": [{
                                "handler": "reverse_proxy",
                                "upstreams": [{"dial": "$upstream"}]
                            }]
                        }
                    ]
                }
            }
        },
        "tls": {
            "automation": {
                "policies": [
                    {
                        "subjects": ["$domain"],
                        "issuers": [
                            {
                                "module": "acme",
                                "email": "$ssl_email"
                            }
                        ]
                    }
                ]
            }
        }
    }
}
'''

8.5.2 配置验证

import jsonschema

# Caddy配置JSON Schema
caddy_schema = {
    "type": "object",
    "properties": {
        "admin": {
            "type": "object",
            "properties": {
                "listen": {"type": "string"},
                "enforce_origin": {"type": "boolean"}
            }
        },
        "apps": {
            "type": "object",
            "properties": {
                "http": {
                    "type": "object",
                    "properties": {
                        "servers": {
                            "type": "object",
                            "patternProperties": {
                                ".*": {
                                    "type": "object",
                                    "properties": {
                                        "listen": {
                                            "type": "array",
                                            "items": {"type": "string"}
                                        },
                                        "routes": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "match": {"type": "array"},
                                                    "handle": {"type": "array"}
                                                },
                                                "required": ["handle"]
                                            }
                                        }
                                    },
                                    "required": ["listen"]
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

def validate_config(config):
    """验证Caddy配置"""
    try:
        jsonschema.validate(config, caddy_schema)
        return True, None
    except jsonschema.ValidationError as e:
        return False, str(e)

# 使用示例
config = {
    "apps": {
        "http": {
            "servers": {
                "srv0": {
                    "listen": [":80"],
                    "routes": [
                        {
                            "handle": [{
                                "handler": "static_response",
                                "body": "Hello"
                            }]
                        }
                    ]
                }
            }
        }
    }
}

valid, error = validate_config(config)
if valid:
    print("Configuration is valid")
else:
    print(f"Configuration error: {error}")

8.6 监控和诊断

8.6.1 API监控

# 获取服务器状态
curl http://localhost:2019/config/apps/http/servers/srv0

# 获取路由信息
curl http://localhost:2019/config/apps/http/servers/srv0/routes

# 获取TLS证书信息
curl http://localhost:2019/config/apps/tls/certificates

8.6.2 配置备份和恢复

#!/usr/bin/env python3
import json
import datetime
import os

class CaddyBackup:
    def __init__(self, api_url="http://localhost:2019", backup_dir="backups"):
        self.api_url = api_url
        self.backup_dir = backup_dir
        os.makedirs(backup_dir, exist_ok=True)
    
    def backup_config(self):
        """备份当前配置"""
        import requests
        
        response = requests.get(f"{self.api_url}/config/")
        response.raise_for_status()
        
        timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_file = os.path.join(self.backup_dir, f"caddy_config_{timestamp}.json")
        
        with open(backup_file, 'w') as f:
            json.dump(response.json(), f, indent=2)
        
        print(f"Configuration backed up to {backup_file}")
        return backup_file
    
    def restore_config(self, backup_file):
        """恢复配置"""
        import requests
        
        with open(backup_file, 'r') as f:
            config = json.load(f)
        
        response = requests.post(f"{self.api_url}/load", json=config)
        response.raise_for_status()
        
        print(f"Configuration restored from {backup_file}")
    
    def list_backups(self):
        """列出所有备份"""
        backups = [f for f in os.listdir(self.backup_dir) if f.endswith('.json')]
        backups.sort(reverse=True)
        return backups

# 使用示例
if __name__ == "__main__":
    backup = CaddyBackup()
    
    # 创建备份
    backup.backup_config()
    
    # 列出备份
    backups = backup.list_backups()
    print("Available backups:")
    for b in backups:
        print(f"  {b}")

8.7 实战案例

8.7.1 动态负载均衡

#!/usr/bin/env python3
import requests
import time
import threading

class DynamicLoadBalancer:
    def __init__(self, caddy_api="http://localhost:2019"):
        self.caddy_api = caddy_api
        self.upstreams = []
        self.health_check_interval = 30
    
    def add_upstream(self, upstream):
        """添加上游服务器"""
        self.upstreams.append(upstream)
        self._update_caddy_config()
    
    def remove_upstream(self, upstream):
        """移除上游服务器"""
        if upstream in self.upstreams:
            self.upstreams.remove(upstream)
            self._update_caddy_config()
    
    def _update_caddy_config(self):
        """更新Caddy配置"""
        config = {
            "match": [{"host": ["api.example.com"]}],
            "handle": [{
                "handler": "reverse_proxy",
                "upstreams": [{"dial": upstream} for upstream in self.upstreams],
                "health_checks": {
                    "active": {
                        "path": "/health",
                        "interval": "30s",
                        "timeout": "5s"
                    }
                }
            }]
        }
        
        # 更新Caddy配置
        url = f"{self.caddy_api}/config/apps/http/servers/srv0/routes/0"
        response = requests.put(url, json=config)
        response.raise_for_status()
        print(f"Updated load balancer with {len(self.upstreams)} upstreams")
    
    def health_check(self):
        """健康检查"""
        healthy_upstreams = []
        
        for upstream in self.upstreams:
            try:
                response = requests.get(f"http://{upstream}/health", timeout=5)
                if response.status_code == 200:
                    healthy_upstreams.append(upstream)
            except requests.RequestException:
                print(f"Upstream {upstream} is unhealthy")
        
        if set(healthy_upstreams) != set(self.upstreams):
            self.upstreams = healthy_upstreams
            self._update_caddy_config()
    
    def start_health_monitoring(self):
        """启动健康监控"""
        def monitor():
            while True:
                self.health_check()
                time.sleep(self.health_check_interval)
        
        thread = threading.Thread(target=monitor, daemon=True)
        thread.start()
        print("Health monitoring started")

# 使用示例
if __name__ == "__main__":
    lb = DynamicLoadBalancer()
    
    # 添加上游服务器
    lb.add_upstream("localhost:8001")
    lb.add_upstream("localhost:8002")
    lb.add_upstream("localhost:8003")
    
    # 启动健康监控
    lb.start_health_monitoring()
    
    # 保持运行
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("Stopping load balancer")

8.7.2 自动SSL证书管理

#!/usr/bin/env python3
import requests
import json
from datetime import datetime, timedelta

class SSLManager:
    def __init__(self, caddy_api="http://localhost:2019"):
        self.caddy_api = caddy_api
    
    def add_domain(self, domain, email):
        """添加域名并自动获取SSL证书"""
        # 获取当前TLS配置
        tls_config = self._get_tls_config()
        
        # 添加新的自动化策略
        new_policy = {
            "subjects": [domain],
            "issuers": [
                {
                    "module": "acme",
                    "ca": "https://acme-v02.api.letsencrypt.org/directory",
                    "email": email,
                    "agreed": True
                }
            ]
        }
        
        if "automation" not in tls_config:
            tls_config["automation"] = {"policies": []}
        
        tls_config["automation"]["policies"].append(new_policy)
        
        # 更新TLS配置
        self._update_tls_config(tls_config)
        print(f"Added SSL automation for domain: {domain}")
    
    def _get_tls_config(self):
        """获取当前TLS配置"""
        try:
            response = requests.get(f"{self.caddy_api}/config/apps/tls")
            response.raise_for_status()
            return response.json()
        except requests.RequestException:
            return {}
    
    def _update_tls_config(self, config):
        """更新TLS配置"""
        response = requests.put(f"{self.caddy_api}/config/apps/tls", json=config)
        response.raise_for_status()
    
    def get_certificates(self):
        """获取所有证书信息"""
        response = requests.get(f"{self.caddy_api}/config/apps/tls/certificates")
        response.raise_for_status()
        return response.json()
    
    def check_certificate_expiry(self):
        """检查证书过期时间"""
        certificates = self.get_certificates()
        expiring_soon = []
        
        for cert_id, cert_info in certificates.items():
            # 这里需要解析证书信息来获取过期时间
            # 实际实现中需要使用cryptography库
            print(f"Certificate {cert_id}: {cert_info}")
        
        return expiring_soon

# 使用示例
if __name__ == "__main__":
    ssl_manager = SSLManager()
    
    # 为新域名添加SSL
    ssl_manager.add_domain("new.example.com", "admin@example.com")
    
    # 检查证书状态
    certificates = ssl_manager.get_certificates()
    print(json.dumps(certificates, indent=2))

8.8 本章总结

本章详细介绍了Caddy的Admin API和JSON配置格式:

  1. Admin API基础:了解API端点和基本操作
  2. JSON配置:掌握完整的JSON配置结构
  3. 动态管理:实现运行时配置更新
  4. 脚本自动化:使用Python和Bash进行配置管理
  5. 监控诊断:配置验证和健康检查
  6. 实战应用:动态负载均衡和SSL管理

8.9 练习题

  1. 使用Admin API创建一个新的虚拟主机
  2. 编写脚本自动备份和恢复Caddy配置
  3. 实现基于健康检查的动态上游管理
  4. 创建配置模板系统支持多环境部署
  5. 开发证书过期监控和自动续期系统

下一章预告:第九章将介绍Caddy的高级特性,包括中间件开发、自定义插件和企业级功能。