вторник, 6 октября 2026 г.

Система приема Netflow и NEL на базе Clickhouse, Kafka и pmacct.

Предположим, что от маршрутизатора Cisco, который выполняет функции BRAS и NAT подают на наш сервер два потока
1) NetFlow в формате IPFIX, порт UDP 2055, это статистика (Кто, куда, сколько байт передал). Формат полей данных следующий:
{
  "ip_src": "10.10.145.224", 
  "ip_dst": "54.191.232.8",
  "port_src": 44354,
  "port_dst": 443,
  "bytes": 4934,
  "packets": 5,
  "ip_proto": 6,                        
  "timestamp_start": 1787910941512000000,
  "timestamp_end": 1787910941737000000,
  "peer_ip_src": "172.19.0.2"
}

2) NEL в формате NetFlow v9, порт UDP 2056, это события состояния сессии NAT (создалась сессия NAT, удалилась сессия NAT).
NEL может передаваться разными шаблонами. Будем использовать самый простой шаблон, который маршрутизатор отдает постоянно. Формат данных следующий:
{
  "peer_ip_src": "172.19.0.2",
  "ip_src": "10.10.148.170", 
  "port_src": 49534,
  "post_nat_ip_src": "97.221.112.206", 
  "post_nat_port_src": 58001,
  "nat_event": 1,             // 1=CREATE, 2=DELETE
  "ip_proto": "udp",
  "timestamp_start": "2026-09-07 16:36:10.415000"
}

Настроим связку которая будет принимать данные и стабильно складывать в базу Clickhouse.
Данные будут приниматься и разбираются на лету сервисом pmacct. Каждое сообщение будет преобразовываться в JSON формат и передается в агрегатор Kafka. Kafka будет выступать буфером, сглаживающим пики и объединяющим порции данных перед вставкой в Clickhouse.
Clickhouse будет постоянно читать топики Kafka и забирать новые данные в базу. Поток flow и поток nel будут очевидно хранится в разных таблицах базы, так как имеют разную структуру.

*** Установка ClickHouse
# apt-get install -y apt-transport-https ca-certificates curl gnupg
# cd /usr/src/

У меня под рукой был старый сервер с процессором Intel Xeon E5620, который не поддерживает современные версии Clickhouse.
Процессор Intel Xeon E5620 относится к семейству Westmere (2010 год). Поддерживает инструкции SSE4.2, но не поддерживает AVX/AVX2. Это важно, так как современные версии ClickHouse требуют AVX2.
Оптимальным выбором будет 22.3 LTS, которые все еще поддерживаются и работают с SSE4.2.
# wget https://packages.clickhouse.com/tgz/lts/clickhouse-common-static-22.3.19.6-amd64.tgz
# wget https://packages.clickhouse.com/tgz/lts/clickhouse-server-22.3.19.6-amd64.tgz
# wget https://packages.clickhouse.com/tgz/lts/clickhouse-client-22.3.19.6-amd64.tgz

Установка:
# tar -xzvf clickhouse-common-static-22.3.19.6-amd64.tgz
# ./clickhouse-common-static-22.3.19.6/install/doinst.sh
# tar -xzvf clickhouse-server-22.3.19.6-amd64.tgz
# ./clickhouse-server-22.3.19.6/install/doinst.sh
# tar -xzvf clickhouse-client-22.3.19.6-amd64.tgz
# ./clickhouse-client-22.3.19.6/install/doinst.sh

Настройка Clickhouse
1. Задать пользовательские настройки
# nano /etc/clickhouse-server/users.xml
1.1. Задать пароль по умолчанию для доступа к Clickhouse
Секция <users> -> <default> -> <password>
Прописываем пароль:
  <password>*******</password>
1.2. Задать ограничения по использованию памяти и ядер процессора
Секция <profiles> -> <default> 
Размер памяти определяем как 50% от памяти, которую выделим Clickhouse (в моем случае - 32 ГБ)
Для сервера с 16 ядрами и выделенными 32 ГБ RAM под ClickHouse, рекомендация max_threads = 4.
   <max_memory_usage>17179869184</max_memory_usage>
   <max_threads>4</max_threads>

2.Правим конфигурацию сервера:
# nano /etc/clickhouse-server/config.xml
2.1. Отключаем использование IPv6, раскомментируем строку (тем самым отключаем IPv6)
 <listen_host>0.0.0.0</listen_host> 
2.2. Изменяем уровень логирования – оставляем только ошибки
Так же устанавливаем размер лог-файла в 100Мбайт (вместо 1G)
 <logger>
   <level>warning</level> 
   <size>100M</size>
 <logger>

2.3. Ограничиваем сервер полкой в использовании памяти - в 32G, иначе сервер съест другие приложения
 <max_server_memory_usage>34359738368</max_server_memory_usage>
2.4. Отключаем процентное указание в использование памяти
 <max_server_memory_usage_to_ram_ratio>0</max_server_memory_usage_to_ram_ratio>
2.5. Проверить, что Clickhouse работает с директорией, которая для этого была создана в примонтированном хранилище:
 <path>/var/lib/clickhouse/</path>
2.6. Из блока <openSSL> вырезать раздел <server>, оставить только раздел клиента
  <openSSL>
    <client>
  ...
    </client>
  </openSSL>

2.7. Добавляем в конец конфигурации перед закрывающим тегом </clickhouse> директивы, определяющие протокол взаимодействия между Kafka и Clickhouse.
Это необходимо, так как версия 22.3 не совместима с новыми сервисами Kafka.
    <kafka>
        <api_version_request>false</api_version_request>
        <broker_version_fallback>2.4.1</broker_version_fallback>
    </kafka>
</clickhouse>

Создаем пользователя clickhouse и передаем ему права на директорию, в которой он должен работать
# groupadd --system clickhouse
# useradd --system --gid clickhouse --shell /usr/sbin/nologin --home-dir /var/lib/clickhouse clickhouse

Передаем clickhouse права на директорию:
# chown -R clickhouse:clickhouse /var/lib/clickhouse/
# chmod -R 750 /var/lib/clickhouse/

Создаем каталог логов
# mkdir -p /var/log/clickhouse-server
# chown clickhouse:clickhouse /var/log/clickhouse-server
# chmod 0750 /var/log/clickhouse-server

Запускаем сервис:
# service clickhouse-server start
Проверка:
# systemctl status clickhouse-server
# ss -tlnp | grep clickhouse

Подключение к серверу с паролем:
# clickhouse-client --query "SELECT 1" --password
Если что-то идет не так смотрим логи:
# journalctl -u clickhouse-server -b -n 100 --no-pager
# tail -f /var/log/clickhouse-server/clickhouse-server.err.log 

Вносим Clickhouse в автозагрузку
# systemctl enable clickhouse-server


*** Установка Kafka
# apt update
Устанавливаем Java
# apt install -y openjdk-21-jdk
# java --version
openjdk 21.0.12.1 2026-08-18

Затем скачать и распаковать Kafka. Для работы с Clickhouse 22.3 нет возможности использовать новую версию Kafka. Используем версию 3.7.1, которая совместима с Clickhouse 22.3.
# wget https://archive.apache.org/dist/kafka/3.7.1/kafka_2.12-3.7.1.tgz
# tar -xzf kafka_2.12-3.7.1.tgz  -C /opt/
# mv /opt/kafka_2.12-3.7.1 /opt/kafka

