feat: добавлен коллектор proxvmservices для обнаружения сервисов на VM
- Создан новый коллектор proxvmservices для обнаружения и мониторинга сервисов - Поддержка PostgreSQL с Patroni (кластер, репликация, конфигурация) - Поддержка etcd кластера (члены, лидер, здоровье) - Поддержка остальных сервисов: Redis, ClickHouse, RabbitMQ, Kafka, MongoDB, Kubernetes - Добавлен в Makefile и конфигурацию агента - Обновлены групповые переменные Ansible для включения в группу proxvms - Исправлены проблемы с шаблонами Ansible (конфигурация и systemd unit) - Создана подробная документация - Протестирован на удаленных серверах через Ansible Автор: Сергей Антропов Сайт: https://devops.org.ru
This commit is contained in:
parent
89512f66bc
commit
5fa101dfff
9
Makefile
9
Makefile
@ -61,7 +61,9 @@ collectors:
|
||||
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/kubernetes ./src/collectors/kubernetes && \
|
||||
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxcluster ./src/collectors/proxcluster && \
|
||||
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxnode ./src/collectors/proxnode && \
|
||||
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxvms ./src/collectors/proxvms"; \
|
||||
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxvms ./src/collectors/proxvms && \
|
||||
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxvmsystem ./src/collectors/proxvmsystem && \
|
||||
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxvmservices ./src/collectors/proxvmservices"; \
|
||||
fi
|
||||
@# Убедимся, что скрипты исполняемые
|
||||
@chmod +x ./bin/agent/collectors/*.sh 2>/dev/null || true
|
||||
@ -89,8 +91,9 @@ collectors-linux:
|
||||
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/kubernetes ./src/collectors/kubernetes && \
|
||||
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxcluster ./src/collectors/proxcluster && \
|
||||
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxnode ./src/collectors/proxnode && \
|
||||
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxvms ./src/collectors/proxvms && \
|
||||
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxvmsystem ./src/collectors/proxvmsystem"
|
||||
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxvms ./src/collectors/proxvms && \
|
||||
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxvmsystem ./src/collectors/proxvmsystem && \
|
||||
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxvmservices ./src/collectors/proxvmservices"
|
||||
|
||||
collectors-windows:
|
||||
# Кросс-сборка коллекторов для Windows
|
||||
|
@ -127,4 +127,13 @@ collectors:
|
||||
exec: "./collectors/proxvmsystem"
|
||||
platforms: [linux]
|
||||
|
||||
proxvmservices:
|
||||
enabled: true
|
||||
type: exec
|
||||
key: proxvmservices
|
||||
interval: "300s"
|
||||
timeout: "60s"
|
||||
exec: "./collectors/proxvmservices"
|
||||
platforms: [linux]
|
||||
|
||||
|
||||
|
@ -62,6 +62,7 @@ make collectors-darwin # darwin/arm64
|
||||
- **proxnode** - информация о Proxmox ноде (ресурсы, сервисы, диски) - Linux ⭐
|
||||
- **proxvms** - информация о виртуальных машинах и контейнерах Proxmox - Linux ⭐
|
||||
- **proxvmsystem** - системные метрики с machine_uid для Proxmox VM/контейнеров - Linux ⭐
|
||||
- **proxvmservices** - обнаружение и мониторинг сервисов на VM (PostgreSQL, etcd, Redis, ClickHouse, RabbitMQ, Kafka, MongoDB, Kubernetes) - Linux ⭐
|
||||
|
||||
### Документация коллекторов
|
||||
|
||||
@ -70,6 +71,7 @@ make collectors-darwin # darwin/arm64
|
||||
- [proxnode](collectors/proxnode.md) - сбор информации о Proxmox ноде
|
||||
- [proxvms](collectors/proxvms.md) - сбор информации о виртуальных машинах и контейнерах Proxmox
|
||||
- [proxvmsystem](collectors/proxvmsystem.md) - системные метрики с machine_uid для Proxmox VM/контейнеров
|
||||
- [proxvmservices](collectors/proxvmservices.md) - обнаружение и мониторинг сервисов на VM
|
||||
- [system](collectors/system.md) - сбор системных метрик
|
||||
- [docker](collectors/docker.md) - сбор информации о Docker контейнерах
|
||||
- [hba](collectors/hba.md) - сбор информации о RAID/HBA контроллерах
|
||||
|
233
docs/collectors/proxvmservices.md
Normal file
233
docs/collectors/proxvmservices.md
Normal file
@ -0,0 +1,233 @@
|
||||
# Коллектор proxvmservices
|
||||
|
||||
## Описание
|
||||
|
||||
Коллектор `proxvmservices` предназначен для обнаружения и мониторинга сервисов на виртуальных машинах и контейнерах Proxmox. Он автоматически определяет запущенные сервисы, их конфигурацию, состояние кластеров и соединения между сервисами.
|
||||
|
||||
## Поддерживаемые сервисы
|
||||
|
||||
### Кластерные сервисы
|
||||
- **PostgreSQL** с Patroni - обнаружение кластера, репликации, конфигурации
|
||||
- **etcd** - обнаружение кластера, членов, лидера, здоровья
|
||||
- **Kubernetes** - обнаружение кластера, версии, портов
|
||||
|
||||
### Автономные сервисы
|
||||
- **Redis** - версия, порты, конфигурация
|
||||
- **ClickHouse** - версия, порты, конфигурация
|
||||
- **RabbitMQ** - версия, порты, конфигурация
|
||||
- **Kafka** - порты, конфигурация
|
||||
- **MongoDB** - версия, порты, конфигурация
|
||||
|
||||
## Методы обнаружения
|
||||
|
||||
### PostgreSQL с Patroni
|
||||
1. **Процессы**: проверка `postgres` и `patroni`
|
||||
2. **Порты**: 5432 (PostgreSQL), 8008 (Patroni REST API)
|
||||
3. **Версия**: через `psql --version` или `postgres --version`
|
||||
4. **Конфигурация**: парсинг файлов `/etc/patroni/patroni.yml`, `/etc/patroni.yml`
|
||||
5. **Кластер**: команда `patronictl list` для получения информации о членах кластера
|
||||
6. **Репликация**: SQL-запрос `SELECT client_addr, state FROM pg_stat_replication`
|
||||
|
||||
### etcd
|
||||
1. **Процессы**: проверка `etcd`
|
||||
2. **Порты**: 2379 (client), 2380 (peer)
|
||||
3. **Версия**: через `etcdctl version`
|
||||
4. **Конфигурация**: парсинг файлов `/etc/etcd/etcd.conf`, systemd unit
|
||||
5. **Кластер**: команды `etcdctl member list`, `etcdctl endpoint status`, `etcdctl endpoint health`
|
||||
|
||||
### Kubernetes
|
||||
1. **Процессы**: проверка `kubelet`, `kube-apiserver`
|
||||
2. **Порты**: 6443 (API server), 10250 (kubelet)
|
||||
3. **Версия**: через `kubectl version --client --short`
|
||||
|
||||
### Остальные сервисы
|
||||
- **Redis**: процесс `redis-server`, порт 6379, версия через `redis-cli --version`
|
||||
- **ClickHouse**: процесс `clickhouse-server`, порты 8123, 9000, версия через `clickhouse-client --version`
|
||||
- **RabbitMQ**: процесс `rabbitmq-server`, порты 5672, 15672, версия через `rabbitmqctl version`
|
||||
- **Kafka**: процесс `kafka.Kafka`, порт 9092
|
||||
- **MongoDB**: процесс `mongod`, порт 27017, версия через `mongosh --version`
|
||||
|
||||
## Структура выходных данных
|
||||
|
||||
```json
|
||||
{
|
||||
"collector_name": "proxvmservices",
|
||||
"execution_time_ms": 280,
|
||||
"execution_time_seconds": 0.280283673,
|
||||
"machine_uid": "1581318a2bb03141",
|
||||
"services": [
|
||||
{
|
||||
"name": "postgresql",
|
||||
"type": "cluster",
|
||||
"status": "running",
|
||||
"version": "14.9",
|
||||
"ports": [5432, 8008],
|
||||
"config": {
|
||||
"config_file": "/etc/patroni/patroni.yml",
|
||||
"scope": "postgresql_cluster",
|
||||
"namespace": "/patroni"
|
||||
},
|
||||
"cluster": {
|
||||
"name": "postgresql_cluster",
|
||||
"state": "healthy",
|
||||
"role": "leader",
|
||||
"members": [
|
||||
{
|
||||
"name": "postgresql-1",
|
||||
"host": "10.14.246.75",
|
||||
"port": 5432,
|
||||
"state": "running",
|
||||
"role": "leader",
|
||||
"lag": 0
|
||||
}
|
||||
],
|
||||
"etcd_endpoint": "10.14.246.77:2379",
|
||||
"config": {
|
||||
"scope": "postgresql_cluster",
|
||||
"namespace": "/patroni"
|
||||
}
|
||||
},
|
||||
"connections": [
|
||||
{
|
||||
"type": "replication",
|
||||
"target": "10.14.246.76",
|
||||
"status": "streaming"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Поля данных
|
||||
|
||||
### ServiceInfo
|
||||
- `name` - имя сервиса (postgresql, etcd, redis, etc.)
|
||||
- `type` - тип сервиса ("standalone" или "cluster")
|
||||
- `status` - статус сервиса ("running", "stopped", "unknown")
|
||||
- `version` - версия сервиса
|
||||
- `ports` - массив портов, на которых слушает сервис
|
||||
- `config` - конфигурация сервиса (файлы, параметры)
|
||||
- `cluster` - информация о кластере (для кластерных сервисов)
|
||||
- `connections` - информация о соединениях (репликация, etc.)
|
||||
|
||||
### PatroniClusterInfo (для PostgreSQL)
|
||||
- `name` - имя кластера
|
||||
- `state` - состояние кластера ("healthy", "degraded")
|
||||
- `role` - роль текущего узла ("leader", "replica", "unknown")
|
||||
- `members` - массив членов кластера
|
||||
- `etcd_endpoint` - endpoint etcd для Patroni
|
||||
- `config` - конфигурация Patroni
|
||||
|
||||
### PatroniMember
|
||||
- `name` - имя члена кластера
|
||||
- `host` - IP-адрес
|
||||
- `port` - порт
|
||||
- `state` - состояние ("running", "stopped")
|
||||
- `role` - роль ("leader", "replica")
|
||||
- `lag` - задержка репликации в байтах
|
||||
|
||||
### EtcdClusterInfo (для etcd)
|
||||
- `name` - имя кластера
|
||||
- `version` - версия etcd
|
||||
- `members` - массив членов кластера
|
||||
- `leader` - ID лидера
|
||||
- `health` - здоровье кластера ("healthy", "unhealthy")
|
||||
- `cluster_size` - размер кластера
|
||||
|
||||
### EtcdMember
|
||||
- `id` - ID члена
|
||||
- `name` - имя члена
|
||||
- `peer_urls` - URL для peer-соединений
|
||||
- `client_urls` - URL для client-соединений
|
||||
- `is_leader` - является ли лидером
|
||||
- `status` - статус члена
|
||||
|
||||
### ConnectionInfo
|
||||
- `type` - тип соединения ("replication", etc.)
|
||||
- `target` - целевой хост
|
||||
- `status` - статус соединения
|
||||
|
||||
## Конфигурация
|
||||
|
||||
```yaml
|
||||
proxvmservices:
|
||||
enabled: true
|
||||
type: exec
|
||||
key: proxvmservices
|
||||
interval: "300s"
|
||||
timeout: "60s"
|
||||
exec: "./collectors/proxvmservices"
|
||||
platforms: [linux]
|
||||
```
|
||||
|
||||
## Требования
|
||||
|
||||
### Системные зависимости
|
||||
- `pgrep` - для проверки процессов
|
||||
- `ss` - для проверки портов
|
||||
- `psql` или `postgres` - для PostgreSQL
|
||||
- `patronictl` - для Patroni
|
||||
- `etcdctl` - для etcd
|
||||
- `kubectl` - для Kubernetes
|
||||
- `redis-cli` - для Redis
|
||||
- `clickhouse-client` - для ClickHouse
|
||||
- `rabbitmqctl` - для RabbitMQ
|
||||
- `mongosh` - для MongoDB
|
||||
|
||||
### Права доступа
|
||||
- Чтение конфигурационных файлов сервисов
|
||||
- Выполнение команд управления сервисами
|
||||
- Доступ к портам для проверки состояния
|
||||
|
||||
## Примеры использования
|
||||
|
||||
### Обнаружение PostgreSQL кластера
|
||||
```bash
|
||||
# Проверка процессов
|
||||
pgrep -f postgres
|
||||
pgrep -f patroni
|
||||
|
||||
# Проверка портов
|
||||
ss -tln sport = :5432
|
||||
ss -tln sport = :8008
|
||||
|
||||
# Информация о кластере
|
||||
patronictl list
|
||||
patronictl show-config
|
||||
|
||||
# Репликация
|
||||
psql -t -c "SELECT client_addr, state FROM pg_stat_replication;"
|
||||
```
|
||||
|
||||
### Обнаружение etcd кластера
|
||||
```bash
|
||||
# Проверка процессов
|
||||
pgrep -f etcd
|
||||
|
||||
# Проверка портов
|
||||
ss -tln sport = :2379
|
||||
ss -tln sport = :2380
|
||||
|
||||
# Информация о кластере
|
||||
etcdctl member list
|
||||
etcdctl endpoint status --write-out=json
|
||||
etcdctl endpoint health
|
||||
```
|
||||
|
||||
## Ограничения
|
||||
|
||||
1. **Версии сервисов**: некоторые команды могут не работать на старых версиях
|
||||
2. **Конфигурационные файлы**: парсинг ограничен стандартными форматами
|
||||
3. **Права доступа**: требует sudo для доступа к некоторым командам
|
||||
4. **Сетевые соединения**: не анализирует содержимое трафика
|
||||
5. **Кластерное состояние**: может не отражать реальное состояние при проблемах с сетью
|
||||
|
||||
## Автор
|
||||
|
||||
**Сергей Антропов**
|
||||
Сайт: https://devops.org.ru
|
||||
|
||||
## Лицензия
|
||||
|
||||
Проект распространяется под лицензией MIT.
|
@ -24,10 +24,9 @@
|
||||
- name: Generate config.yaml from template
|
||||
ansible.builtin.template:
|
||||
src: ../templates/config.yaml.j2
|
||||
dest: "{{ tmp_dir }}/config.yaml"
|
||||
dest: "/tmp/sensusagent_config_{{ inventory_hostname }}.yaml"
|
||||
mode: '0644'
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
|
||||
- name: Copy collectors directory via scp -r to tmp (from controller)
|
||||
ansible.builtin.command: >
|
||||
@ -36,13 +35,19 @@
|
||||
{{ local_bin_dir }}/collectors {{ ansible_user }}@{{ ansible_host }}:{{ tmp_dir }}/
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Copy config.yaml to remote server
|
||||
ansible.builtin.command: >
|
||||
scp -B -i {{ ansible_ssh_private_key_file | default('~/.ssh/id_rsa') }}
|
||||
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
|
||||
/tmp/sensusagent_config_{{ inventory_hostname }}.yaml {{ ansible_user }}@{{ ansible_host }}:{{ tmp_dir }}/config.yaml
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Move files into {{ remote_dir }} with root and fix permissions
|
||||
ansible.builtin.raw: |
|
||||
cp -f {{ tmp_dir }}/agent {{ remote_dir }}/agent && chmod 0755 {{ remote_dir }}/agent
|
||||
cp -f {{ tmp_dir }}/config.yaml {{ remote_dir }}/config.yaml && chmod 0644 {{ remote_dir }}/config.yaml
|
||||
rm -rf {{ remote_dir }}/collectors && mkdir -p {{ remote_dir }}/collectors && cp -r {{ tmp_dir }}/collectors/* {{ remote_dir }}/collectors/ || true
|
||||
chmod -R 0755 {{ remote_dir }}/collectors 2>/dev/null || true
|
||||
rm -rf {{ tmp_dir }}
|
||||
|
||||
- name: Optional deps (Debian/Ubuntu) — ignore errors
|
||||
ansible.builtin.raw: |
|
||||
@ -64,10 +69,16 @@
|
||||
- name: Generate systemd unit from template
|
||||
ansible.builtin.template:
|
||||
src: ../templates/sensusagent.service.j2
|
||||
dest: "{{ tmp_dir }}/sensusagent.service"
|
||||
dest: "/tmp/sensusagent_{{ inventory_hostname }}.service"
|
||||
mode: '0644'
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
|
||||
- name: Copy systemd unit to remote server
|
||||
ansible.builtin.command: >
|
||||
scp -B -i {{ ansible_ssh_private_key_file | default('~/.ssh/id_rsa') }}
|
||||
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
|
||||
/tmp/sensusagent_{{ inventory_hostname }}.service {{ ansible_user }}@{{ ansible_host }}:{{ tmp_dir }}/sensusagent.service
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Install/refresh systemd unit
|
||||
ansible.builtin.raw: |
|
||||
@ -77,4 +88,19 @@
|
||||
- name: Enable and start service
|
||||
ansible.builtin.raw: "systemctl enable --now sensusagent"
|
||||
|
||||
- name: Clean up temp directory
|
||||
ansible.builtin.raw: "rm -rf {{ tmp_dir }}"
|
||||
|
||||
- name: Clean up local temp config files
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/sensusagent_config_{{ inventory_hostname }}.yaml"
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Clean up local temp service files
|
||||
ansible.builtin.file:
|
||||
path: "/tmp/sensusagent_{{ inventory_hostname }}.service"
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
# Список коллекторов, которые должны быть включены для группы proxvms
|
||||
collectors_enabled:
|
||||
- proxvmsystem
|
||||
- proxvmservices
|
||||
- uptime
|
||||
|
||||
# Дополнительные настройки для коллекторов (опционально)
|
||||
@ -12,10 +13,13 @@ collectors_config:
|
||||
proxvmsystem:
|
||||
interval: "300s"
|
||||
timeout: "60s"
|
||||
proxvmservices:
|
||||
interval: "300s"
|
||||
timeout: "60s"
|
||||
uptime:
|
||||
interval: "60s"
|
||||
timeout: "5s"
|
||||
|
||||
# Настройки systemd сервиса для VM/контейнеров
|
||||
# Можно переопределить глобальные настройки
|
||||
agent_mode: kafka # VM/контейнеры могут использовать stdout вместо kafka
|
||||
agent_mode: stdout # VM/контейнеры используют stdout вместо kafka
|
||||
|
@ -1,21 +1,26 @@
|
||||
# Конфигурация SensusAgent
|
||||
# Автоматически сгенерировано на основе групповых переменных
|
||||
# Автор: Сергей Антропов
|
||||
# Сайт: https://devops.org.ru
|
||||
# Автор: Сергей Антропов, сайт: https://devops.org.ru
|
||||
# Общая конфигурация агента SensusAgent
|
||||
|
||||
# Настройки агента
|
||||
agent:
|
||||
log_level: "{{ agent_log_level | default('info') }}"
|
||||
kafka:
|
||||
brokers: "{{ kafka_brokers | default('localhost:9092') }}"
|
||||
topic: "{{ kafka_topic | default('sensus-metrics') }}"
|
||||
ssl:
|
||||
enabled: {{ kafka_ssl_enabled | default(false) | lower }}
|
||||
ca_cert: "{{ kafka_ssl_ca_cert | default('') }}"
|
||||
client_cert: "{{ kafka_ssl_client_cert | default('') }}"
|
||||
client_key: "{{ kafka_ssl_client_key | default('') }}"
|
||||
mode: {{ agent_mode | default('kafka') }} # stdout | kafka | auto
|
||||
log_level: {{ agent_log_level | default('info') }}
|
||||
|
||||
kafka:
|
||||
enabled: true
|
||||
brokers: ["{{ kafka_brokers | default('localhost:9092') }}"]
|
||||
topic: "{{ kafka_topic | default('sensus-metrics') }}"
|
||||
client_id: "sensusagent"
|
||||
enable_tls: {{ kafka_ssl_enabled | default(false) | lower }}
|
||||
timeout: "5s"
|
||||
# SSL настройки для Kafka
|
||||
ssl_enabled: {{ kafka_ssl_enabled | default(false) | lower }}
|
||||
ssl_keystore_location: "{{ kafka_ssl_keystore_location | default('') }}"
|
||||
ssl_keystore_password: "{{ kafka_ssl_keystore_password | default('') }}"
|
||||
ssl_key_password: "{{ kafka_ssl_key_password | default('') }}"
|
||||
ssl_truststore_location: "{{ kafka_ssl_truststore_location | default('') }}"
|
||||
ssl_truststore_password: "{{ kafka_ssl_truststore_password | default('') }}"
|
||||
ssl_client_auth: "{{ kafka_ssl_client_auth | default('none') }}" # none, required, requested
|
||||
ssl_endpoint_identification_algorithm: "{{ kafka_ssl_endpoint_identification_algorithm | default('https') }}" # https, none
|
||||
|
||||
# Коллекторы
|
||||
collectors:
|
||||
{% for collector_name in collectors_enabled %}
|
||||
{{ collector_name }}:
|
||||
|
45
src/collectors/proxvmservices/main.go
Normal file
45
src/collectors/proxvmservices/main.go
Normal file
@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
startTime := time.Now()
|
||||
|
||||
timeout := parseDurationOr("COLLECTOR_TIMEOUT", 30*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
data, err := collectProxVMServices(ctx)
|
||||
if err != nil || data == nil {
|
||||
fmt.Println("{}")
|
||||
return
|
||||
}
|
||||
|
||||
executionTime := time.Since(startTime)
|
||||
|
||||
data["execution_time_ms"] = executionTime.Milliseconds()
|
||||
data["execution_time_seconds"] = executionTime.Seconds()
|
||||
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetEscapeHTML(false)
|
||||
_ = enc.Encode(data)
|
||||
}
|
||||
|
||||
func parseDurationOr(env string, def time.Duration) time.Duration {
|
||||
v := strings.TrimSpace(os.Getenv(env))
|
||||
if v == "" {
|
||||
return def
|
||||
}
|
||||
d, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return d
|
||||
}
|
798
src/collectors/proxvmservices/proxvmservices_linux.go
Normal file
798
src/collectors/proxvmservices/proxvmservices_linux.go
Normal file
@ -0,0 +1,798 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ServiceInfo представляет информацию о сервисе
|
||||
type ServiceInfo struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"` // standalone/cluster
|
||||
Status string `json:"status"` // running/stopped/unknown
|
||||
Version string `json:"version"`
|
||||
Ports []int `json:"ports"`
|
||||
Config map[string]any `json:"config"`
|
||||
Cluster interface{} `json:"cluster,omitempty"`
|
||||
Connections []ConnectionInfo `json:"connections,omitempty"`
|
||||
}
|
||||
|
||||
// ConnectionInfo представляет информацию о соединениях
|
||||
type ConnectionInfo struct {
|
||||
Type string `json:"type"`
|
||||
Target string `json:"target"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// PatroniClusterInfo представляет информацию о Patroni кластере
|
||||
type PatroniClusterInfo struct {
|
||||
Name string `json:"name"`
|
||||
State string `json:"state"` // master/replica/unknown
|
||||
Role string `json:"role"` // leader/replica
|
||||
Members []PatroniMember `json:"members"`
|
||||
EtcdEndpoint string `json:"etcd_endpoint"`
|
||||
Config map[string]any `json:"config"`
|
||||
}
|
||||
|
||||
// PatroniMember представляет информацию о члене Patroni кластера
|
||||
type PatroniMember struct {
|
||||
Name string `json:"name"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
State string `json:"state"`
|
||||
Role string `json:"role"`
|
||||
Lag int `json:"lag"` // replication lag в байтах
|
||||
}
|
||||
|
||||
// EtcdClusterInfo представляет информацию о etcd кластере
|
||||
type EtcdClusterInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Members []EtcdMember `json:"members"`
|
||||
Leader string `json:"leader"`
|
||||
Health string `json:"health"` // healthy/unhealthy
|
||||
ClusterSize int `json:"cluster_size"`
|
||||
}
|
||||
|
||||
// EtcdMember представляет информацию о члене etcd кластера
|
||||
type EtcdMember struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
PeerURLs []string `json:"peer_urls"`
|
||||
ClientURLs []string `json:"client_urls"`
|
||||
IsLeader bool `json:"is_leader"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// collectProxVMServices собирает информацию о сервисах на VM
|
||||
func collectProxVMServices(ctx context.Context) (map[string]any, error) {
|
||||
result := map[string]any{
|
||||
"collector_name": "proxvmservices",
|
||||
}
|
||||
|
||||
// Получаем machine_uid для текущей машины
|
||||
machineUID := getMachineIDFromHost()
|
||||
if machineUID != "" {
|
||||
result["machine_uid"] = machineUID
|
||||
}
|
||||
|
||||
// Обнаруживаем сервисы
|
||||
services := []ServiceInfo{}
|
||||
|
||||
// Проверяем каждый тип сервиса
|
||||
if pg := detectPostgreSQL(); pg != nil {
|
||||
services = append(services, *pg)
|
||||
}
|
||||
if etcd := detectEtcd(); etcd != nil {
|
||||
services = append(services, *etcd)
|
||||
}
|
||||
if redis := detectRedis(); redis != nil {
|
||||
services = append(services, *redis)
|
||||
}
|
||||
if clickhouse := detectClickHouse(); clickhouse != nil {
|
||||
services = append(services, *clickhouse)
|
||||
}
|
||||
if rabbitmq := detectRabbitMQ(); rabbitmq != nil {
|
||||
services = append(services, *rabbitmq)
|
||||
}
|
||||
if kafka := detectKafka(); kafka != nil {
|
||||
services = append(services, *kafka)
|
||||
}
|
||||
if mongodb := detectMongoDB(); mongodb != nil {
|
||||
services = append(services, *mongodb)
|
||||
}
|
||||
if k8s := detectKubernetes(); k8s != nil {
|
||||
services = append(services, *k8s)
|
||||
}
|
||||
|
||||
result["services"] = services
|
||||
|
||||
if len(result) == 1 && machineUID != "" { // Только collector_name и machine_uid
|
||||
return nil, errors.New("no services detected besides machine_uid")
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil, errors.New("no data")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// getMachineIDFromHost пытается получить machine-id из /etc/machine-id или /var/lib/dbus/machine-id
|
||||
// и возвращает его SHA256 хэш.
|
||||
func getMachineIDFromHost() string {
|
||||
paths := []string{
|
||||
"/etc/machine-id",
|
||||
"/var/lib/dbus/machine-id",
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
data, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
machineID := strings.TrimSpace(string(data))
|
||||
if machineID != "" {
|
||||
hash := sha256.Sum256([]byte(machineID))
|
||||
return hex.EncodeToString(hash[:])[:16]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// isProcessRunning проверяет, запущен ли процесс
|
||||
func isProcessRunning(processName string) bool {
|
||||
cmd := exec.Command("pgrep", "-f", processName)
|
||||
err := cmd.Run()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// getListeningPorts возвращает список портов, на которых слушает процесс
|
||||
func getListeningPorts(ports ...int) []int {
|
||||
var listeningPorts []int
|
||||
|
||||
for _, port := range ports {
|
||||
cmd := exec.Command("ss", "-tln", fmt.Sprintf("sport = :%d", port))
|
||||
output, err := cmd.Output()
|
||||
if err == nil && len(output) > 0 {
|
||||
listeningPorts = append(listeningPorts, port)
|
||||
}
|
||||
}
|
||||
|
||||
return listeningPorts
|
||||
}
|
||||
|
||||
// runCommand выполняет команду и возвращает вывод
|
||||
func runCommand(command string, args ...string) (string, error) {
|
||||
cmd := exec.Command(command, args...)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
}
|
||||
|
||||
// detectPostgreSQL обнаруживает PostgreSQL с Patroni
|
||||
func detectPostgreSQL() *ServiceInfo {
|
||||
// Проверяем процессы
|
||||
if !isProcessRunning("postgres") && !isProcessRunning("patroni") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Проверяем порты
|
||||
ports := getListeningPorts(5432, 8008)
|
||||
|
||||
// Получаем версию
|
||||
version := getPostgreSQLVersion()
|
||||
|
||||
// Читаем конфиг Patroni
|
||||
config := parsePatroniConfig()
|
||||
|
||||
// Проверяем кластер через Patroni
|
||||
cluster := getPatroniCluster()
|
||||
|
||||
// Получаем информацию о репликации
|
||||
connections := getPostgreSQLConnections()
|
||||
|
||||
return &ServiceInfo{
|
||||
Name: "postgresql",
|
||||
Type: determineServiceType(cluster),
|
||||
Status: "running",
|
||||
Version: version,
|
||||
Ports: ports,
|
||||
Config: config,
|
||||
Cluster: cluster,
|
||||
Connections: connections,
|
||||
}
|
||||
}
|
||||
|
||||
// getPostgreSQLVersion получает версию PostgreSQL
|
||||
func getPostgreSQLVersion() string {
|
||||
version, err := runCommand("psql", "--version")
|
||||
if err != nil {
|
||||
// Пробуем через postgres
|
||||
version, err = runCommand("postgres", "--version")
|
||||
if err != nil {
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Извлекаем версию из строки типа "psql (PostgreSQL) 14.9"
|
||||
re := regexp.MustCompile(`(\d+\.\d+)`)
|
||||
matches := re.FindStringSubmatch(version)
|
||||
if len(matches) > 1 {
|
||||
return matches[1]
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// parsePatroniConfig парсит конфигурацию Patroni
|
||||
func parsePatroniConfig() map[string]any {
|
||||
config := make(map[string]any)
|
||||
|
||||
// Пробуем найти конфиг Patroni
|
||||
configPaths := []string{
|
||||
"/etc/patroni/patroni.yml",
|
||||
"/etc/patroni.yml",
|
||||
"/opt/patroni/patroni.yml",
|
||||
}
|
||||
|
||||
for _, path := range configPaths {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
data, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
// Простой парсинг YAML (можно улучшить)
|
||||
content := string(data)
|
||||
config["config_file"] = path
|
||||
|
||||
// Извлекаем основные параметры
|
||||
if strings.Contains(content, "scope:") {
|
||||
re := regexp.MustCompile(`scope:\s*([^\s\n]+)`)
|
||||
if matches := re.FindStringSubmatch(content); len(matches) > 1 {
|
||||
config["scope"] = matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(content, "namespace:") {
|
||||
re := regexp.MustCompile(`namespace:\s*([^\s\n]+)`)
|
||||
if matches := re.FindStringSubmatch(content); len(matches) > 1 {
|
||||
config["namespace"] = matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// getPatroniCluster получает информацию о Patroni кластере
|
||||
func getPatroniCluster() *PatroniClusterInfo {
|
||||
// patronictl list
|
||||
output, err := runCommand("patronictl", "list")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
members := parsePatronictlOutput(output)
|
||||
if len(members) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Получаем конфигурацию
|
||||
configOutput, err := runCommand("patronictl", "show-config")
|
||||
config := make(map[string]any)
|
||||
if err == nil {
|
||||
config = parsePatroniConfigFromOutput(configOutput)
|
||||
}
|
||||
|
||||
return &PatroniClusterInfo{
|
||||
Name: extractClusterNameFromConfig(config),
|
||||
State: determineClusterState(members),
|
||||
Role: determineNodeRole(members),
|
||||
Members: members,
|
||||
EtcdEndpoint: extractEtcdEndpoint(config),
|
||||
Config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// parsePatronictlOutput парсит вывод команды patronictl list
|
||||
func parsePatronictlOutput(output string) []PatroniMember {
|
||||
var members []PatroniMember
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "Cluster:") || strings.HasPrefix(line, "Member") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Парсим строку типа: "postgresql-1 | 10.14.246.75:5432 | running | leader | 0"
|
||||
parts := strings.Split(line, "|")
|
||||
if len(parts) >= 4 {
|
||||
name := strings.TrimSpace(parts[0])
|
||||
hostPort := strings.TrimSpace(parts[1])
|
||||
state := strings.TrimSpace(parts[2])
|
||||
role := strings.TrimSpace(parts[3])
|
||||
|
||||
// Парсим host:port
|
||||
hostPortParts := strings.Split(hostPort, ":")
|
||||
host := hostPortParts[0]
|
||||
port := 5432
|
||||
if len(hostPortParts) > 1 {
|
||||
if p, err := strconv.Atoi(hostPortParts[1]); err == nil {
|
||||
port = p
|
||||
}
|
||||
}
|
||||
|
||||
// Получаем lag (если есть)
|
||||
lag := 0
|
||||
if len(parts) > 4 {
|
||||
if l, err := strconv.Atoi(strings.TrimSpace(parts[4])); err == nil {
|
||||
lag = l
|
||||
}
|
||||
}
|
||||
|
||||
members = append(members, PatroniMember{
|
||||
Name: name,
|
||||
Host: host,
|
||||
Port: port,
|
||||
State: state,
|
||||
Role: role,
|
||||
Lag: lag,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return members
|
||||
}
|
||||
|
||||
// parsePatroniConfigFromOutput парсит конфигурацию из вывода patronictl show-config
|
||||
func parsePatroniConfigFromOutput(output string) map[string]any {
|
||||
config := make(map[string]any)
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.Contains(line, ":") {
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
config[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// extractClusterNameFromConfig извлекает имя кластера из конфигурации
|
||||
func extractClusterNameFromConfig(config map[string]any) string {
|
||||
if scope, ok := config["scope"].(string); ok {
|
||||
return scope
|
||||
}
|
||||
return "postgresql_cluster"
|
||||
}
|
||||
|
||||
// determineClusterState определяет состояние кластера
|
||||
func determineClusterState(members []PatroniMember) string {
|
||||
hasLeader := false
|
||||
hasRunning := false
|
||||
|
||||
for _, member := range members {
|
||||
if member.Role == "leader" {
|
||||
hasLeader = true
|
||||
}
|
||||
if member.State == "running" {
|
||||
hasRunning = true
|
||||
}
|
||||
}
|
||||
|
||||
if hasLeader && hasRunning {
|
||||
return "healthy"
|
||||
}
|
||||
return "degraded"
|
||||
}
|
||||
|
||||
// determineNodeRole определяет роль текущего узла
|
||||
func determineNodeRole(members []PatroniMember) string {
|
||||
// Определяем роль текущего узла по hostname
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
for _, member := range members {
|
||||
if strings.Contains(member.Name, hostname) || strings.Contains(member.Host, hostname) {
|
||||
return member.Role
|
||||
}
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// extractEtcdEndpoint извлекает endpoint etcd из конфигурации
|
||||
func extractEtcdEndpoint(config map[string]any) string {
|
||||
if etcd, ok := config["etcd"].(map[string]any); ok {
|
||||
if hosts, ok := etcd["hosts"].(string); ok {
|
||||
return hosts
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getPostgreSQLConnections получает информацию о соединениях PostgreSQL
|
||||
func getPostgreSQLConnections() []ConnectionInfo {
|
||||
var connections []ConnectionInfo
|
||||
|
||||
// Проверяем репликацию
|
||||
output, err := runCommand("psql", "-t", "-c", "SELECT client_addr, state FROM pg_stat_replication;")
|
||||
if err == nil && output != "" {
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" {
|
||||
parts := strings.Split(line, "|")
|
||||
if len(parts) >= 2 {
|
||||
target := strings.TrimSpace(parts[0])
|
||||
status := strings.TrimSpace(parts[1])
|
||||
connections = append(connections, ConnectionInfo{
|
||||
Type: "replication",
|
||||
Target: target,
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return connections
|
||||
}
|
||||
|
||||
// determineServiceType определяет тип сервиса
|
||||
func determineServiceType(cluster interface{}) string {
|
||||
if cluster != nil {
|
||||
return "cluster"
|
||||
}
|
||||
return "standalone"
|
||||
}
|
||||
|
||||
// detectEtcd обнаруживает etcd
|
||||
func detectEtcd() *ServiceInfo {
|
||||
// Проверяем процессы
|
||||
if !isProcessRunning("etcd") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Проверяем порты
|
||||
ports := getListeningPorts(2379, 2380)
|
||||
|
||||
// Получаем версию
|
||||
version := getEtcdVersion()
|
||||
|
||||
// Читаем конфиг
|
||||
config := parseEtcdConfig()
|
||||
|
||||
// Проверяем кластер
|
||||
cluster := getEtcdCluster()
|
||||
|
||||
return &ServiceInfo{
|
||||
Name: "etcd",
|
||||
Type: "cluster",
|
||||
Status: "running",
|
||||
Version: version,
|
||||
Ports: ports,
|
||||
Config: config,
|
||||
Cluster: cluster,
|
||||
}
|
||||
}
|
||||
|
||||
// getEtcdVersion получает версию etcd
|
||||
func getEtcdVersion() string {
|
||||
version, err := runCommand("etcdctl", "version")
|
||||
if err != nil {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// Извлекаем версию из строки типа "etcdctl version: 3.5.7"
|
||||
re := regexp.MustCompile(`(\d+\.\d+\.\d+)`)
|
||||
matches := re.FindStringSubmatch(version)
|
||||
if len(matches) > 1 {
|
||||
return matches[1]
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// parseEtcdConfig парсит конфигурацию etcd
|
||||
func parseEtcdConfig() map[string]any {
|
||||
config := make(map[string]any)
|
||||
|
||||
// Пробуем найти конфиг etcd
|
||||
configPaths := []string{
|
||||
"/etc/etcd/etcd.conf",
|
||||
"/etc/systemd/system/etcd.service",
|
||||
}
|
||||
|
||||
for _, path := range configPaths {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
data, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
content := string(data)
|
||||
config["config_file"] = path
|
||||
|
||||
// Извлекаем основные параметры
|
||||
if strings.Contains(content, "ETCD_NAME=") {
|
||||
re := regexp.MustCompile(`ETCD_NAME=([^\s\n]+)`)
|
||||
if matches := re.FindStringSubmatch(content); len(matches) > 1 {
|
||||
config["name"] = matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(content, "ETCD_DATA_DIR=") {
|
||||
re := regexp.MustCompile(`ETCD_DATA_DIR=([^\s\n]+)`)
|
||||
if matches := re.FindStringSubmatch(content); len(matches) > 1 {
|
||||
config["data_dir"] = matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// getEtcdCluster получает информацию о etcd кластере
|
||||
func getEtcdCluster() *EtcdClusterInfo {
|
||||
// etcdctl member list
|
||||
membersOutput, err := runCommand("etcdctl", "member", "list")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
members := parseEtcdMembers(membersOutput)
|
||||
if len(members) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// etcdctl endpoint status
|
||||
statusOutput, err := runCommand("etcdctl", "endpoint", "status", "--write-out=json")
|
||||
version := "unknown"
|
||||
leader := ""
|
||||
health := "unknown"
|
||||
|
||||
if err == nil {
|
||||
var status map[string]any
|
||||
if json.Unmarshal([]byte(statusOutput), &status) == nil {
|
||||
if v, ok := status["version"].(string); ok {
|
||||
version = v
|
||||
}
|
||||
if l, ok := status["leaderInfo"].(map[string]any); ok {
|
||||
if leaderID, ok := l["leader"].(string); ok {
|
||||
leader = leaderID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем здоровье
|
||||
healthOutput, err := runCommand("etcdctl", "endpoint", "health")
|
||||
if err == nil && strings.Contains(healthOutput, "healthy") {
|
||||
health = "healthy"
|
||||
} else {
|
||||
health = "unhealthy"
|
||||
}
|
||||
|
||||
return &EtcdClusterInfo{
|
||||
Name: "etcd_cluster",
|
||||
Version: version,
|
||||
Members: members,
|
||||
Leader: leader,
|
||||
Health: health,
|
||||
ClusterSize: len(members),
|
||||
}
|
||||
}
|
||||
|
||||
// parseEtcdMembers парсит вывод команды etcdctl member list
|
||||
func parseEtcdMembers(output string) []EtcdMember {
|
||||
var members []EtcdMember
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Парсим строку типа: "8e9e05c52164694d, started, etcd-1, http://10.14.246.77:2380, http://10.14.246.77:2379"
|
||||
parts := strings.Split(line, ",")
|
||||
if len(parts) >= 5 {
|
||||
id := strings.TrimSpace(parts[0])
|
||||
status := strings.TrimSpace(parts[1])
|
||||
name := strings.TrimSpace(parts[2])
|
||||
peerURL := strings.TrimSpace(parts[3])
|
||||
clientURL := strings.TrimSpace(parts[4])
|
||||
|
||||
members = append(members, EtcdMember{
|
||||
ID: id,
|
||||
Name: name,
|
||||
PeerURLs: []string{peerURL},
|
||||
ClientURLs: []string{clientURL},
|
||||
IsLeader: false, // Определим позже
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return members
|
||||
}
|
||||
|
||||
// Заглушки для остальных сервисов (пока не реализованы)
|
||||
func detectRedis() *ServiceInfo {
|
||||
if !isProcessRunning("redis-server") {
|
||||
return nil
|
||||
}
|
||||
|
||||
ports := getListeningPorts(6379)
|
||||
version := "unknown"
|
||||
|
||||
versionOutput, err := runCommand("redis-cli", "--version")
|
||||
if err == nil {
|
||||
re := regexp.MustCompile(`(\d+\.\d+\.\d+)`)
|
||||
matches := re.FindStringSubmatch(versionOutput)
|
||||
if len(matches) > 1 {
|
||||
version = matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
return &ServiceInfo{
|
||||
Name: "redis",
|
||||
Type: "standalone",
|
||||
Status: "running",
|
||||
Version: version,
|
||||
Ports: ports,
|
||||
Config: make(map[string]any),
|
||||
}
|
||||
}
|
||||
|
||||
func detectClickHouse() *ServiceInfo {
|
||||
if !isProcessRunning("clickhouse-server") {
|
||||
return nil
|
||||
}
|
||||
|
||||
ports := getListeningPorts(8123, 9000)
|
||||
version := "unknown"
|
||||
|
||||
versionOutput, err := runCommand("clickhouse-client", "--version")
|
||||
if err == nil {
|
||||
re := regexp.MustCompile(`(\d+\.\d+\.\d+)`)
|
||||
matches := re.FindStringSubmatch(versionOutput)
|
||||
if len(matches) > 1 {
|
||||
version = matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
return &ServiceInfo{
|
||||
Name: "clickhouse",
|
||||
Type: "standalone",
|
||||
Status: "running",
|
||||
Version: version,
|
||||
Ports: ports,
|
||||
Config: make(map[string]any),
|
||||
}
|
||||
}
|
||||
|
||||
func detectRabbitMQ() *ServiceInfo {
|
||||
if !isProcessRunning("rabbitmq-server") {
|
||||
return nil
|
||||
}
|
||||
|
||||
ports := getListeningPorts(5672, 15672)
|
||||
version := "unknown"
|
||||
|
||||
versionOutput, err := runCommand("rabbitmqctl", "version")
|
||||
if err == nil {
|
||||
re := regexp.MustCompile(`(\d+\.\d+\.\d+)`)
|
||||
matches := re.FindStringSubmatch(versionOutput)
|
||||
if len(matches) > 1 {
|
||||
version = matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
return &ServiceInfo{
|
||||
Name: "rabbitmq",
|
||||
Type: "standalone",
|
||||
Status: "running",
|
||||
Version: version,
|
||||
Ports: ports,
|
||||
Config: make(map[string]any),
|
||||
}
|
||||
}
|
||||
|
||||
func detectKafka() *ServiceInfo {
|
||||
if !isProcessRunning("kafka.Kafka") {
|
||||
return nil
|
||||
}
|
||||
|
||||
ports := getListeningPorts(9092)
|
||||
version := "unknown"
|
||||
|
||||
return &ServiceInfo{
|
||||
Name: "kafka",
|
||||
Type: "standalone",
|
||||
Status: "running",
|
||||
Version: version,
|
||||
Ports: ports,
|
||||
Config: make(map[string]any),
|
||||
}
|
||||
}
|
||||
|
||||
func detectMongoDB() *ServiceInfo {
|
||||
if !isProcessRunning("mongod") {
|
||||
return nil
|
||||
}
|
||||
|
||||
ports := getListeningPorts(27017)
|
||||
version := "unknown"
|
||||
|
||||
versionOutput, err := runCommand("mongosh", "--version")
|
||||
if err == nil {
|
||||
re := regexp.MustCompile(`(\d+\.\d+\.\d+)`)
|
||||
matches := re.FindStringSubmatch(versionOutput)
|
||||
if len(matches) > 1 {
|
||||
version = matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
return &ServiceInfo{
|
||||
Name: "mongodb",
|
||||
Type: "standalone",
|
||||
Status: "running",
|
||||
Version: version,
|
||||
Ports: ports,
|
||||
Config: make(map[string]any),
|
||||
}
|
||||
}
|
||||
|
||||
func detectKubernetes() *ServiceInfo {
|
||||
if !isProcessRunning("kubelet") && !isProcessRunning("kube-apiserver") {
|
||||
return nil
|
||||
}
|
||||
|
||||
ports := getListeningPorts(6443, 10250)
|
||||
version := "unknown"
|
||||
|
||||
versionOutput, err := runCommand("kubectl", "version", "--client", "--short")
|
||||
if err == nil {
|
||||
re := regexp.MustCompile(`(\d+\.\d+\.\d+)`)
|
||||
matches := re.FindStringSubmatch(versionOutput)
|
||||
if len(matches) > 1 {
|
||||
version = matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
return &ServiceInfo{
|
||||
Name: "kubernetes",
|
||||
Type: "cluster",
|
||||
Status: "running",
|
||||
Version: version,
|
||||
Ports: ports,
|
||||
Config: make(map[string]any),
|
||||
}
|
||||
}
|
14
src/collectors/proxvmservices/proxvmservices_unsupported.go
Normal file
14
src/collectors/proxvmservices/proxvmservices_unsupported.go
Normal file
@ -0,0 +1,14 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// collectProxVMServices для неподдерживаемых платформ
|
||||
func collectProxVMServices(ctx context.Context) (map[string]any, error) {
|
||||
return nil, errors.New("proxvmservices collector is not supported on this platform")
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user