Files
2026-11-04 00:53:46 +01:00

30 KiB
Raw Permalink Blame History

htop-web: Веб-интерфейс мониторинга системных ресурсов на базе Spring Boot WebFlux и Angular


Аннотация

В статье рассматривается архитектура и реализация htop-web — самохостимого веб-приложения для мониторинга системных ресурсов Linux-серверов в реальном времени. Приложение объединяет реактивный бэкенд на Spring Boot WebFlux с фронтендом на Angular 22, упаковывая оба компонента в единый Docker-контейнер. Ключевая особенность решения — сбор метрик хост-системы из контейнеризованной среды через монтирование файловых систем /proc и /sys хоста, что позволяет получить точные данные о CPU, памяти, дисках и сетевых интерфейсах реального сервера, а не виртуальной среды контейнера.

Ключевые слова: мониторинг, Spring Boot, WebFlux, Angular, Docker, контейнеризация, OSHI, системные метрики.


1. Введение

Мониторинг системных ресурсов является критически важной задачей при администрировании серверной инфраструктуры. Классические инструменты, такие как htop, top, vmstat и iostat, предоставляют детальную информацию, однако требуют доступа к терминалу и не подходят для удалённого мониторинга через веб-браузер.

Существующие веб-решения мониторинга (Grafana + Prometheus, Netdata, Zabbix) предлагают мощные возможности визуализации и агрегации, но часто требуют значительных ресурсов и сложной настройки. Для небольших инфраструктур и индивидуальных серверов возникает потребность в лёгком, самодостаточном решении, которое:

  • Разворачивается за несколько команд;
  • Не требует внешних баз данных или брокеров сообщений;
  • Предоставляет информацию в реальном времени;
  • Занимает минимальные ресурсы хоста.

htop-web отвечает этим требованиям, предоставляя компактный веб-дашборд, который собирает метрики напрямую из файловых систем Linux и отображает их через REST API и реактивный фронтенд.


2. Обзор существующих решений

Решение Тип Ресурсы Настройка Особенности
htop / top CLI Минимальные Нет Требует терминала, нет веб-интерфейса
Grafana + Prometheus Веб Высокие Сложная Агрегация, алерты, требует Node Exporter
Netdata Веб Средние Средняя Real-time, но тяжёлый агент
Zabbix Веб Высокие Сложная Корпоративный мониторинг, алертинг
Glances Веб/CLI Низкие Простая Python-зависимости, ограниченный UI
htop-web Веб Низкие Простая Один контейнер, нет внешних зависимостей

htop-web занимает нишу между CLI-инструментами и тяжёлыми системами мониторинга, предлагая минималистичный подход с фокусом на простоту развёртывания.


3. Архитектура системы

3.1 Общая схема

┌─────────────────────────────────────────────────────┐
│                    Веб-браузер                       │
│              (Angular 22, TypeScript)                │
└──────────────────────┬──────────────────────────────┘
                       │ HTTP (polling, 1 сек)
                       ▼