Конфигурации kafka тут: /opt/kafka/config/
Нас интересует конфигурация в режиме kraft: /opt/kafka/config/kraft/server.properties
# nano /opt/kafka/config/kraft/server.properties
Тут важно указать директорию, что бы данные были именно в ней и не забивали нужный диск.
Выбираем директорию /home/kafka, так как тут есть место.
Так же необходимо указать формат взаимодействия с Clickhouse - 3.0
Дополнительно указываем размер топиков на дисков и время жизни топиков:
 log.dirs=/home/kafka
 log.message.format.version=3.0
 # Хранить данные не более 24 часов
 log.retention.hours=24
 # Ограничение на один топик - 10G
 log.segment.bytes=10737418240
 # Оганичение размера сегмента
 log.segment.bytes=536870912  # 512MB
 # Время жизни сегмента
 log.segment.ms=3600000  # 1 час

Создаем директорию для данных, которую определили в конфигурации и директорию для логов:
# mkdir -p /home/kafka
# chmod -R 755 /home/kafka
# mkdir -p /var/log/kafka

Генерация идентификатора кластера Kafka и форматирование директории с даными Kafka
# CLUSTER_ID=$(/opt/kafka/bin/kafka-storage.sh random-uuid)
#  /opt/kafka/bin/kafka-storage.sh format \
  -t $CLUSTER_ID \
  -c /opt/kafka/config/kraft/server.properties

Создаем пользователя под которым будет работать kafka и передаем ему права на директории:
# useradd -r -s /bin/false -d /opt/kafka kafka
# chown -R kafka:kafka /opt/kafka /var/log/kafka /home/kafka

Сервис systemd
# nano /etc/systemd/system/kafka.service
Содержание файла:
[Unit]
Description=Apache Kafka Server (KRaft mode)
Documentation=http://kafka.apache.org/documentation.html
Requires=network.target remote-fs.target
After=network.target remote-fs.target
[Service]
Type=simple
User=kafka
Group=kafka
# Переменные окружения
Environment="JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64"
Environment="KAFKA_HEAP_OPTS=-Xmx1G -Xms1G"
Environment="KAFKA_OPTS=-Djava.net.preferIPv4Stack=true"
Environment="LOG_DIR=/var/log/kafka"
# Команда запуска
ExecStart=/opt/kafka/bin/kafka-server-start.sh /opt/kafka/config/kraft/server.properties
# Команда остановки (мягкая)
ExecStop=/opt/kafka/bin/kafka-server-stop.sh
# Политика перезапуска
Restart=on-failure
RestartSec=10
# Лимиты
LimitNOFILE=100000
LimitNPROC=100000
[Install]
WantedBy=multi-user.target

# systemctl daemon-reload
# systemctl start kafka

Проверка:
# systemctl status kafka
# journalctl -u kafka -f -n 100
# ss -tlnp | grep -E '9092|9093'
# systemctl enable kafka


=>>> ПРОБНЫЕ ЗАПИСИ <<<=
=>>> Тестовый топик
=>>> # /opt/kafka/bin/kafka-topics.sh --create \
       --topic test-topic \
       --bootstrap-server localhost:9092 \
       --partitions 3 \
       --replication-factor 1
=>>> Список топиков
=>>> # /opt/kafka/bin/kafka-topics.sh --list --bootstrap-server localhost:9092
=>>> Тестовое сообщение
=>>> # /opt/kafka/bin/kafka-console-producer.sh --topic test-topic --bootstrap-server localhost:9092
    Теперь вводим несколько сообщений и для выхода Ctrl+C
=>>> Информация о топике
=>>> # /opt/kafka/bin/kafka-topics.sh --describe --topic test-topic --bootstrap-server localhost:9092
=>>> Принят сообщение:
=>>> # /opt/kafka/bin/kafka-console-consumer.sh --topic test-topic --from-beginning --bootstrap-server localhost:9092
=>>> Список всех топиков:
=>>> # /opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --list
=>>> Удаление тестового топика:
=>>> # /opt/kafka/bin/kafka-topics.sh --delete --topic test-topic --bootstrap-server localhost:9092

Теперь создаем топики pmacct-flows и pmacct-nel для приема сообщений Netflow (сам поток Netflow и NEL):
# /opt/kafka/bin/kafka-topics.sh --create \
  --topic pmacct-flows \
  --bootstrap-server localhost:9092 \
  --partitions 3 \
  --replication-factor 1 
# /opt/kafka/bin/kafka-topics.sh --create \
  --topic pmacct-nel \
  --bootstrap-server localhost:9092 \
  --partitions 3 \
  --replication-factor 1

  

*** Сборка и установка pmacct с поддержкой kafka
# /usr/src/
# apt install git
# apt update && sudo apt install -y libtool automake autoconf
# apt install -y pkgconf build-essential
# apt install -y libpcap-dev
# apt install -y libcurl4-openssl-dev
# apt install -y libmariadb-dev-compat libmariadb-dev
# apt install -y libnuma-dev
# apt install -y librdkafka-dev
# apt install -y libjansson-dev
# git clone https://github.com/pmacct/pmacct.git
# cd pmacct/
# ./autogen.sh
# ./configure --enable-jansson --enable-kafka --disable-bgp-bins --disable-bmp-bins --disable-st-bins
# make -j$(nproc)
# make install
# nfacctd -V

В выводе мы должны увидеть версию программы и информацию о том, что плагин kafka подключен
 Libs:
  cdada 0.6.4
  libpcap version 1.10.5 (with TPACKET_V3)
  rdkafka 2.8.0
  jansson 2.14
 Plugins:
  ...
  kafka

Создаем директорию для логов:
# mkdir -p /var/log/pmacct
Конфиги лежат тут: /etc/pmacct/
Создаем конфиг для приема основного потока Netflow: pmacct-flows.conf
# nano /etc/pmacct/pmacct-flows.conf
 ##### Для теста с выводом в консоль:
 #daemonize: false
 #pidfile: /var/run/pmacct-flows.pid
 #logfile: /var/log/pmacct/pmacct-flows.log
 #debug: true 
 #nfacctd_port: 2055
 #nfacctd_time_new: true
 #plugins: print[flows]
 #aggregate[flows]: src_host, dst_host, src_port, dst_port, proto, timestamp_start, peer_src_ip
 #print_output[flows]: json
 #print_refresh_time[flows]: 5
 ##### Для записи в Kafka
 #daemonize: true  # Для запуска без systemd
 daemonize: false
 pidfile: /var/run/pmacct-flows.pid
 logfile: /var/log/pmacct/pmacct-flows.log
 debug: false
 nfacctd_port: 2055
 nfacctd_time_new: true
 plugins: kafka[flows]
 aggregate[flows]: src_host, dst_host, src_port, dst_port, proto, timestamp_start, peer_src_ip
 kafka_broker_host[flows]: localhost
 kafka_broker_port[flows]: 9092
 kafka_topic[flows]: pmacct-flows
 kafka_partition_key[flows]: src_host
 kafka_refresh_time[flows]: 10

