Files
second-mind-aep/Идеи/Оптимизация ресурсов VPS/Webhook Server на Go.md
2025-08-04 15:28:34 +04:00

13 KiB

Webhook Server на Go

1. Обзор проекта

1.1 Назначение

Webhook сервер на Go для автоматической сборки и деплоя Hugo сайта при изменениях в Git репозитории.

1.2 Ключевые функции

  • Обработка webhook от Git (GitHub, GitLab, Gitea)
  • Автоматическое клонирование/обновление репозитория
  • Запуск Hugo сборки
  • Валидация и безопасность
  • Логирование и мониторинг
  • Обработка ошибок и retry логика

2. Архитектура

2.1 Компонентная диаграмма

graph TB
    A[Git Webhook] --> B[Webhook Handler]
    B --> C[Validator]
    C --> D[Git Client]
    D --> E[Repository Manager]
    E --> F[Hugo Builder]
    F --> G[File System]
    G --> H[Nginx Reload]
    
    subgraph "Webhook Server"
        B
        C
        D
        E
        F
    end
    
    subgraph "External"
        A
        G
        H
    end

2.2 Структура проекта

webhook-server/
├── cmd/
│   └── server/
│       └── main.go              # Точка входа приложения
├── internal/
│   ├── handler/
│   │   ├── webhook.go          # Обработчик webhook
│   │   └── health.go           # Health check endpoint
│   ├── builder/
│   │   ├── hugo.go             # Hugo сборка
│   │   └── git.go              # Git операции
│   ├── config/
│   │   └── config.go           # Конфигурация
│   ├── validator/
│   │   └── webhook.go          # Валидация webhook
│   └── logger/
│       └── logger.go           # Структурированное логирование
├── pkg/
│   ├── git/
│   │   └── client.go           # Git клиент
│   └── metrics/
│       └── prometheus.go       # Метрики Prometheus
├── configs/
│   ├── config.yaml             # Конфигурация
│   └── nginx.conf              # Nginx конфигурация
├── scripts/
│   ├── build.sh                # Скрипт сборки
│   └── deploy.sh               # Скрипт деплоя
├── go.mod
├── go.sum
├── Dockerfile
├── docker-compose.yml
└── README.md

3. Техническая реализация

3.1 Основные зависимости

// go.mod
module webhook-server

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/go-git/go-git/v5 v5.8.1
    github.com/prometheus/client_golang v1.17.0
    github.com/sirupsen/logrus v1.9.3
    github.com/spf13/viper v1.17.0
    gopkg.in/go-playground/webhooks.v5 v5.17.0
)

3.2 Конфигурация

# configs/config.yaml
server:
  port: 8080
  host: "0.0.0.0"

git:
  repository: "https://github.com/user/second-mind"
  branch: "main"
  webhook_secret: "your-webhook-secret"
  ssh_key_path: "/root/.ssh/id_rsa"

hugo:
  source_path: "/app/hugo-site"
  output_path: "/var/www/html"
  config_file: "config.toml"
  base_url: "https://aepif.ru"

nginx:
  config_path: "/etc/nginx/nginx.conf"
  reload_command: "nginx -s reload"

logging:
  level: "info"
  format: "json"

metrics:
  enabled: true
  port: 9090

3.3 Основные компоненты

Webhook Handler

// internal/handler/webhook.go
package handler

import (
    "net/http"
    "github.com/gin-gonic/gin"
    "github.com/sirupsen/logrus"
)

type WebhookHandler struct {
    gitClient   *git.Client
    hugoBuilder *builder.HugoBuilder
    validator   *validator.WebhookValidator
    logger      *logrus.Logger
}

