diff --git a/backend/api-gateway/deploy.sh b/backend/api-gateway/deploy.sh index 0fc7a0a8..7fd0f4eb 100644 --- a/backend/api-gateway/deploy.sh +++ b/backend/api-gateway/deploy.sh @@ -12,6 +12,8 @@ # ./deploy.sh health # 健康检查 # ./deploy.sh reload # 重载 Kong 配置 # ./deploy.sh routes # 查看所有路由 +# ./deploy.sh monitoring # 启动监控栈 (Prometheus + Grafana) +# ./deploy.sh metrics # 查看 Prometheus 指标 # ============================================================================= set -e @@ -28,8 +30,11 @@ PROJECT_NAME="rwa-api-gateway" KONG_ADMIN_URL="http://localhost:8001" KONG_PROXY_URL="http://localhost:8000" +# 脚本目录 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + # 切换到脚本所在目录 -cd "$(dirname "$0")" +cd "$SCRIPT_DIR" # 日志函数 log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } @@ -187,6 +192,65 @@ cmd_clean() { log_success "清理完成" } +# 启动监控栈 +cmd_monitoring_up() { + log_info "启动监控栈 (Prometheus + Grafana)..." + $COMPOSE_CMD -f docker-compose.yml -f docker-compose.monitoring.yml up -d prometheus grafana + + log_info "等待服务启动..." + sleep 5 + + log_success "监控栈启动成功!" + echo "" + echo "监控服务地址:" + echo " Grafana: http://localhost:3030 (admin/admin123)" + echo " Prometheus: http://localhost:9090" + echo " Kong 指标: http://localhost:8001/metrics" + echo "" +} + +# 安装监控栈 (包括 Nginx + SSL) +cmd_monitoring_install() { + local domain="${1:-monitor.szaiai.com}" + log_info "安装监控栈..." + + if [ ! -f "$SCRIPT_DIR/scripts/install-monitor.sh" ]; then + log_error "安装脚本不存在: scripts/install-monitor.sh" + exit 1 + fi + + sudo bash "$SCRIPT_DIR/scripts/install-monitor.sh" "$domain" +} + +# 停止监控栈 +cmd_monitoring_down() { + log_info "停止监控栈..." + docker stop rwa-prometheus rwa-grafana 2>/dev/null || true + docker rm rwa-prometheus rwa-grafana 2>/dev/null || true + log_success "监控栈已停止" +} + +# 查看 Prometheus 指标 +cmd_metrics() { + log_info "Kong Prometheus 指标概览:" + echo "" + + # 获取关键指标 + metrics=$(curl -s $KONG_ADMIN_URL/metrics 2>/dev/null) + + if [ $? -eq 0 ]; then + echo "=== 请求统计 ===" + echo "$metrics" | grep -E "^kong_http_requests_total" | head -20 + echo "" + echo "=== 延迟统计 ===" + echo "$metrics" | grep -E "^kong_latency_" | head -10 + echo "" + echo "完整指标: curl $KONG_ADMIN_URL/metrics" + else + log_error "无法获取指标,请确保 Kong 正在运行且 prometheus 插件已启用" + fi +} + # 显示帮助 show_help() { echo "" @@ -207,6 +271,13 @@ show_help() { echo " services 查看所有服务" echo " test 测试 API 路由" echo " clean 清理容器和数据" + echo "" + echo "监控命令:" + echo " monitoring install [domain] 一键安装监控 (Nginx+SSL+服务)" + echo " monitoring up 启动监控栈" + echo " monitoring down 停止监控栈" + echo " metrics 查看 Prometheus 指标" + echo "" echo " help 显示帮助" echo "" echo "注意: 需要先启动 backend/services 才能启动 Kong" @@ -255,6 +326,27 @@ main() { clean) cmd_clean ;; + monitoring) + case "${2:-up}" in + install) + cmd_monitoring_install "$3" + ;; + up) + cmd_monitoring_up + ;; + down) + cmd_monitoring_down + ;; + *) + log_error "未知监控命令: $2" + echo "用法: ./deploy.sh monitoring [install|up|down]" + exit 1 + ;; + esac + ;; + metrics) + cmd_metrics + ;; help|--help|-h) show_help ;; diff --git a/backend/api-gateway/docker-compose.monitoring.yml b/backend/api-gateway/docker-compose.monitoring.yml new file mode 100644 index 00000000..5dab27d7 --- /dev/null +++ b/backend/api-gateway/docker-compose.monitoring.yml @@ -0,0 +1,62 @@ +# ============================================================================= +# Kong Monitoring Stack - Prometheus + Grafana +# ============================================================================= +# Usage: +# docker compose -f docker-compose.yml -f docker-compose.monitoring.yml up -d +# ============================================================================= + +services: + # =========================================================================== + # Prometheus - 指标收集 + # =========================================================================== + prometheus: + image: prom/prometheus:latest + container_name: rwa-prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.console.libraries=/usr/share/prometheus/console_libraries' + - '--web.console.templates=/usr/share/prometheus/consoles' + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro + - prometheus_data:/prometheus + ports: + - "9090:9090" + restart: unless-stopped + networks: + - rwa-network + + # =========================================================================== + # Grafana - 可视化仪表盘 + # =========================================================================== + grafana: + image: grafana/grafana:latest + container_name: rwa-grafana + environment: + - GF_SECURITY_ADMIN_USER=admin + - GF_SECURITY_ADMIN_PASSWORD=admin123 + - GF_USERS_ALLOW_SIGN_UP=false + # 反向代理支持 + - GF_SERVER_ROOT_URL=https://monitor.szaiai.com + - GF_SERVER_SERVE_FROM_SUB_PATH=false + volumes: + - grafana_data:/var/lib/grafana + - ./grafana/provisioning:/etc/grafana/provisioning:ro + ports: + - "3030:3000" + depends_on: + - prometheus + restart: unless-stopped + networks: + - rwa-network + +volumes: + prometheus_data: + driver: local + grafana_data: + driver: local + +networks: + rwa-network: + external: true + name: api-gateway_rwa-network diff --git a/backend/api-gateway/grafana/provisioning/dashboards/dashboards.yml b/backend/api-gateway/grafana/provisioning/dashboards/dashboards.yml new file mode 100644 index 00000000..aa440694 --- /dev/null +++ b/backend/api-gateway/grafana/provisioning/dashboards/dashboards.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +providers: + - name: 'Kong API Gateway' + orgId: 1 + folder: '' + type: file + disableDeletion: false + updateIntervalSeconds: 10 + options: + path: /etc/grafana/provisioning/dashboards diff --git a/backend/api-gateway/grafana/provisioning/dashboards/kong-dashboard.json b/backend/api-gateway/grafana/provisioning/dashboards/kong-dashboard.json new file mode 100644 index 00000000..869379af --- /dev/null +++ b/backend/api-gateway/grafana/provisioning/dashboards/kong-dashboard.json @@ -0,0 +1,612 @@ +{ + "annotations": { + "list": [] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "sum(rate(kong_http_requests_total[5m])) by (service)", + "legendFormat": "{{service}}", + "refId": "A" + } + ], + "title": "API 请求速率 (按服务)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "histogram_quantile(0.95, sum(rate(kong_latency_bucket{type=\"request\"}[5m])) by (le, service))", + "legendFormat": "{{service}} - P95", + "refId": "A" + } + ], + "title": "请求延迟 P95 (按服务)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "displayLabels": ["name", "value"], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": ["value"] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "sum(increase(kong_http_requests_total[1h])) by (service)", + "legendFormat": "{{service}}", + "refId": "A" + } + ], + "title": "过去1小时请求分布 (按服务)", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "sum(rate(kong_http_requests_total{code=~\"5..\"}[5m])) by (service)", + "legendFormat": "{{service}} - 5xx", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "sum(rate(kong_http_requests_total{code=~\"4..\"}[5m])) by (service)", + "legendFormat": "{{service}} - 4xx", + "refId": "B" + } + ], + "title": "错误率 (4xx/5xx)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 16 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "sum(increase(kong_http_requests_total[24h]))", + "legendFormat": "", + "refId": "A" + } + ], + "title": "24小时总请求数", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 1 + }, + { + "color": "red", + "value": 5 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 16 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "sum(rate(kong_http_requests_total{code=~\"5..\"}[5m])) / sum(rate(kong_http_requests_total[5m]))", + "legendFormat": "", + "refId": "A" + } + ], + "title": "5xx 错误率", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 500 + }, + { + "color": "red", + "value": 1000 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 16 + }, + "id": 7, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "histogram_quantile(0.95, sum(rate(kong_latency_bucket{type=\"request\"}[5m])) by (le))", + "legendFormat": "", + "refId": "A" + } + ], + "title": "整体 P95 延迟", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 16 + }, + "id": 8, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "expr": "sum(rate(kong_http_requests_total[5m]))", + "legendFormat": "", + "refId": "A" + } + ], + "title": "当前 QPS", + "type": "stat" + } + ], + "refresh": "10s", + "schemaVersion": 38, + "style": "dark", + "tags": ["kong", "api-gateway"], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Kong API Gateway 监控", + "uid": "kong-dashboard", + "version": 1, + "weekStart": "" +} diff --git a/backend/api-gateway/grafana/provisioning/datasources/datasources.yml b/backend/api-gateway/grafana/provisioning/datasources/datasources.yml new file mode 100644 index 00000000..bb009bb2 --- /dev/null +++ b/backend/api-gateway/grafana/provisioning/datasources/datasources.yml @@ -0,0 +1,9 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + editable: false diff --git a/backend/api-gateway/kong.yml b/backend/api-gateway/kong.yml index 5c0eba30..dbeb26d8 100644 --- a/backend/api-gateway/kong.yml +++ b/backend/api-gateway/kong.yml @@ -226,3 +226,12 @@ plugins: config: allowed_payload_size: 50 size_unit: megabytes + + # Prometheus 监控指标 + - name: prometheus + config: + per_consumer: true + status_code_metrics: true + latency_metrics: true + bandwidth_metrics: true + upstream_health_metrics: true diff --git a/backend/api-gateway/prometheus.yml b/backend/api-gateway/prometheus.yml new file mode 100644 index 00000000..3a0b3fcb --- /dev/null +++ b/backend/api-gateway/prometheus.yml @@ -0,0 +1,19 @@ +# ============================================================================= +# Prometheus 配置 - Kong API Gateway 监控 +# ============================================================================= + +global: + scrape_interval: 15s + evaluation_interval: 15s + +scrape_configs: + # Kong Prometheus 指标端点 + - job_name: 'kong' + static_configs: + - targets: ['kong:8001'] + metrics_path: /metrics + + # Prometheus 自身监控 + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] diff --git a/backend/api-gateway/scripts/install-monitor.sh b/backend/api-gateway/scripts/install-monitor.sh new file mode 100644 index 00000000..5352f1bf --- /dev/null +++ b/backend/api-gateway/scripts/install-monitor.sh @@ -0,0 +1,368 @@ +#!/bin/bash +# ============================================================================= +# Kong 监控栈一键安装脚本 +# ============================================================================= +# 功能: +# - 自动配置 Nginx 反向代理 +# - 自动申请 Let's Encrypt SSL 证书 +# - 启动 Prometheus + Grafana 监控服务 +# +# 用法: +# ./install-monitor.sh # 使用默认域名 monitor.szaiai.com +# ./install-monitor.sh mydomain.com # 使用自定义域名 +# ============================================================================= + +set -e + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +# 日志函数 +log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}[OK]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1"; } +log_step() { echo -e "${CYAN}[STEP]${NC} $1"; } + +# 默认配置 +DOMAIN="${1:-monitor.szaiai.com}" +GRAFANA_PORT=3030 +PROMETHEUS_PORT=9090 +GRAFANA_USER="admin" +GRAFANA_PASS="admin123" + +# 获取脚本目录 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +# 显示 Banner +show_banner() { + echo -e "${CYAN}" + echo "╔═══════════════════════════════════════════════════════════════╗" + echo "║ Kong 监控栈一键安装脚本 ║" + echo "║ Prometheus + Grafana ║" + echo "╚═══════════════════════════════════════════════════════════════╝" + echo -e "${NC}" + echo "域名: $DOMAIN" + echo "Grafana 端口: $GRAFANA_PORT" + echo "Prometheus 端口: $PROMETHEUS_PORT" + echo "" +} + +# 检查 root 权限 +check_root() { + if [ "$EUID" -ne 0 ]; then + log_error "请使用 root 权限运行此脚本" + echo "用法: sudo $0 [domain]" + exit 1 + fi +} + +# 检查依赖 +check_dependencies() { + log_step "检查依赖..." + + local missing=() + + if ! command -v docker &> /dev/null; then + missing+=("docker") + fi + + if ! command -v nginx &> /dev/null; then + missing+=("nginx") + fi + + if ! command -v certbot &> /dev/null; then + missing+=("certbot") + fi + + if [ ${#missing[@]} -gt 0 ]; then + log_error "缺少依赖: ${missing[*]}" + echo "" + echo "请先安装:" + echo " apt update && apt install -y docker.io nginx certbot python3-certbot-nginx" + exit 1 + fi + + log_success "依赖检查通过" +} + +# 检查 DNS 解析 +check_dns() { + log_step "检查 DNS 解析..." + + local resolved_ip=$(dig +short $DOMAIN 2>/dev/null | head -1) + local server_ip=$(curl -s ifconfig.me 2>/dev/null || curl -s icanhazip.com 2>/dev/null) + + if [ -z "$resolved_ip" ]; then + log_error "无法解析域名 $DOMAIN" + echo "请先在 DNS 管理面板添加 A 记录:" + echo " $DOMAIN -> $server_ip" + exit 1 + fi + + if [ "$resolved_ip" != "$server_ip" ]; then + log_warn "DNS 解析的 IP ($resolved_ip) 与本机公网 IP ($server_ip) 不匹配" + read -p "是否继续? [y/N] " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi + fi + + log_success "DNS 解析正确: $DOMAIN -> $resolved_ip" +} + +# 生成 Nginx 配置 +generate_nginx_config() { + log_step "生成 Nginx 配置..." + + cat > /etc/nginx/sites-available/$DOMAIN.conf << EOF +# Kong 监控面板 Nginx 配置 +# 自动生成于 $(date) + +# HTTP -> HTTPS 重定向 +server { + listen 80; + listen [::]:80; + server_name $DOMAIN; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://\$host\$request_uri; + } +} + +# HTTPS 配置 +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name $DOMAIN; + + # SSL 证书 (Let's Encrypt) + ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem; + + # SSL 优化 + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:50m; + ssl_session_tickets off; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers off; + + # HSTS + add_header Strict-Transport-Security "max-age=63072000" always; + + # 日志 + access_log /var/log/nginx/$DOMAIN.access.log; + error_log /var/log/nginx/$DOMAIN.error.log; + + # Grafana + location / { + proxy_pass http://127.0.0.1:$GRAFANA_PORT; + proxy_http_version 1.1; + proxy_set_header Upgrade \$http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_cache_bypass \$http_upgrade; + proxy_read_timeout 86400; + } + + # Prometheus (仅内网) + location /prometheus/ { + allow 127.0.0.1; + allow 10.0.0.0/8; + allow 172.16.0.0/12; + allow 192.168.0.0/16; + deny all; + + proxy_pass http://127.0.0.1:$PROMETHEUS_PORT/; + proxy_http_version 1.1; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + } + + # 健康检查 + location = /health { + access_log off; + return 200 '{"status":"ok","service":"monitor-nginx"}'; + add_header Content-Type application/json; + } +} +EOF + + log_success "Nginx 配置已生成: /etc/nginx/sites-available/$DOMAIN.conf" +} + +# 申请 SSL 证书 +obtain_ssl_cert() { + log_step "申请 SSL 证书..." + + # 检查证书是否已存在 + if [ -f "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" ]; then + log_success "SSL 证书已存在" + return 0 + fi + + # 创建 certbot webroot 目录 + mkdir -p /var/www/certbot + + # 临时启用 HTTP 配置用于验证 + cat > /etc/nginx/sites-available/$DOMAIN-temp.conf << EOF +server { + listen 80; + server_name $DOMAIN; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 200 'Waiting for SSL...'; + add_header Content-Type text/plain; + } +} +EOF + + ln -sf /etc/nginx/sites-available/$DOMAIN-temp.conf /etc/nginx/sites-enabled/ + nginx -t && systemctl reload nginx + + # 申请证书 + certbot certonly --webroot -w /var/www/certbot -d $DOMAIN --non-interactive --agree-tos --email admin@$DOMAIN || { + log_error "SSL 证书申请失败" + rm -f /etc/nginx/sites-enabled/$DOMAIN-temp.conf + rm -f /etc/nginx/sites-available/$DOMAIN-temp.conf + exit 1 + } + + # 清理临时配置 + rm -f /etc/nginx/sites-enabled/$DOMAIN-temp.conf + rm -f /etc/nginx/sites-available/$DOMAIN-temp.conf + + log_success "SSL 证书申请成功" +} + +# 启用 Nginx 配置 +enable_nginx_config() { + log_step "启用 Nginx 配置..." + + ln -sf /etc/nginx/sites-available/$DOMAIN.conf /etc/nginx/sites-enabled/ + + nginx -t || { + log_error "Nginx 配置测试失败" + exit 1 + } + + systemctl reload nginx + log_success "Nginx 配置已启用" +} + +# 启动监控服务 +start_monitoring_services() { + log_step "启动监控服务..." + + cd "$PROJECT_DIR" + + # 检查 Kong 是否运行 + if ! docker ps | grep -q rwa-kong; then + log_warn "Kong 未运行,先启动 Kong..." + docker compose up -d + sleep 10 + fi + + # 同步 Kong 配置 (启用 prometheus 插件) + log_info "同步 Kong 配置..." + docker compose run --rm kong-config || log_warn "配置同步失败,可能已是最新" + + # 启动监控栈 + log_info "启动 Prometheus + Grafana..." + docker compose -f docker-compose.yml -f docker-compose.monitoring.yml up -d prometheus grafana + + # 等待服务启动 + sleep 5 + + # 检查服务状态 + if docker ps | grep -q rwa-grafana && docker ps | grep -q rwa-prometheus; then + log_success "监控服务启动成功" + else + log_error "监控服务启动失败" + docker compose -f docker-compose.yml -f docker-compose.monitoring.yml logs --tail=50 + exit 1 + fi +} + +# 显示安装结果 +show_result() { + echo "" + echo -e "${GREEN}╔═══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${GREEN}║ 安装完成! ║${NC}" + echo -e "${GREEN}╚═══════════════════════════════════════════════════════════════╝${NC}" + echo "" + echo "访问地址:" + echo -e " Grafana: ${CYAN}https://$DOMAIN${NC}" + echo -e " 用户名: ${YELLOW}$GRAFANA_USER${NC}" + echo -e " 密码: ${YELLOW}$GRAFANA_PASS${NC}" + echo "" + echo "Prometheus (仅内网可访问):" + echo -e " 地址: ${CYAN}https://$DOMAIN/prometheus/${NC}" + echo "" + echo "Kong 指标端点:" + echo -e " 地址: ${CYAN}http://localhost:8001/metrics${NC}" + echo "" + echo "管理命令:" + echo " ./deploy.sh monitoring up # 启动监控" + echo " ./deploy.sh monitoring down # 停止监控" + echo " ./deploy.sh metrics # 查看指标" + echo "" +} + +# 卸载函数 +uninstall() { + log_warn "正在卸载监控栈..." + + # 停止服务 + cd "$PROJECT_DIR" + docker stop rwa-prometheus rwa-grafana 2>/dev/null || true + docker rm rwa-prometheus rwa-grafana 2>/dev/null || true + + # 删除 Nginx 配置 + rm -f /etc/nginx/sites-enabled/$DOMAIN.conf + rm -f /etc/nginx/sites-available/$DOMAIN.conf + systemctl reload nginx 2>/dev/null || true + + log_success "监控栈已卸载" + echo "注意: SSL 证书未删除,如需删除请运行: certbot delete --cert-name $DOMAIN" +} + +# 主函数 +main() { + show_banner + + # 检查是否卸载 + if [ "$1" = "uninstall" ] || [ "$1" = "--uninstall" ]; then + uninstall + exit 0 + fi + + check_root + check_dependencies + check_dns + generate_nginx_config + obtain_ssl_cert + enable_nginx_config + start_monitoring_services + show_result +} + +main "$@"