[Пробный запуск /usr/local/sbin/nfacctd -f /etc/pmacct/pmacct-flows.conf ]

Создаем unit systemd для запуска через systemd
# nano /etc/systemd/system/pmacct-flows.service
 [Unit]
 Description=pmacct NetFlow Accounting Daemon
 Documentation=http://www.pmacct.net/
 After=network.target kafka.service
 Wants=kafka.service
 [Service]
 Type=simple
 User=root
 Group=root
 ExecStart=/usr/local/sbin/nfacctd -f /etc/pmacct/pmacct-flows.conf
 Restart=on-failure
 RestartSec=5
 LimitNOFILE=65536
 [Install]
 WantedBy=multi-user.target


Создаем конфиг для приема потока NEL: pmacct-nel.conf
# nano /etc/pmacct/pmacct-nel.conf
 ##### Для теста с выводом в консоль:
 #daemonize: false
 #pidfile: /var/run/pmacct-nel.pid
 #logfile: /var/log/pmacct/pmacct-nel.log
 #debug: true 
 #nfacctd_port: 2056
 #nfacctd_time_new: true
 #plugins: print[cgn]
 #aggregate[cgn]: src_host, post_nat_src_host, src_port, post_nat_src_port, proto, nat_event, timestamp_start, peer_src_ip
 #aggregate_filter[cgn]: dst_host == ""
 #print_output[cgn]: json
 #print_refresh_time[cgn]: 5
 #print_output_file[cgn]: /dev/stdout
 ##### Для записи в Kafka
 #daemonize: true
 daemonize: false
 pidfile: /var/run/pmacct-nel.pid
 logfile: /var/log/pmacct/pmacct-nel.log
 debug: false
 nfacctd_port: 2056
 nfacctd_time_new: true
 plugins: kafka[cgn]
 aggregate[cgn]: src_host, post_nat_src_host, src_port, post_nat_src_port, proto, nat_event, timestamp_start, peer_src_ip
 aggregate_filter[cgn]: dst_host == ""
 kafka_broker_host[cgn]: localhost
 kafka_broker_port[cgn]: 9092
 kafka_topic[cgn]: pmacct-nel
 kafka_partition_key[cgn]: src_host
 kafka_refresh_time[cgn]: 10

[Пробный запуск /usr/local/sbin/nfacctd -f /etc/pmacct/pmacct-nel.conf ]

Создаем unit systemd для запуска через systemd

# nano /etc/systemd/system/pmacct-nel.service
 [Unit]
 Description=pmacct NEL
 Documentation=http://www.pmacct.net/
 After=network.target kafka.service
 Wants=kafka.service
 [Service]
 Type=simple
 User=root
 Group=root
 ExecStart=/usr/local/sbin/nfacctd -f /etc/pmacct/pmacct-nel.conf
 Restart=on-failure
 RestartSec=5
 LimitNOFILE=65536
 [Install]
 WantedBy=multi-user.target

Запускаем:
[Если запускал руками, то сначала нужно убить процессы nfacctd]
# systemctl daemon-reload
# systemctl start pmacct-flows
# systemctl status pmacct-flows
# systemctl start pmacct-nel
# systemctl status pmacct-nel

Проверка того, что данные залетают в Kafka:

# ps aux | grep nfacctd
# /opt/kafka/bin/kafka-console-consumer.sh \
  --bootstrap-server localhost:9092 \
  --topic pmacct-flows \
  --from-beginning \
  --max-messages 5
# /opt/kafka/bin/kafka-console-consumer.sh \
  --bootstrap-server localhost:9092 \
  --topic pmacct-nel \
  --from-beginning \
  --max-messages 5
# systemctl enable pmacct-flows
# systemctl enable pmacct-nel



** Подключаем Kafka к Clickhouse
1 часть. Поток Netflow.
# clickhouse-client --password (нужен пароль)
1. Создаем базу данных:
CREATE DATABASE IF NOT EXISTS netflow;
2. Создание Kafka Engine таблицы для подключения к Kafka (удаление старой таблицы DROP TABLE IF EXISTS netflow.flows_kafka;)
CREATE TABLE netflow.flows_kafka
(
 `value` String
)
ENGINE = Kafka()
SETTINGS
kafka_broker_list = 'localhost:9092',
kafka_topic_list = 'pmacct-flows',
kafka_group_name = 'clickhouse-flows',
kafka_format = 'JSONAsString';

3. Создание таблицы хранения данных: (удаление старой таблицы DROP TABLE IF EXISTS netflow.flows;)
CREATE TABLE netflow.flows
(
    `timestamp_start` DateTime CODEC(Delta(4), ZSTD(1)),
    `ip_src` IPv4,
    `ip_dst` IPv4,
    `port_src` UInt16 DEFAULT 0 CODEC(ZSTD(1)),
    `port_dst` UInt16 DEFAULT 0 CODEC(ZSTD(1)),
    `proto` LowCardinality(String) DEFAULT '' CODEC(ZSTD(1)),
    `peer_ip_src` IPv4,
    `bytes` UInt64 DEFAULT 0 CODEC(ZSTD(1)),
    `packets` UInt32 DEFAULT 0 CODEC(ZSTD(1))
)
ENGINE = MergeTree
PARTITION BY toYYYYMMDD(timestamp_start)
ORDER BY (timestamp_start, ip_src, ip_dst)
TTL timestamp_start + toIntervalMonth(6)
SETTINGS index_granularity = 8192;

4. Создание Materialized View для форматирования принимаемых из Kafka данных (удаление старой таблицы DROP TABLE IF EXISTS netflow.flows_mv;)
CREATE MATERIALIZED VIEW netflow.flows_mv TO netflow.flows AS
SELECT
    toDateTime(substring(JSONExtractString(value, 'timestamp_start'), 1, 19)) AS timestamp_start,
    toIPv4(nullIf(JSONExtractString(value, 'ip_src'), '')) AS ip_src,
    toIPv4(nullIf(JSONExtractString(value, 'ip_dst'), '')) AS ip_dst,
    toUInt16(JSONExtractInt(value, 'port_src')) AS port_src,
    toUInt16(JSONExtractInt(value, 'port_dst')) AS port_dst,
    JSONExtractString(value, 'ip_proto') AS proto,
    toIPv4(nullIf(JSONExtractString(value, 'peer_ip_src'), '')) AS peer_ip_src,
    toUInt64(JSONExtractInt(value, 'bytes')) AS bytes,
    toUInt32(JSONExtractInt(value, 'packets')) AS packets
FROM netflow.flows_kafka
WHERE (JSONExtractString(value, 'ip_src') != '') AND (JSONExtractString(value, 'ip_dst') != '')

Проверка логов на предмет чего-то критичного:
# tail -n 20 /var/log/clickhouse-server/clickhouse-server.err.log | grep -i kafka
2 часть. Поток NEL
5. Создание Kafka Engine таблицы для подключения к Kafka (удаление старой таблицы DROP TABLE IF EXISTS netflow.nel_kafka;)
CREATE TABLE IF NOT EXISTS netflow.nel_kafka 
(
    `value` String
)
ENGINE = Kafka()
SETTINGS
    kafka_broker_list = 'localhost:9092',
    kafka_topic_list = 'pmacct-nel',
    kafka_group_name = 'clickhouse-nel',
    kafka_format = 'JSONAsString';