func (h *WebhookHandler) HandleWebhook(c *gin.Context) {
    // Валидация webhook
    if err := h.validator.Validate(c.Request); err != nil {
        h.logger.WithError(err).Error("Webhook validation failed")
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // Асинхронная обработка
    go h.processWebhook()

    c.JSON(http.StatusOK, gin.H{"status": "processing"})
}

func (h *WebhookHandler) processWebhook() {
    // Клонирование/обновление репозитория
    if err := h.gitClient.Pull(); err != nil {
        h.logger.WithError(err).Error("Git pull failed")
        return
    }

    // Hugo сборка
    if err := h.hugoBuilder.Build(); err != nil {
        h.logger.WithError(err).Error("Hugo build failed")
        return
    }

    // Перезагрузка Nginx
    if err := h.reloadNginx(); err != nil {
        h.logger.WithError(err).Error("Nginx reload failed")
        return
    }

    h.logger.Info("Deployment completed successfully")
}

Hugo Builder

// internal/builder/hugo.go
package builder

import (
    "os/exec"
    "path/filepath"
    "github.com/sirupsen/logrus"
)

type HugoBuilder struct {
    sourcePath string
    outputPath string
    configFile string
    logger     *logrus.Logger
}

func (h *HugoBuilder) Build() error {
    h.logger.Info("Starting Hugo build")

    cmd := exec.Command("hugo",
        "--source", h.sourcePath,
        "--destination", h.outputPath,
        "--config", filepath.Join(h.sourcePath, h.configFile),
        "--minify",
        "--cleanDestinationDir",
    )

    output, err := cmd.CombinedOutput()
    if err != nil {
        h.logger.WithError(err).WithField("output", string(output)).Error("Hugo build failed")
        return err
    }

    h.logger.WithField("output", string(output)).Info("Hugo build completed")
    return nil
}

Git Client

// pkg/git/client.go
package git

import (
    "github.com/go-git/go-git/v5"
    "github.com/go-git/go-git/v5/plumbing"
    "github.com/sirupsen/logrus"
)

type Client struct {
    repositoryPath string
    remoteURL      string
    branch         string
    logger         *logrus.Logger
}

func (c *Client) Pull() error {
    c.logger.Info("Starting git pull")

    repo, err := git.PlainOpen(c.repositoryPath)
    if err != nil {
        return err
    }

    worktree, err := repo.Worktree()
    if err != nil {
        return err
    }

    err = worktree.Pull(&git.PullOptions{
        RemoteName: "origin",
        BranchName: plumbing.NewBranchReferenceName(c.branch),
    })

    if err != nil && err != git.NoErrAlreadyUpToDate {
        c.logger.WithError(err).Error("Git pull failed")
        return err
    }

    c.logger.Info("Git pull completed")
    return nil
}

3.4 Метрики и мониторинг

// pkg/metrics/prometheus.go
package metrics

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
)

var (
    BuildDuration = promauto.NewHistogram(prometheus.HistogramOpts{
        Name: "hugo_build_duration_seconds",
        Help: "Duration of Hugo build in seconds",
    })

    BuildSuccess = promauto.NewCounter(prometheus.CounterOpts{
        Name: "hugo_build_success_total",
        Help: "Total number of successful builds",
    })

    BuildFailures = promauto.NewCounter(prometheus.CounterOpts{
        Name: "hugo_build_failures_total",
        Help: "Total number of failed builds",
    })

    WebhookRequests = promauto.NewCounter(prometheus.CounterOpts{
        Name: "webhook_requests_total",
        Help: "Total number of webhook requests",
    })
)

4. Docker контейнеризация

4.1 Dockerfile

# Многоэтапная сборка
FROM golang:1.21-alpine AS builder

# Установка зависимостей
RUN apk add --no-cache git ca-certificates tzdata

WORKDIR /app

# Копирование go.mod и go.sum
COPY go.mod go.sum ./
RUN go mod download

# Копирование исходного кода
COPY . .

# Сборка приложения
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/server

# Финальный образ
FROM alpine:latest

# Установка необходимых пакетов
RUN apk --no-cache add ca-certificates tzdata hugo git openssh-client nginx