┌─────────────────────────────────────────────────────┐
│              Docker-контейнер                        │
│                                                     │
│  ┌─────────────────────────────────────────────┐    │
│  │         Spring Boot WebFlux (Java 21)       │    │
│  │                                             │    │
│  │  REST API: /api/system/*                    │    │
│  │    ├─ /metrics     — CPU, RAM, ОС           │    │
│  │    ├─ /processes   — список процессов        │    │
│  │    ├─ /linux       — uptime, load average    │    │
│  │    ├─ /cpu         — детали по ядрам        │    │
│  │    ├─ /linux/memory — детальная память       │    │
│  │    ├─ /linux/disks  — диски и файловые сист. │    │
│  │    └─ /linux/network — сетевые интерфейсы    │    │
│  │                                             │    │
│  │  Сервисы сбора метрик:                      │    │
│  │    ├─ SystemMetricsService                  │    │
│  │    ├─ SystemProcessesService                │    │
│  │    ├─ LinuxDetailsService                   │    │
│  │    ├─ CpuDetailsService                     │    │
│  │    ├─ LinuxMemoryService                    │    │
│  │    ├─ LinuxDiskService                      │    │
│  │    └─ LinuxNetworkService                   │    │
│  │                                             │    │
│  │  Статические ресурсы Angular                │    │
│  └─────────────────────────────────────────────┘    │
│                                                     │
│  Монтированные тома хоста:                           │
│    /host/root  ← / (корень хоста, read-only)        │
│    /host/sys   ← /sys (системные данные, read-only) │
└─────────────────────────────────────────────────────┘

3.2 Ключевые принципы архитектуры

  1. Single-container deployment — фронтенд и бэкенд упакованы в один JAR-файл и один Docker-контейнер.
  2. Host filesystem mounting — метрики хоста получаются через монтирование /proc и /sys хоста в контейнер.
  3. Reactive backend — Spring WebFlux обеспечивает неблокирующую обработку запросов.
  4. RxJS polling — фронтенд использует реактивные потоки для периодического опроса API.
  5. Fallback mechanism — при недоступности файлов хоста используется библиотека OSHI как запасной вариант.

4. Реализация бэкенда

4.1 Технологический стек

Компонент Технология Версия
Язык Java 21
Фреймворк Spring Boot 4.1.2
Реактивность Spring WebFlux 4.1.2
Системные метрики OSHI 7.7.2
Сборка Gradle 8.x

4.2 REST API контроллер

Контроллер SystemMetricsController экспонирует шесть REST-эндпоинтов, каждый из которых делегирует сбор данных соответствующему сервису:

@RestController
@RequestMapping("/api/system")
public class SystemMetricsController {

    private final SystemMetricsService _systemMetricsService;
    private final LinuxDetailsService _linuxDetailsService;
    private final CpuDetailsService _cpuDetailsService;
    private final LinuxMemoryService _linuxMemoryService;
    private final LinuxDiskService _linuxDiskService;
    private final LinuxNetworkService _linuxNetworkService;

    @GetMapping("/metrics")
    public Mono<SystemMetricsResponse> getMetrics() {
        return Mono.fromCallable(_systemMetricsService::getMetrics)
                .subscribeOn(Schedulers.boundedElastic());
    }

    @GetMapping("/linux")
    public Mono<LinuxDetailsResponse> getLinuxDetails() {
        return Mono.fromCallable(_linuxDetailsService::getDetails)
                .subscribeOn(Schedulers.boundedElastic());
    }
    // ... остальные эндпоинты
}

Каждый метод оборачивает синхронный вызов сервиса в Mono и выполняет его на Schedulers.boundedElastic(), что предотвращает блокировку реактивного пула потоков при чтении файловых систем.

4.3 Сбор метрик CPU и памяти

Сервис SystemMetricsService демонстрирует ключевой паттерн проекта — чтение метрик хоста из смонтированной файловой системы с fallback на OSHI:

@Service
public class SystemMetricsService {

    private static final Path HOST_ROOT = Path.of("/host/root");
    private static final Path HOST_PROC = HOST_ROOT.resolve("proc");

    public synchronized SystemMetricsResponse getMetrics() {
        CpuTicks currentTicks = readHostCpuTicks();
        double cpuUsage = 0.0;

        if (currentTicks != null && _previousHostCpuTicks != null) {
            long totalDelta = currentTicks.total() - _previousHostCpuTicks.total();
            long idleDelta = currentTicks.idle() - _previousHostCpuTicks.idle();

            if (totalDelta > 0 && idleDelta >= 0 && idleDelta <= totalDelta) {
                cpuUsage = (double) (totalDelta - idleDelta) / totalDelta;
            }
        }
        // ... расчёт памяти и формирование ответа
    }

    private CpuTicks readHostCpuTicks() {
        Path statFile = HOST_PROC.resolve("stat");
        // Чтение /host/root/proc/stat и парсинг строки "cpu ..."
        // Возвращает null при ошибке — триггер для fallback на OSHI
    }
}

Алгоритм расчёта загрузки CPU основан на сравнении тиков процессора между двумя последовательными чтениями файла /proc/stat. Формула:

CPU_{usage} = \frac{\Delta_{total} - \Delta_{idle}}{\Delta_{total}}

где \Delta_{total} — разница суммарных тиков всех состояний процессора, а \Delta_{idle} — разница тиков в состоянии idle.

4.4 Сбор информации о процессах

Сервис SystemProcessesService парсит /proc/[pid]/stat для каждого процесса, вычисляя загрузку CPU на основе разницы тиков между двумя снимками:

@Service
public class SystemProcessesService {

    public synchronized List<ProcessInfoResponse> getProcesses() {
        Path procRoot = Files.isDirectory(HOST_PROC) ? HOST_PROC : PROC;

        long nowNanos = System.nanoTime();
        double elapsedSeconds =
                (nowNanos - _previousSampleNanos) / 1_000_000_000.0;

        try (var entries = Files.list(procRoot)) {
            entries.filter(path ->
                            path.getFileName().toString().matches("\\d+"))
                    .forEach(path -> {
                        int pid = Integer.parseInt(path.getFileName().toString());
                        ProcessSnapshot current = readProcess(path, pid, bootTimeSeconds);
                        // Расчёт CPU usage на основе delta тиков
                        // ...
                    });
        }
        // Fallback: getProcessesFromOshi() при недоступности /proc
    }
}

Расчёт CPU usage процесса:

CPU_{process} = \frac{\Delta_{ticks}}{CLK\_TCK \times \Delta_{seconds}} \times \frac{100}{N_{cores}}

где CLK\_TCK — количество тиков системы в секунду (обычно 100), а N_{cores} — количество логических ядер.

4.5 Детали Linux-системы

Сервис LinuxDetailsService предоставляет информацию о системе через OSHI:

@Service
public class LinuxDetailsService {

    public LinuxDetailsResponse getDetails() {
        String hostname = _operatingSystem.getNetworkParams().getHostName();
        String distribution = _operatingSystem.getFamily();
        String kernelVersion = _operatingSystem.getVersionInfo().getVersion();
        long uptimeSeconds = _operatingSystem.getSystemUptime();
        double[] loadAverages = _processor.getSystemLoadAverage(3);

        return new LinuxDetailsResponse(
                linux, hostname, distribution, kernelVersion,
                uptimeSeconds,
                safeLoad(loadAverages, 0),  // 1 мин
                safeLoad(loadAverages, 1),  // 5 мин
                safeLoad(loadAverages, 2),  // 15 мин
                _logicalProcessors
        );
    }
}

5. Реализация фронтенда

5.1 Технологический стек

Компонент Технология Версия
Фреймворк Angular 22
Язык TypeScript 5.x
Реактивность RxJS 7.x
Стили SCSS —

5.2 Сервис опроса метрик

Фронтенд использует RxJS timer для периодического опроса API с интервалом 1 секуннда:

@Injectable({ providedIn: 'root' })
export class SystemMetricsService {
  private readonly http = inject(HttpClient);
  private readonly refreshInterval = 1000;

  readonly metrics$: Observable<SystemMetrics> = timer(0, this.refreshInterval).pipe(
    switchMap(() =>
      this.http.get<SystemMetrics>('/api/system/metrics').pipe(
        catchError((error: unknown) => {
          console.error('Failed to load system metrics', error);
          return of(null);
        }),
      ),
    ),
    shareReplay({ bufferSize: 1, refCount: true }),
  );

  readonly processes$: Observable<SystemProcess[]> = timer(0, this.refreshInterval).pipe(
    switchMap(() =>
      this.http.get<SystemProcess[]>('/api/system/processes').pipe(
        catchError((error: unknown) => of([] as SystemProcess[])),
      ),
    ),
    shareReplay({ bufferSize: 1, refCount: true }),
  );
  // ... остальные потоки: linux$, cpu$, memory$, disks$, network$
}

Оператор shareReplay обеспечивает многоподписку: все компоненты, подписанные на один поток, получают одни и те же данные без дублирования HTTP-запросов.

5.3 Компонент дашборда

Компонент Dashboard инъецирует сервис метрик и предоставляет данные для шаблона:

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [AsyncPipe, DecimalPipe, JsonPipe],
  templateUrl: './dashboard.html',
  styleUrl: './dashboard.scss',
})
export class Dashboard {
  private readonly _systemMetricsService = inject(SystemMetricsService);

  readonly metrics$ = this._systemMetricsService.metrics$;
  readonly processes$ = this._systemMetricsService.processes$;
  readonly linux$ = this._systemMetricsService.linux$;
  readonly cpu$ = this._systemMetricsService.cpu$;
  readonly memory$ = this._systemMetricsService.memory$;
  readonly disks$ = this._systemMetricsService.disks$;
  readonly network$ = this._systemMetricsService.network$;

  getSortedProcesses(processes: SystemProcess[]): SystemProcess[] {
    return processes
      .filter(p => p.name.toLowerCase().includes(this.processFilter))
      .sort((a, b) => {
        switch (this.processSort) {
          case 'cpu': return a.cpuUsagePercent - b.cpuUsagePercent;
          case 'memory': return a.residentMemoryBytes - b.residentMemoryBytes;
          case 'name': return a.name.localeCompare(b.name);
          case 'pid': return a.pid - b.pid;
        }
      });
  }
}

5.4 Три режима отображения

Дашборд поддерживает три режима визуализации:

  1. Monitor — карточный интерфейс с метриками CPU, памяти, дисков и сети.
  2. htop — табличное представление процессов, стилизованное под классический htop.
  3. htop-original — имитация терминального интерфейса htop с функциональными клавишами (F3 — поиск, F4 — фильтр, F6 — сортировка, F10 — выход).
<nav class="view-switcher">
  <button (click)="setViewMode('monitor')">Monitor</button>
  <button (click)="setViewMode('htop')">htop</button>
  <button (click)="setViewMode('htop-original')">htop-original</button>
</nav>

6. Сборка и упаковка

6.1 Gradle-сборка

Процесс сборки автоматически строит фронтенд и упаковывает его в бэкенд:

tasks.register('buildFrontend', Exec) {
    dependsOn 'installFrontendDependencies'
    workingDir file('../frontend')
    commandLine 'npm', 'run', 'build', '--', '--configuration', 'production'
}

tasks.register('copyFrontend', Copy) {
    dependsOn 'buildFrontend'
    from(file('../frontend/dist/frontend/browser'))
    into(file('src/main/resources/static'))
}

tasks.named('processResources') {
    dependsOn 'copyFrontend'
}

Порядок выполнения:

  1. npm ci — установка зависимостей фронтенда;
  2. npm run build — production-сборка Angular;
  3. copyFrontend — копирование статических файлов в src/main/resources/static;
  4. bootJar — упаковка JAR с фронтендом внутри.

6.2 Docker-образ

Dockerfile минимален и использует официальный образ Eclipse Temurin:

FROM eclipse-temurin:21-jre
WORKDIR /app
COPY backend/build/libs/backend-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Образ весит около 300 МБ и не содержит инструментов разработки.


7. Развёртывание через Docker Compose

7.1 Базовая конфигурация

Файл docker-compose.yml определяет сервис с доступом к файловой системе хоста:

services:
  htop-web:
    image: htop-web:latest
    build:
      context: .
      dockerfile: Dockerfile
    container_name: htop-web
    restart: unless-stopped
    pid: host
    volumes:
      - /sys:/host/sys:ro
      - /:/host/root:ro
    ports:
      - "8088:8080"

Ключевые параметры:

Параметр Назначение
pid: host Разделение PID-пространства хоста для видимости процессов
/:/host/root:ro Чтение /proc хоста для метрик CPU, памяти и процессов
/sys:/host/sys:ro Доступ к системным данным ядра
restart: unless-stopped Автоматический перезапуск при сбое

7.2 Пошаговая инструкция

Шаг 1: Клонирование репозитория

git clone https://gitea.jambotron.com/liosha84/htop-web.git
cd htop-web

Шаг 2: Сборка приложения

cd backend
./gradlew clean build
cd ..

Gradle автоматически соберёт Angular-фронтенд и упакует его в JAR-файл бэкенда.

Шаг 3: Запуск контейнера

docker compose up -d --build

Приложение доступно по адресу http://localhost:8088.

Шаг 4: Проверка статуса

# Статус контейнера
docker compose ps

# Логи в реальном времени
docker compose logs -f htop-web

Шаг 5: Остановка

docker compose down

7.3 Продвинутое развёртывание с Caddy

Для продакшн-развёртывания рекомендуется использовать обратный прокси с автоматическим HTTPS:

services:
  htop-web:
    image: gitea.jambotron.com/liosha84/htop-web:latest
    container_name: htop-web
    restart: unless-stopped
    pid: host
    volumes:
      - /sys:/host/sys:ro
      - /:/host/root:ro
    labels:
      caddy: htop-web.example.com
      caddy.reverse_proxy: "{{upstreams 8080}}"
    networks:
      - jambotron-net

networks:
  jambotron-net:
    external: true

Caddy автоматически выдаёт TLS-сертификат через Let's Encrypt и маршрутизирует запросы на контейнер.

7.4 Развёртывание на различных платформах

Linux-сервер

На Linux-сервере контейнер получает прямой доступ к /proc и /sys хоста, что обеспечивает точные метрики:

docker compose up -d --build

Docker Desktop на Windows

Docker Desktop на Windows запускает Linux-контейнеры в виртуальной машине (WSL2 или Hyper-V). В этом случае метрики отражают состояние виртуальной машины, а не хостовой Windows-системы:

Важно: Метрики из контейнера не представляют полную картину Windows-хоста. Для мониторинга Windows рекомендуется использовать нативные инструменты или развёртывание на Linux.

Raspberry Pi и ARM-устройства

htop-web поддерживает ARM-архитектуру благодаря кроссплатформенности Java и OSHI. Сборка выполняется аналогично:

docker compose up -d --build

Образ eclipse-temurin:21-jre доступен для arm64 и arm/v7.


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

Мониторинг системы раскрывает конфиденциальную информацию. При развёртывании необходимо учитывать:

  1. Аутентификация — не размещайте htop-web в открытом доступе без защиты. Используйте обратный прокси с базовой аутентификацией или OAuth.
  2. HTTPS — всегда используйте TLS при доступе из внешних сетей.
  3. Минимальные привилегии — контейнер запускается с read-only доступом к файловой системе хоста, что снижает риски.
  4. Сетевая изоляция — ограничьте доступ к порту 8088 через firewall или Docker-сети.

9. Сравнение с аналогами

9.1 Функциональное сравнение

Функция htop-web Netdata Glances Grafana+Prometheus
Веб-интерфейс ✓ ✓ ✓ ✓
CPU-метрики ✓ ✓ ✓ ✓
Память ✓ ✓ ✓ ✓
Процессы ✓ ✓ ✓ ✗
Диски ✓ ✓ ✓ ✓
Сеть ✓ ✓ ✓ ✓
Развёртывание 1 контейнер 1 контейнер pip install 3+ компонента
Ресурсы ~100 МБ RAM ~200 МБ RAM ~50 МБ RAM ~500+ МБ RAM
История данных ✗ ✓ ✗ ✓
Алертинг ✗ ✓ ✓ ✓
Графики Прогресс-бары Графики Прогресс-бары Графики

9.2 Позиционирование

htop-web ориентирован на сценарии, где важны:

  • Простота — одна команда для запуска;
  • Минимальные ресурсы — контейнер потребляет ~100 МБ RAM;
  • Отсутствие внешних зависимостей — нет баз данных, брокеров, агентов;
  • Визуальное сходство с htop — привычный интерфейс для системных администраторов.

Для задач, требующих исторических данных, алертинга и мультихостного мониторинга, рекомендуется использовать Grafana + Prometheus или Netdata.


10. Заключение

htop-web демонстрирует, что эффективный инструмент мониторинга может быть реализован как компактное самодостаточное приложение. Ключевые достижения проекта:

  1. Единый контейнер — Angular-фронтенд и Spring Boot-бэкенд упакованы в один JAR, что упрощает развёртывание и обновление.
  2. Точные метрики хоста — монтирование /proc и /sys хоста позволяет контейнеризованному приложению получать реальные данные сервера, а не метрики виртуальной среды контейнера.
  3. Реактивная архитектура — Spring WebFlux на бэкенде и RxJS на фронтенде обеспечивают эффективную обработку данных в реальном времени.
  4. Fallback-механизм — библиотека OSHI служит запасным источником метрик при недоступности файлов хоста.
  5. Минимальные требования — приложение работает на устройствах с 512 МБ RAM, включая Raspberry Pi.

Перспективы развития включают добавление исторического хранения метрик, алертинга по пороговым значениям и поддержки WebSocket для push-обновлений вместо polling.


Список литературы

  1. Spring Boot Documentation. — https://docs.spring.io/spring-boot/docs/current/reference/html/
  2. Spring WebFlux Reactive Web. — https://docs.spring.io/spring-framework/reference/web/reactive.html
  3. Angular Documentation. — https://angular.dev/
  4. RxJS Documentation. — https://rxjs.dev/
  5. OSHI — Operating System and Hardware Information. — https://github.com/oshi/oshi
  6. Docker Documentation. — https://docs.docker.com/
  7. Linux /proc filesystem. — man 5 proc
  8. The htop source code. — https://github.com/htop-dev/htop
  9. Netdata — Distributed real-time monitoring. — https://github.com/netdata/netdata
  10. Grafana — The open observability platform. — https://github.com/grafana/grafana

11. Скачивание и демо

Живое демо

Попробовать htop-web в действии можно по адресу:

https://htop-web.jambotron.com/

Демо-инстанс развёрнут на Linux-сервере с доступом к реальным системным метрикам.

Репозиторий

Исходный код проекта доступен на Gitea:

https://gitea.jambotron.com/liosha84/htop-web

Для клонирования:

git clone https://gitea.jambotron.com/liosha84/htop-web.git
cd htop-web

Docker-образ

Готовый Docker-образ опубликован в контейнерном реестре Gitea:

gitea.jambotron.com/liosha84/htop-web:latest

Быстрый запуск одним командой:

docker run -d --name htop-web --pid host \
  -v /:/host/root:ro -v /sys:/host/sys:ro \
  -p 8088:8080 \
  gitea.jambotron.com/liosha84/htop-web:latest

Статья подготовлена на основе исходного кода проекта htop-web, версия 0.0.1-SNAPSHOT.