6. Создание таблицы хранения данных NEL (удаление старой таблицы DROP TABLE IF EXISTS netflow.nel;)
CREATE TABLE netflow.nel
(
    `timestamp_start` DateTime CODEC(Delta(4), ZSTD(1)),
    `ip_src` IPv4,
    `port_src` UInt16 DEFAULT 0 CODEC(ZSTD(1)),
    `proto` LowCardinality(String) DEFAULT '' CODEC(ZSTD(1)),
    `peer_ip_src` IPv4,
    `post_nat_ip_src` IPv4,
    `post_nat_port_src` UInt16 DEFAULT 0 CODEC(ZSTD(1)),
    `nat_event` UInt8 DEFAULT 0 CODEC(ZSTD(1))
)
ENGINE = MergeTree
PARTITION BY toYYYYMMDD(timestamp_start)
ORDER BY (timestamp_start, ip_src, post_nat_ip_src)
TTL timestamp_start + toIntervalMonth(6)
SETTINGS index_granularity = 8192;

7. Создание Materialized View для форматирования принимаемых из Kafka данных (удаление старой таблицы DROP TABLE IF EXISTS netflow.nel_mv;)
CREATE MATERIALIZED VIEW IF NOT EXISTS netflow.nel_mv TO netflow.nel AS
SELECT
    toDateTime(substring(JSONExtractString(value, 'timestamp_start'), 1, 19)) AS timestamp_start,
    toIPv4(nullIf(JSONExtractString(value, 'ip_src'), '')) AS ip_src,
    toUInt16(JSONExtractInt(value, 'port_src')) AS port_src,
    JSONExtractString(value, 'ip_proto') AS proto,
    toIPv4(nullIf(JSONExtractString(value, 'peer_ip_src'), '')) AS peer_ip_src,
    toIPv4(nullIf(JSONExtractString(value, 'post_nat_ip_src'), '')) AS post_nat_ip_src,
    toUInt16(JSONExtractInt(value, 'post_nat_port_src')) AS post_nat_port_src,
    toUInt8(JSONExtractInt(value, 'nat_event')) AS nat_event
FROM netflow.nel_kafka
WHERE 
    JSONExtractString(value, 'ip_src') != '' AND 
    JSONExtractString(value, 'post_nat_ip_src') != '';


*** Включаем firewall
# apt install ufw
# ufw allow ssh
# ufw allow 2055/udp
# ufw allow 2056/udp
# ufw allow from 10.0.0.0/8 to any port 8123 proto tcp
# ufw allow from 10.0.0.0/8 to any port 9000 proto tcp
# ufw enable

Проверка:
# ufw status verbose
# systemctl status ufw



*** Включение ротации логов

У сlickhouse ротацию логов уже включили через конфиг при насройке clickhouse