# Создание пользователя
RUN addgroup -g 1000 -S appgroup && \
    adduser -u 1000 -S appuser -G appgroup

WORKDIR /app

# Копирование бинарника
COPY --from=builder /app/main .

# Копирование конфигурации
COPY configs/ /app/configs/

# Создание директорий
RUN mkdir -p /var/www/html /root/.ssh && \
    chown -R appuser:appgroup /app /var/www/html

# Переключение на пользователя
USER appuser

EXPOSE 8080 9090

CMD ["./main"]

4.2 Docker Compose

# docker-compose.yml
version: '3.8'

services:
  webhook-server:
    build: .
    ports:
      - "8080:8080"
      - "9090:9090"
    volumes:
      - ./configs:/app/configs
      - /var/www/html:/var/www/html
      - ~/.ssh:/root/.ssh:ro
    environment:
      - CONFIG_PATH=/app/configs/config.yaml
    restart: unless-stopped
    networks:
      - webhook-network

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/www/html:/usr/share/nginx/html
      - ./configs/nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - webhook-server
    restart: unless-stopped
    networks:
      - webhook-network

networks:
  webhook-network:
    driver: bridge

5. Безопасность

5.1 Webhook валидация

  • Проверка подписи webhook
  • Валидация payload
  • Rate limiting
  • IP whitelist

5.2 Git безопасность

  • SSH ключи для аутентификации
  • Проверка подписи коммитов
  • Ограничение доступа к репозиторию

5.3 Системная безопасность

  • Запуск от непривилегированного пользователя
  • Минимальные права доступа
  • Регулярные обновления зависимостей

6. Мониторинг и логирование

6.1 Структурированное логирование

// internal/logger/logger.go
func SetupLogger(level, format string) *logrus.Logger {
    logger := logrus.New()
    
    // Уровень логирования
    if level, err := logrus.ParseLevel(level); err == nil {
        logger.SetLevel(level)
    }
    
    // Формат логирования
    if format == "json" {
        logger.SetFormatter(&logrus.JSONFormatter{})
    }
    
    return logger
}

6.2 Health Check

// internal/handler/health.go
func (h *HealthHandler) HealthCheck(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "status": "healthy",
        "timestamp": time.Now().Unix(),
        "version": "1.0.0",
    })
}

7. Развертывание

7.1 Скрипт сборки

#!/bin/bash
# scripts/build.sh

set -e

echo "Building webhook server..."

# Сборка Docker образа
docker build -t webhook-server:latest .

echo "Build completed successfully"

7.2 Скрипт деплоя

#!/bin/bash
# scripts/deploy.sh

set -e

echo "Deploying webhook server..."

# Остановка существующих контейнеров
docker-compose down

# Запуск новых контейнеров
docker-compose up -d

echo "Deployment completed successfully"

8. Тестирование

8.1 Unit тесты

// internal/handler/webhook_test.go
func TestWebhookHandler_HandleWebhook(t *testing.T) {
    // Тесты валидации webhook
    // Тесты обработки ошибок
    // Тесты успешной обработки
}

8.2 Integration тесты

// tests/integration_test.go
func TestFullDeploymentFlow(t *testing.T) {
    // Тест полного цикла деплоя
    // Тест обработки ошибок
    // Тест метрик
}

9. Документация API

9.1 Endpoints

POST /webhook

  • Обработка webhook от Git
  • Валидация подписи
  • Асинхронная обработка

GET /health

  • Health check endpoint
  • Статус сервиса
  • Версия приложения

GET /metrics

  • Prometheus метрики
  • Метрики сборки
  • Метрики webhook

9.2 Примеры запросов

# Health check
curl http://localhost:8080/health

# Метрики
curl http://localhost:9090/metrics

# Webhook (пример)
curl -X POST http://localhost:8080/webhook \
  -H "Content-Type: application/json" \
  -H "X-GitHub-Event: push" \
  -d '{"ref":"refs/heads/main"}'