Логи демона pmacct храняться тут /var/log/pmacct
Используем стандартный logrotate для ротации логов
# nano /etc/logrotate.d/pmacct
Содержимое файла:
/var/log/pmacct/*.log {
    hourly
    rotate 2
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
}

Этот конфиг копирует текущий лог в архив и очищает исходный файл. pmacct продолжает писать в тот же файловый дескриптор, поэтому перезапуск сервиса не нужен.
Проверка синтаксиса:
# logrotate -d /etc/logrotate.d/pmacct
Принудительная ротация логов прямо сейчас:
# logrotate -f /etc/logrotate.d/pmacct
Ротация будет проводиться раз в сутки. Когда следующая ротация покажет команда:
# systemctl list-timers logrotate.timer

Логи kafka расположены в директории /var/log/kafka
Kafka использует log4j для логирования. Файл конфигурации логирования:  /opt/kafka/config/log4j.properties
А вот удаление старых файлов kafka делать не умеет.
Для удаления старых файлов используем команду в crontab: 
# Очищение логов kafka старше 5 суток
42 *    * * *   root  find /var/log/kafka -type f -name "*.log.20*" -mmin +7200 -delete > /dev/null 2>&1

суббота, 5 сентября 2026 г.

Установка GoFlow2 в Debian

GoFlow2 принимает данные из потоков Netflow и в стандартном случае передает на стандартный выход в консоль.

# cd /usr/src/
# wget https://github.com/netsampler/goflow2/releases/download/v2.2.6/goflow2_2.2.6_amd64.deb
# apt-get install -f -y ./goflow2_2.2.6_amd64.deb
# goflow2 --v
GoFlow2 v2.2.6 (2025-12-27T05:02:22+0000)


Проверка наличия сервиса systemd
# systemctl cat goflow2

Запуск:  
# goflow2   -listen "netflow://:2055"   -format json
# goflow2   -listen "netflow://:2056"   -format json


Удаление пакета
# dpkg --purge goflow2

четверг, 27 августа 2026 г.

Gitea. Установка и базовая настройка

Создаем директорию для контейнера gitea

# mkdir /mnt/data/docker_projects/gitea

В интерфейсе portainer переходим в окружение local.

Выбираем пункты меню Stacks --> + Add Stack. Прописываем имя в поле Name - gitea


Оставляем выбор метода задания параметров Web editor.

В поле редактора конфига вносим конфигурацию:

services:

  gitea:

    image: gitea/gitea:latest

    container_name: gitea

    restart: unless-stopped

    ports:

      - "3000:3000"

      - "2222:22"

    volumes:

      - /mnt/data/docker_projects/gitea/data:/data

    environment:

      - USER_UID=1000

      - USER_GID=1000

      - GITEA__database__DB_TYPE=postgres

      - GITEA__database__HOST=db:5432

      - GITEA__database__NAME=gitea

      - GITEA__database__USER=gitea

      - GITEA__database__PASSWD=gitea_pass

      - GITEA__server__ROOT_URL=http://gitea.example.ru/

      - GITEA__server__LOCAL_ROOT_URL=http://localhost:3000/

      - GITEA__server__SSH_PORT=2222

      - GITEA__server__SSH_DOMAIN=gitea.example.ru

      - GITEA__packages__ENABLED=true

      - GITEA__server__DOMAIN=gitea.example.ru

      - GITEA__service__DISABLE_REGISTRATION=true

      - GITEA__service__REQUIRE_SIGNIN_VIEW=true

    depends_on:

      db:

        condition: service_healthy


  db:

    image: postgres:16-alpine

    container_name: gitea-db

    restart: unless-stopped

    volumes:

      - /mnt/data/docker_projects/gitea/db:/var/lib/postgresql/data

    environment:

      - POSTGRES_USER=gitea

      - POSTGRES_PASSWORD=gitea_pass

      - POSTGRES_DB=gitea

    healthcheck:

      test: ["CMD-SHELL", "pg_isready -U gitea"]

      interval: 5s

      timeout: 5s

      retries: 5

Активируем кнопку "Deploy the stack", Будет создан стек контейнеров.


Открываем порт для работы через web-интерфейс с Gitea

# firewall-cmd --permanent --add-port=3000/tcp

# firewall-cmd --reload

# firewall-cmd --list-all


Открываем WEB-интерфейс GITEA

http://IP:3000/

Будет показан стартовый экран первичных настроек.

Важно написать название сервиса. Например, Gitea smarts.

Прокручиваем вниз и нажимает "Установить Gitea".


Затем нужно задать первого пользователя - администратора системы и нажать "Регистрация аккаунта".

 


Будет создан пользователь и откроется интерфейс gitea.

Создаем репозитарий.

Раздел Репозитарий --> + Новый репозитарий.

Задаем Название репозитория.

Устанавливаем флаг Видимость (Сделать репозиторий приватным).

Другие флажки не устанавливаем.

Нажимаем кнопку "Создать репозитарий"

Теперь на сервере разработки создаем ключ SSH по которому с сервера можно будет коннектиться к Gitea.

Проверка налияи ключа:

# ls -l ~/.ssh/ | grep pub

Показываем ключ:

# cat ~/.ssh/id_ecdsa.pub 

ecdsa-sha2-nistp256 Arx7r.....rlgehf0/XGro4B9A= root@server

И переносим его в Gitea в раздел Аватар (справа вверху) --> Настройки --> Ключи SSH / GPG Keys --> Управление ключами SSH --> Добавить ключ

Задаем имя ключа, вставляем ключ в поле и проводим его добавление.

Сбросить пароль в gitea

Если вдруг забыл пароль:

# docker exec -it -u git gitea gitea admin user change-password \

  --username korolev \

  --password <НОВЫЙ_ВРЕМЕННЫЙ_ПАРОЛЬ>

После этого когда будет выполнен вход в gitea через WEB-интерфейс с новым временным паролем, система будет запрашивать смену пароля.


x

среда, 26 августа 2026 г.

Установка zabbix в контейнер Docker. Мониторинг хоста через agent

Создаем директорию для контейнера zabbix
# mkdir /mnt/data/docker_projects/zabbix

Создаем конфигурацию PostgreSQL
# nano /mnt/data/docker_projects/zabbix/postgresql.conf
listen_addresses = '*'
port = 5432
max_connections = 100
shared_buffers = 1GB
effective_cache_size = 3GB
maintenance_work_mem = 256MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 500
random_page_cost = 1.1
effective_io_concurrency = 200
work_mem = 64MB
min_wal_size = 1GB
max_wal_size = 4GB


В интерфейсе portainer переходим в окружение local.
Выбираем пункты меню Stacks --> + Add Stack.
Прописываем имя в поле Name - zabbix/
Оставляем выбор метода задания параметров Web editor.
В поле редактора конфига вносим конфигурацию:
x-logging: &default-logging
  driver: json-file
  options:
    max-size: "50m"
    max-file: "3"
services:
  postgresql:
    image: postgres:16-alpine
    container_name: zabbix-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: zabbix
      POSTGRES_PASSWORD: SecurePassword123
      POSTGRES_DB: zabbix
    volumes:
      - /mnt/data/docker_projects/zabbix/data_pgsql:/var/lib/postgresql/data:z
      - /mnt/data/docker_projects/zabbix/postgresql.conf:/etc/postgresql/postgresql.conf
    networks:
      - zabbix-net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U zabbix -d zabbix"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    cpus: 1.5
    mem_limit: 2g
    logging: *default-logging
  zabbix-server:
    image: zabbix/zabbix-server-pgsql:ubuntu-7.0-latest
    container_name: zabbix-server
    restart: unless-stopped
    environment:
      TZ: Europe/Samara
      DB_SERVER_HOST: postgresql
      POSTGRES_USER: zabbix
      POSTGRES_PASSWORD: SecurePassword123
      POSTGRES_DB: zabbix
      ZBX_CACHESIZE: 512M
      ZBX_HISTORYCACHESIZE: 128M
      ZBX_VALUECACHESIZE: 256M
      ZBX_STARTPOLLERS: 20
      ZBX_STARTTRAPPERS: 10
      ZBX_STARTDISCOVERERS: 5
    volumes:
      - /mnt/data/docker_projects/zabbix/data_zabbix_server:/var/lib/zabbix:z
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "10051:10051"
    depends_on:
      postgresql:
        condition: service_healthy
    networks:
      - zabbix-net
    cpus: 1.5
    mem_limit: 2g
    logging: *default-logging
 
  zabbix-web:
    image: zabbix/zabbix-web-apache-pgsql:ubuntu-7.0-latest
    container_name: zabbix-web
    restart: unless-stopped
    environment:
      PHP_TZ: Europe/Samara
      ZBX_SERVER_HOST: zabbix-server
      DB_SERVER_HOST: postgresql
      POSTGRES_USER: zabbix
      POSTGRES_PASSWORD: SecurePassword123
      POSTGRES_DB: zabbix
    ports:
      - "8080:8080"
    depends_on:
      zabbix-server:
        condition: service_started
      postgresql:
        condition: service_healthy
    networks:
      - zabbix-net
    cpus: 0.5
    mem_limit: 512m
    logging: *default-logging
networks:
  zabbix-net:
    driver: bridge


Открываем порт 8080 для zabbix:
# firewall-cmd --permanent --add-port=8080/tcp && firewall-cmd --reload

Открываем WEB-интерфейс http://IP:8080/
Логин / пароль по умолчанию: Admin / zabbix
Сразу меняем пароль в разделе User settings --> Profile --> Change password // новый_пароль
Перезаходим в Zabbix Admin / новый_пароль

Zabbix агент на хостовой машине:
Добавить репозиторий Zabbix на хостовую машину и настроить агента zabbix
# rpm -Uvh https://repo.zabbix.com/zabbix/7.0/rocky/9/x86_64/zabbix-release-latest.el9.noarch.rpm
# dnf clean all
# dnf install zabbix-agent -y

Настроить конфигурацию агента, что бы он в ативном режиме отправлял данные на сервер.
# nano /etc/zabbix_agentd.conf
Главные директивы:
  ServerActive=127.0.0.1:10051
  Hostname=rossyp
  RefreshActiveChecks=60

Заводим агента Zabbix в работу:
# systemctl enable zabbix-agent
# systemctl start zabbix-agent
# systemctl status zabbix-agent


В Интерфейсе Zabbix необходимо корректно прописать host, на который будут поступать данные от агента.
В активном режиме (Active Mode) агент сам инициирует соединение. Вот так прописывается хост:
Data collection → Hosts → Create host.
Host name: точное значение параметра Hostname из конфига агента rossyp.
Templates: шаблон Linux by Zabbix agent active.
Host groups: Linux servers.

Docker. Установка. Portainer CE. Установка.

1. Установка docker


1.1. Проверяем установку docker
# docker --version
# systemctl status docker

1.2 Устанавливаем docker, если его нет
# dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

2. Меняем root директорию docker, что бы docker не забивал корень (папку по умолчанию: /var/lib/docker/)
# mkdir -p /etc/docker
# nano /etc/docker/daemon.json
Содержимое:
{
  "data-root": "/mnt/data/docker",
  "default-address-pools": [
    {
      "base": "172.30.0.0/16",
      "size": 24
    }
  ]
}
# systemctl enable --now docker
После запуска docker все его файлы будут складываться в /mnt/data/docker
IP адреса контейнеров будут браться из 172.30.0.0/16 нарезаясь по /24.

3. Создаем папку проектов docker рядом с самой директорией для docker
# mkdir -p /mnt/data/docker_projects

4. Создаем docker контейнер с Portainer
# docker volume create portainer_data
Запускаем
# docker run -d \
  --name portainer \
  --restart=unless-stopped \
  -p 9443:9443 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v portainer_data:/data \
  portainer/portainer-ce:latest
Проверяем работу
# docker ps | grep portainer

5. Открываем порт 9443 для доступ извне:
# firewall-cmd --permanent --add-port=9443/tcp
# firewall-cmd --reload
# firewall-cmd --list-all

6. Подключаемся к серверу по WEB-интерфейсу
https://10.0.1.60:9443
Браузер заругается на сертификат, делаем исключение для сайта и продолжаем. Придумываем пароль как минимум из 12 символов



В поле "Setup token" нужно ввести токен, который генерируется при старте контейнера без пользователя.
Это поле нужно посмотреть в консоле сервера и найти строчку где встречаается "setup_token=..."
Пример:
# docker logs portainer
...
2026/08/01 03:52PM INF github.com/portainer/portainer/api/cmd/portainer/main.go:256 > no administrator account configured; admin initialization and backup restore require this setup token in the X-Setup-Token header. Start with --no-setup-token to disable. | setup_token=19af87915ebf5626320257f13245744b2d3c7da57a288107fa87edeb8fb0096c
2026/08/01 03:52PM INF github.com/portainer/portainer/api/chisel/service.go:228 > generated a new Chisel private key file | private-key=/data/chisel/private-key.pem
...
Если не успеть войти в Portainer при старте за 5 минут, вход заблокируется и будет предупреждение:


Не беда, перезапускаем контейнер и у нас есть новые 5 минут для того что бы придумать пароль и ввести стартовый токен.
После успешной авторизации должны увидеть панель Portainer. Нажимаем Get Started и начинаем работать.





вторник, 26 мая 2026 г.

Запуск скрипта-сервиса на Python для непрерывной работы через systemd

Для примера далее рассматривается скрипт "/home/korolev/autocall_process.py"
Скрипт Python должен работать в бесконечном цикле примерно так:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import subprocess
from psycopg2 import sql
from datetime import datetime, time
<... функция work() и другие ...>
def main():
    while True:
        work()
        # Пауза перед следующей итерацией
        time.sleep(CHECK_INTERVAL)
if __name__ == "__main__":
    main()


После отладки, делаем сам скрипт исполняемым:
# chmod +x /home/korolev/autocall_process.py
Создаем unit системы systemd:
# nano /etc/systemd/system/autocall.service
[Unit]
Description=AutoCall Dialer Service Freeswitch
After=network.target postgresql.service freeswitch.service
Wants=postgresql.service freeswitch.service
[Service]
Type=simple
User=korolev
Group=korolev
WorkingDirectory=/home/korolev
Environment=PYTHONUNBUFFERED=1
ExecStart=/usr/bin/python3 /home/korolev/autocall_process.py
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=autocall
[Install]
WantedBy=multi-user.target


Так как скрипт работает от пользователя korolev, но должен обращаться к ПО Freeswitch, работающее под пользователем freeswitch, добавляем пользователя korolev в группу freeswitch
# usermod -aG freeswitch korolev

Запускаемся:
# systemctl daemon-reload
# systemctl start autocall.service
# systemctl enable autocall.service


Следить за логами теперь можно через системный журнал так:
# journalctl -u autocall.service -f

Аккаутинг с Cisco ASR1001X. Настройка логов аккаутинга. FreeRADIUS 3.2.8 + PostgreSQL 16. ОС: RockyLinux 9.7

Обновляем ОС:
# dnf update

Устанавливаем репозитарии EPEL и CRB:
# dnf install epel-release
# dnf config-manager --set-enabled crb

Устанавливаем PostgreSQL 16-ой версии:
# dnf install perl-IPC-Run
Подключаем репозитарий с версиями PostgreSQL
# dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm
Отключаем встроенный модуль postgresql в репозитариях по умолчанию
# dnf -qy module disable postgresql
Проверка подключения репозитариев:
# yum repolist
Видим, что репозитарий в 16-ой версией присутсвует в списке доступных.
Вводим команду установки:
# dnf install postgresql16-server
# dnf install postgresql16-devel

Иницируем базу данных:
# /usr/pgsql-16/bin/postgresql-16-setup initdb
# systemctl start postgresql-16.service
# systemctl enable postgresql-16.service


Установка пакетов, необходимых для сборки FreeRADIUS из исходных кодов
# dnf groupinstall -y "Development Tools"
# dnf install -y gcc make readline-devel openssl-devel libtool pkg-config git python3 python3-devel sqlite-devel curl wget net-snmp-devel libxml2-devel popt-devel

# dnf install libtalloc-devel
Скачиваем пакет исходных кодов FreeRADIUS
# cd /usr/src
# wget https://github.com/FreeRADIUS/freeradius-server/releases/download/release_3_2_8/freeradius-server-3.2.8.tar.gz
# tar -xf freeradius-server-3.2.8.tar.gz
# cd freeradius-server-3.2.8

Конфигурируем сборку с поддержкой PostgreSQL
# ./configure \
    --with-utils \
    --with-postgresql-prefix=/usr/pgsql-16 \
    CPPFLAGS="-I/usr/pgsql-16/include" \
    LDFLAGS="-L/usr/pgsql-16/lib" \
    PKG_CONFIG_PATH="/usr/pgsql-16/lib/pkgconfig" \
    --sysconfdir=/etc/freeradius \
    --localstatedir=/var/lib/freeradius \
    --sbindir=/usr/sbin \
    --with-user=freerad

В завешении листинга процесса конфигурирования должно отображаться:
<....>
rlm_sql ................. OK
rlm_sql_db2 ............. OK
rlm_sql_firebird ........ OK
rlm_sql_freetds ......... skipping (requires ctpublic.h libct)
rlm_sql_iodbc ........... skipping (requires libiodbc isql.h)
rlm_sql_mongo ........... OK
rlm_sql_mysql ........... skipping (requires libmysqlclient || libmysqlclient_r mysql.h)
rlm_sql_null ............ OK
rlm_sql_oracle .......... skipping (requires oci.h libclntsh libnnz(9-12))
rlm_sql_postgresql ...... OK
rlm_sql_sqlite .......... OK
rlm_sql_unixodbc ........ OK
rlm_sql_map ............. OK
rlm_sqlcounter .......... OK
rlm_sqlippool ........... OK
rlm_test ................ OK
<....>

Важно, что бы у пакета rlm_sql_postgresql был статус OK (rlm_sql_postgresql ...... OK)
Запуск сборки и установки:
# make clean
# make -j$(nproc)
# make install
# ldconfig

Далее необходимо провести первый запуск, что бы понять, что под root Freeradius запускается в принципе:
# radiusd -X
Вывод будет завершаться информацией о открытых портах:
Listening on auth address * port 1812 bound to server default
Listening on acct address * port 1813 bound to server default
Listening on auth address :: port 1812 bound to server default
Listening on acct address :: port 1813 bound to server default
Listening on auth address 127.0.0.1 port 18120 bound to server inner-tunnel
Listening on proxy address * port 39231
Listening on proxy address :: port 59954
Ready to process requests


Удаляем шаблоны, работающие по умолчанию default и inner-tunnel
# rm -f /etc/freeradius/raddb/sites-enabled/inner-tunnel
# rm -f /etc/freeradius/raddb/sites-enabled/default

Отключаем проксирование и вносим изменения в режим логирования:
# nano /etc/freeradius/raddb/radiusd.conf
Параметр proxy_requests ставим в "no" и комментируем директиву "$INCLUDE proxy.conf"
 proxy_requests  = no
 #$INCLUDE proxy.conf

В секции log {...} прописываем параметры auth и timestamp:
 auth = yes
 timestamp = yes

Отключаем логирование модуля unix
# nano /etc/freeradius/raddb/mods-enabled/unix
Комментируем строку "radwtmp = ${logdir}/radwtmp"
 #radwtmp = ${logdir}/radwtmp
Актируем функцию разбора Cisco AV Pair 
# nano /etc/freeradius/raddb/mods-enabled/preprocess
 with_cisco_vsa_hack = yes

Добавляем в словарь данные о типовых Cisco AV Pair для PPPoE подключений:
# nano /etc/freeradius/raddb/dictionary
Добавляем в конец:
 ATTRIBUTE       ppp-disconnect-cause   173      string
 ATTRIBUTE       disc-cause-ext         174      string

 
Создаем свой модуль работы с SQL: sql_sm
# touch /etc/freeradius/raddb/mods-available/sql_sm
# ln -s /etc/freeradius/raddb/mods-available/sql_sm /etc/freeradius/raddb/mods-enabled/sql_sm

Пока этот файл пустой.
Создаем шаблон для Cisco1001X на основе типового шаблона:
# cp /etc/freeradius/raddb/sites-available/default /etc/freeradius/raddb/sites-available/cisco1001X
# ln -s /etc/freeradius/raddb/sites-available/cisco1001X /etc/freeradius/raddb/sites-enabled/cisco1001X

Создаем пользователя под которым будет работать FreerADIUS
# groupadd freerad
# useradd -r -g freerad -s /sbin/nologin -d /var/lib/freeradius freerad

Даем пользователю права на логи:
# chown -R freerad:freerad /etc/freeradius/raddb/ /var/lib/freeradius
Проверяем еще раз запуск
# radiusd -X

Создаем unit systemd для FreeRADIUS:
# touch /etc/systemd/system/radiusd.service 
# chmod +x /etc/systemd/system/radiusd.service
# ln -s /etc/systemd/system/radiusd.service /etc/systemd/system/multi-user.target.wants/radiusd.service

Наполнение юнита:
 [Unit]
 Description=FreeRADIUS multi-protocol policy server
 After=network-online.target postgresql-16.service
 [Service]
 Type=forking
 ExecStartPre=/usr/sbin/radiusd -Cx -lstdout
 ExecStart=/usr/sbin/radiusd
 PIDFile=/var/lib/freeradius/run/radiusd/radiusd.pid
 User=freerad
 Group=freerad
 Restart=on-failure
 RestartSec=5
 ReadOnlyDirectories=/etc/freeradius/raddb/
 ReadWriteDirectories=/var/lib/freeradius/log
 PrivateTmp=true
 AmbientCapabilities=CAP_NET_BIND_SERVICE
 [Install]
 WantedBy=multi-user.target

Рестартуем демон systemd:
# systemctl daemon-reload
Теперь запуск сервера выполняем так:
# systemctl start radiusd.service
# systemctl status radiusd.service
# systemctl enable radiusd.service


Конфигурационные файлы FreeRADIUS тут /etc/freeradius/raddb/
Основной лог-файл: /var/lib/freeradius/log/radius/radius.log
Детальные accounting логи: /var/lib/freeradius/log/radius/radacct/
Для того что бы пользоваться без префикса утилитой radclient добавляем путь к утилите в переменные окружения:
# echo 'export PATH=$PATH:/usr/local/bin' >> ~/.bashrc
# source ~/.bashrc

Генерируем тестовый стартовый аккаутинговый пакет с помощью утилиты radclient:
# echo "User-Name = testuser, Acct-Status-Type = Start" | radclient -x localhost:1813 acct testing123
В ответ мы должны увидеть структурированный запрос и ответ сервера:
Sent Accounting-Request Id 87 from 0.0.0.0:39087 to 127.0.0.1:1813 length 36
        User-Name = "testuser"
        Acct-Status-Type = Start

Received Accounting-Response Id 87 from 127.0.0.1:1813 to 127.0.0.1:39087 length 20
В логах (/var/lib/freeradius/log/radius/radacct/127.0.0.1/detail-*) мы теперь должны видеть лог запроса.
Генерируем тестовый стоповый аккаутинговый пакет с помощью утилиты radclient.
Тут данные для формирования стопового пакета нужно записать в файл:
# cat > stoppak.txt << EOF
Acct-Status-Type = Stop
User-Name = samara260374
Acct-Session-Id = 1234567890
NAS-IP-Address = 192.168.1.1
NAS-Port = 1001
Acct-Session-Time = 3600
Acct-Terminate-Cause = User-Request
Acct-Input-Octets = 1048576
Acct-Output-Octets = 2097152
Acct-Input-Packets = 10000
Acct-Output-Packets = 15000
EOF

# radclient -x -4 10.0.1.52:1813 acct testing123 < stoppak.txt
В логах (/var/lib/freeradius/log/radius/radacct/127.0.0.1/detail-*) мы теперь должны увидеть лог стопового пакета.

Открываем в firewall порт 1813 для приема аккаутинговых пакетов:
# firewall-cmd --permanent --add-port=1813/udp
# firewall-cmd --reload
# firewall-cmd --list-all


Добавляем клиента (сетевое устройство) для работы с FreeRADIUS:
# nano /etc/freeradius/raddb/clients.conf 
Добавляем:
 client 10.10.0.2 {
      ipaddr = 10.10.0.2
      proto = *
      secret = smtools123
      shortname = ARS1001X_Samara
      require_message_authenticator = yes
      nas_type = cisco
 }

# /usr/sbin/radiusd -C
# systemctl restart radiusd.service


Готовим базу данных PostgreSQL для приема аккаутинга:
# su - postgres
# psql 
postgres=# CREATE DATABASE pppoelog;
postgres=# \c pppoelog;
postgres=# CREATE TABLE public.pppoelog_start_stop (
id bigserial NOT NULL,
nas_ip_address inet NULL,
cisco_nas_port text NULL,
session_id text NULL,
date_and_time timestamp NULL,
user_name text NULL,
mac macaddr NULL,
framed_ip_address inet NULL,
cisco_service_info text NULL,
acct_session_time int4 NULL,
acct_input_octets int8 NULL,
acct_output_octets int8 NULL,
acct_terminate_cause text NULL,
packet_type varchar(10) NULL,
acct_unique_session_id varchar(200) NULL,
CONSTRAINT pppoelog_start_stop_pkey PRIMARY KEY (id)
);
postgres=# CREATE INDEX i_date_and_time ON public.pppoelog_start_stop USING btree (date_and_time);
postgres=# CREATE INDEX i_framed_ip_address ON public.pppoelog_start_stop USING gist (framed_ip_address inet_ops);
postgres=# CREATE INDEX i_mac ON public.pppoelog_start_stop USING btree (mac);
postgres=# CREATE INDEX i_session_id ON public.pppoelog_start_stop USING btree (session_id);
postgres=# CREATE INDEX i_user_date_type_desc ON public.pppoelog_start_stop USING btree (user_name, packet_type, date_and_time DESC) WHERE ((packet_type)::text = 'stop'::text);
postgres=# CREATE INDEX i_user_name ON public.pppoelog_start_stop USING btree (user_name);
postgres=# CREATE USER pppoelog WITH ENCRYPTED PASSWORD 'superpassword';
postgres=# GRANT ALL privileges ON DATABASE pppoelog TO pppoelog;
postgres=# GRANT ALL ON pppoelog_start_stop TO pppoelog;
postgres=# GRANT USAGE ON SCHEMA public TO pppoelog;
postgres=# GRANT ALL ON ALL TABLES IN SCHEMA public TO pppoelog;
postgres=# GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO pppoelog;
postgres=# GRANT ALL ON ALL FUNCTIONS IN SCHEMA public TO pppoelog;
postgres=# GRANT ALL ON pppoelog_start_stop TO pppoelog;
postgres=# GRANT ALL ON SEQUENCE pppoelog_start_stop_id_seq TO pppoelog;
postgres=# ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO pppoelog;
postgres=# ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO pppoelog;
postgres=# ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON FUNCTIONS TO pppoelog;
postgres=# ALTER TABLE pppoelog_start_stop OWNER TO pppoelog;
postgres=# ALTER SEQUENCE pppoelog_start_stop_id_seq OWNER TO pppoelog;
postgres=# ALTER DEFAULT PRIVILEGES IN SCHEMA public OWNER TO pppoelog;

Формируем права доступа на базу данных:
# nano /var/lib/pgsql/16/data/pg_hba.conf
Вносим изменения:
# IPv4 local connections:
 host    all             pppoelog        10.0.0.0/8              md5

Разрешаем прослушивание сетевых интерфейсов:
# nano /var/lib/pgsql/16/data/postgresql.conf
 #listen_addresses = 'localhost'         # what IP address(es) to listen on;
 listen_addresses = '*'

Рестартуем базу:
# systemctl restart postgresql-16.service

Открываем доступ к базе по сети:
# firewall-cmd --permanent --add-port=5432/tcp
# firewall-cmd --reload
# firewall-cmd --list-all


Теперь активируем в экземпляре Freeradius модуль sql_sm
# nano /etc/freeradius/raddb/sites-enabled/cisco1001X 
Вносим изменения в раздел "accounting"
 accounting {
        ...
#-sql
sql_sm
        ...
 }

Теперь заполняем данные для модуля SQL "sql_sm"
# nano /etc/freeradius/raddb/mods-enabled/sql_sm
 sql sql_sm {
        driver = "rlm_sql_postgresql"
        dialect = "postgresql"
        server = "10.0.1.52"
        port = 5432
        login = "pppoelog"
        password = "superpassword"
        radius_db = "pppoelog"
        acct_table1 = "pppoelog_start_stop"
        pool {
                start = ${thread[pool].start_servers}
                min = ${thread[pool].min_spare_servers}
                max = ${thread[pool].max_servers}
                spare = ${thread[pool].max_spare_servers}
                uses = 0
                retry_delay = 30
                lifetime = 0
                idle_timeout = 60
        }
        $INCLUDE /etc/freeradius/raddb/mods-config/sql/main/postgresql/queries_sqlsm.conf
 }

В файле /etc/freeradius/raddb/mods-config/sql/main/postgresql/queries_sqlsm.conf необходимо прописать запросы для доступа к базе.
# touch /etc/freeradius/raddb/mods-config/sql/main/postgresql/queries_sqlsm.conf
# nano /etc/freeradius/raddb/mods-config/sql/main/postgresql/queries_sqlsm.conf

 accounting {
      reference = "%{tolower:type.%{%{Acct-Status-Type}:-none}.query}"
        column_list = "\
                nas_ip_address, \
                cisco_nas_port, \
                session_id, \
                date_and_time, \
                user_name, \
                mac, \
                framed_ip_address, \
                cisco_service_info, \
                acct_session_time, \
                acct_input_octets, \
                acct_output_octets, \
                acct_terminate_cause, \
                packet_type, \
                acct_unique_session_id"
        type {
                accounting-on {
                }
                accounting-off {
                }
                start {
                      query = "\
                              INSERT INTO ${....acct_table1} \
                                       (${...column_list}) \
                               VALUES(\
                                        '%{NAS-IP-Address}', \
                                        '%{Cisco-NAS-Port}', \
                                        '%{Acct-Session-Id}', \
                                        TO_TIMESTAMP(%{integer:Event-Timestamp}), \
                                        '%{User-Name}', \
                                        NULLIF('%{Calling-Station-Id}', '')::macaddr, \
                                        NULLIF('%{Framed-IP-Address}', '')::inet, \
                                        '%{Cisco-Service-Info}', \
                                        0, \
                                        0, \
                                        0, \
                                        '', \
                                        'start', \
                                        '%{Acct-Unique-Session-Id}')"
                }
                interim-update {
                }
                stop {
                      query = "\
                              INSERT INTO ${....acct_table1} \
                                       (${...column_list}) \
                               VALUES(\
                                        '%{NAS-IP-Address}', \
                                        '%{Cisco-NAS-Port}', \
                                        '%{Acct-Session-Id}', \
                                        TO_TIMESTAMP(%{integer:Event-Timestamp}), \
                                        '%{User-Name}', \
                                        NULLIF('%{Calling-Station-Id}', '')::macaddr, \
                                        NULLIF('%{Framed-IP-Address}', '')::inet, \
                                        '%{Cisco-Service-Info}', \
                                        NULLIF('%{Acct-Session-Time}', '')::bigint, \
                                        (('%{%{Acct-Input-Gigawords}:-0}'::bigint << 32) + \
                                                '%{%{Acct-Input-Octets}:-0}'::bigint), \
                                        (('%{%{Acct-Output-Gigawords}:-0}'::bigint << 32) + \
                                                '%{%{Acct-Output-Octets}:-0}'::bigint), \
                                        '%{ppp-disconnect-cause}, %{disc-cause-ext}', \
                                        'stop', \
                                        '%{Acct-Unique-Session-Id}')"

                }
                none {
                }
        }
 }

Рестартуем Freeradius:
# systemctl restart radiusd.service
Теперь аккаутинг должен записыватсья в базу данных PostgreSQL.

Нам больше не нужен лог файл с аккаутингом в файле.
# nano /etc/freeradius/raddb/sites-enabled/cisco1001X
Необходимо закомментировать модуль "detail" в секции "accounting"
 accounting {
        #  Create a 'detail'ed log of the packets.
        #  Note that accounting requests which are proxied
        #  are also logged in the detail file.
#detail
        ...
 }

И рестартовать Freeradius:
# systemctl restart radiusd.service