вторник, 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

Сбор syslog и snmptrap с сетевых устройств в базу Clikhouse через Vector. Rocky Linux 9.7.

Реализуем следующую схему:
1. Настройка NTP сервера для корректного учета времени логов.
Проверяем установку NTP клиента
# systemctl status chronyd
Устанавливаем сервера времени:
# nano /etc/chrony.conf
Сервера для работы:
 server ntp.vtt.net iburst
 pool ru.pool.ntp.org iburst
 server ntp1.yandex.ru
 server ntp2.yandex.ru
 server ntp1.stratum2.ru iburst
 server ntp2.stratum2.ru iburst

# systemctl restart chronyd
Проверка:
# timedatectl status
# date
# chronyc tracking
# chronyc sources


2.Для быстрой работы базы Clickhouse необходимо немного подтюнить операционную систему:
2.1 Отключить использование SWAP
# swapoff -a
Закомментировать строку, подключающую SWAP в fstab
# nano /etc/fstab
У меня это строка:
 #/dev/mapper/rl-swap     none                    swap    defaults        0 0
2.2 Увеличить лимит открытых файлов (для high-load)
# echo "clickhouse soft nofile 262144" | sudo tee -a /etc/security/limits.conf
# echo "clickhouse hard nofile 262144" | sudo tee -a /etc/security/limits.conf

2.3 Включить опцию madvise механизма Transparent Huge Pages (THP) и разрешить Clickhouse опрос задержек ядра.
Цель: что бы ОС Linux не мешала Clikhouse использовать большие блоки памяти (На виртуальных машинах работает не всегда).
И чтобы Clickhouse не ругался на невозможность мониторить параметры задержки ядра процессора.
# nano /etc/sysctl.conf
Добавляем строки:
 vm.transparent_hugepage = madvise
 kernel.task_delayacct = 1

Добавляем в загрузчик GRUB опцию загрузки ОС с параметром madvise:
# grubby --update-kernel=ALL --args="transparent_hugepage=madvise"
Теперь необходима перезагрузка сервера.
# reboot
После перезагрузки проверяем, что опция madvise применилась. Пример правильного вывода:
# cat /proc/cmdline
BOOT_IMAGE=(hd0,msdos1)/vmlinuz-5.14.0-611.36.1.el9_7.x86_64 root=/dev/mapper/rl-root ro resume=/dev/mapper/rl-swap rd.lvm.lv=rl/root rd.lvm.lv=rl/swap crashkernel=1G-2G:192M,2G-64G:256M,64G-:512M transparent_hugepage=madvise
# cat /sys/kernel/mm/transparent_hugepage/enabled
always [madvise] never

# sysctl kernel.task_delayacct
kernel.task_delayacct = 1
# cat /proc/sys/kernel/task_delayacct
1


3. Устанавливаем Clikhouse:
# dnf install yum-utils curl ca-certificates gnupg2
# yum-config-manager --add-repo https://packages.clickhouse.com/rpm/clickhouse.repo
# dnf install -y clickhouse-server clickhouse-client
# systemctl enable --now clickhouse-server
# systemctl status clickhouse-server

После установки и запуска сервера, выполняем проверку:
# clickhouse-client --query "SELECT version();"

4. Правим конфигурационный файл Clikhouse
# nano /etc/clickhouse-server/config.xml
Раскомментируем строку (тем самым отключаем IPv6)
 <listen_host>0.0.0.0</listen_host> 
Изменяем уровень логирования – оставляем только ошибки
 <logger>
   <level>warning</level> 
 <logger>

Ограничиваем сервер полкой в использовании памяти - в 5Г, иначе сервер съест другие приложения
 <max_server_memory_usage>5000000000</max_server_memory_usage>
Отключаем процентное указание в использование памяти
 <max_server_memory_usage_to_ram_ratio>0</max_server_memory_usage_to_ram_ratio>
Рестартуем сервис для изменений:
# systemctl restart clickhouse-server

5. Создаем базу данных и таблицу для записи метрик с партициями по месяцу:
# clickhouse-client -h localhost
 :) CREATE DATABASE IF NOT EXISTS logsdb;
 :) CREATE TABLE logsdb.logsdb (
    ts DateTime64(3),
    d Date MATERIALIZED toDate(ts),
    source_type Enum8('syslog' = 1, 'snmptrap' = 2),
    host String,
    severity LowCardinality(String),
    facility LowCardinality(String),
    message String CODEC(ZSTD(3)),
    INDEX idx_host_token host TYPE tokenbf_v1(1024, 3, 0) GRANULARITY 4,
    INDEX idx_message_token message TYPE tokenbf_v1(4096, 3, 0) GRANULARITY 8,
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(d)
ORDER BY (ts, source_type, host)
TTL d + INTERVAL 1 YEAR
SETTINGS
    index_granularity = 8192,
    compress_primary_key = 1,
    enable_mixed_granularity_parts = 1,
    min_bytes_for_wide_part = 0;


6. Устанавливаем демон SNMPTRAPD, который будет ловить трапы от оборудования. Демон snmptrapd входит в пакет net-snmp.
# dnf install net-snmp net-snmp-utils
Конфиг лежит тут: /etc/snmp/snmptrapd.conf
# nano /etc/snmp/snmptrapd.conf
В конфигурационый файл вносим директивы:
 disableAuthorization yes
 # Формат для SNMPv1
 format1 TRAPIP=%b | SNMPVER=1 | TRAPTIME=%y-%m-%L %H:%J:%k | TRAPTYPE=%W | TRAPVAL: %v\n
 # Формат для SNMPv2c
 format2 TRAPIP=%b | SNMPVER=2 | TRAPTIME=%y-%m-%L %H:%J:%k | TRAPTYPE=%W | TRAPVAL: %v\n

Создаем файл логов snmptrapd:
# touch /var/log/snmptrapd.log
# chmod 644 /var/log/snmptrapd.log

Запускаем демон для теста
# snmptrapd -f -Lo -c /etc/snmp/snmptrapd.conf
Из другой консоли генерируем отправку трапа:
# snmptrap -v 2c -c my_secret_string 127.0.0.1 "" .1.3.6.1.4.1.9.9.43.2.0.1
При этом в консоле демона snmptrapd должны увидеть захваченный лог.

7. Настраиваем unit systemd для запуска SNMPTRAPD.
При настройке важно, что режим перенаправления логов в файл задается при запуске демона:
# systemctl edit snmptrapd
Вставляем команды редактирующие основной unit:
 ### Anything between here and the comment below will become the new contents of the file
 [Service]
 ExecStart=
 ExecStart=/usr/sbin/snmptrapd -f -A -c /etc/snmp/snmptrapd.conf -Lf /var/log/snmptrapd.log
 ### Lines below this comment will be discarded

Запускаем службу:
# systemctl daemon-reload
# systemctl start snmptrapd
# systemctl enable snmptrapd
# systemctl status snmptrapd


8. Организовываем ротацию файлов логов трапов snmp
# nano /etc/logrotate.d/snmptrapd
Содержимое файла:
 /var/log/snmptrapd.log {
    daily
    rotate 7
    missingok
    notifempty
    copytruncate
 }

Проверка синтаксиса:
# logrotate -d /etc/logrotate.d/snmptrapd
Принудительная ротация логов прямо сейчас:
# logrotate -f /etc/logrotate.d/snmptrapd

9. Устанавливаем Vector.
Скачиваем пакет установки Vector для Rocky Linux 9.6:
# cd /usr/src
# wget https://packages.timber.io/vector/0.48.0/vector-0.48.0-1.x86_64.rpm
# sudo dnf localinstall -y vector-0.48.0-1.x86_64.rpm

Проверка
# vector --version
# which vector


10. Конфигурация Vector для приема syslog и snmptrap
# nano /etc/vector/vector.toml
 [sources.syslog]
 type = "socket"
 address = "0.0.0.0:514"
 mode = "udp"
 decoding.codec = "bytes"
 host_key = "source_ip"
 [sources.snmptrap]
 type = "file"
 include = ["/var/log/snmptrapd.log"]
 read_from = "beginning"
 ignore_not_found = true
 fingerprinting.strategy = "device_and_inode"
 [transforms.parse_syslog]
 type = "remap"
 inputs = ["syslog"]
 source = '''
 messageone = to_string!(.message)
 # Ищем PRI в любом месте строки, извлекаем только цифры внутри <>
 matched = parse_regex(messageone, r'\<(?P<pri_value>[0-9]+)>(?P<mess>.*)$') ?? {}
 .message = to_string(matched.mess)
 if matched.pri_value != null {
    .pri = matched.pri_value
    
    # Выводим в лог
    #log("MATCHED PRI VALUE: " + .pri)
    .fac_id = (to_int(to_int!(.pri) / 8))
    .facility_name = "unknown"
    if .fac_id == 0 { .facility_name = "kern" }
    if .fac_id == 1 { .facility_name = "user" }
    if .fac_id == 2 { .facility_name = "mail" }
    if .fac_id == 3 { .facility_name = "daemon" }
    if .fac_id == 4 { .facility_name = "auth" }
    if .fac_id == 5 { .facility_name = "syslog" }
    if .fac_id == 6 { .facility_name = "lpr" }
    if .fac_id == 7 { .facility_name = "news" }
    if .fac_id == 8 { .facility_name = "uucp" }
    if .fac_id == 9 { .facility_name = "cron" }
    if .fac_id == 10 { .facility_name = "authpriv" }
    if .fac_id == 11 { .facility_name = "ftp" }
    if .fac_id == 12 { .facility_name = "ntp" }
    if .fac_id == 13 { .facility_name = "audit" }
    if .fac_id == 14 { .facility_name = "alert" }
    if .fac_id == 15 { .facility_name = "clock" }
    if .fac_id == 16 { .facility_name = "local0" }
    if .fac_id == 17 { .facility_name = "local1" }
    if .fac_id == 18 { .facility_name = "local2" }
    if .fac_id == 19 { .facility_name = "local3" }
    if .fac_id == 20 { .facility_name = "local4" }
    if .fac_id == 21 { .facility_name = "local5" }
    if .fac_id == 22 { .facility_name = "local6" }
    if .fac_id == 23 { .facility_name = "local7" }
    .facility = .facility_name
    sev_id = to_int!(.pri) - (.fac_id * 8)
    .severity_name = "unknown" 
    if sev_id == 0 { .severity_name = "emerg" }
    if sev_id == 1 { .severity_name = "alert" }
    if sev_id == 2 { .severity_name = "crit" }
    if sev_id == 3 { .severity_name = "err" }
    if sev_id == 4 { .severity_name = "warning" }
    if sev_id == 5 { .severity_name = "notice" }
    if sev_id == 6 { .severity_name = "info" }
    if sev_id == 7 { .severity_name = "debug" }
    .severity = .severity_name
 } else {
    #log("PRI not found in message", level: "warn")
    .facility = "unknown"
    .severity = "unknown"
    .message = messageone
 }
 .ts = to_int(to_unix_timestamp(now()) * 1000.0)
 .source_type = 1
 .host = .source_ip
 '''
 [transforms.parse_snmptrap]
 type = "remap"
 inputs = ["snmptrap"]
 source = '''
 ts_value = to_int(to_unix_timestamp(now()) * 1000.0)
 # Разбиваем сообщение на массив
 trap_array = split(string!(.message), "|")
 arr_len = length(trap_array)
 # Инициализируем переменные дефолтными значениями
 snmpver = ""
 trapip = ""
 msg_2 = ""
 msg_3 = ""
 msg_4 = ""
 facility_value = "snmp_unknown"
 message_value = ""
 # Заполняем переменные только если индексы физически существуют в массиве
 if arr_len > 0 { trapip = to_string(trap_array[0]) }
 if arr_len > 1 { snmpver = to_string(trap_array[1]) }
 if arr_len > 2 { msg_2 = to_string(trap_array[2]) }
 if arr_len > 3 { msg_3 = to_string(trap_array[3]) }
 if arr_len > 4 { msg_4 = to_string(trap_array[4]) }
 matched = parse_regex(trapip, r'UDP:\s*\[(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]') ?? {}
 host_value = to_string(matched.ip)
 if snmpver == " SNMPVER=1 " {
  facility_value = "snmp1"
 } else {
  facility_value = "snmp2"
 }
 # Если строка вообще не содержала разделителей "|", сохраняем оригинальное сообщение целиком
 if msg_2 == "" && msg_3 == "" {
  message_value = strip_whitespace(string!(.message))
 } else {
  message_value = strip_whitespace(msg_2 + "|" + msg_3 + "|" + msg_4)
 }
 # Полностью очищаем объект от системных метаданных Vector
 . = {}
 # Наполняем строго по схеме таблицы net_logs
 .ts = ts_value
 .host = host_value
 .facility = facility_value
 .message = message_value
 .severity = "info"
 .source_type = "snmptrap"
 '''
 [sinks.clickhouse_syslog]
 type = "clickhouse"
 inputs = ["parse_syslog"]
 database = "logsdb"
 table = "net_logs"
 endpoint = "http://localhost:8123"
 compression = "none"
 [sinks.clickhouse_syslog.auth]
 strategy = "basic"
 user = "default"
 password = ""
 [sinks.clickhouse_syslog.batch]
 max_events = 1000
 timeout_secs = 1
 [sinks.clickhouse_snmptrap]
 type = "clickhouse"
 inputs = ["parse_snmptrap"]
 database = "logsdb"
 table = "net_logs"
 endpoint = "http://localhost:8123"
 compression = "none"
 [sinks.clickhouse_snmptrap.auth]
 strategy = "basic"
 user = "default"
 password = ""
 [sinks.clickhouse_snmptrap.batch]
 max_events = 1000
 timeout_secs = 1
 [sinks.clickhouse_snmptrap.request]
 retry_attempts = 0

Проверяем конфиг
# vector validate --config-dir /etc/vector/
Пытаемся запуститься под root
# vector --config /etc/vector/vector.toml
Видим, что все запускается, порты 162 и 514 начинают прослушиваться.

11. Настройка службы запуска vector.
11.1 По умолчанию Linux не позволяет обычным программам, типа vector, слушать порты ниже 1024. Vector же должен слушать порты SNMPtrap (162) и syslog (514):
# setcap 'cap_net_bind_service=+ep' $(which vector)
11.2 Вносим изменения в unit systemd службы vector
# systemctl edit vector
Вставляем директивы запуска, отличные от типовых:
 ### Anything between here and the comment below will become the new contents of the file
 [Unit]
 After=network-online.target snmptrapd.service clickhouse-server.service
 Requires=network-online.target snmptrapd.service clickhouse-server.service
 [Service]
 User=vector
 Group=vector
 ExecStart=
 ExecStart=/usr/bin/vector --config /etc/vector/vector.toml
 AmbientCapabilities=CAP_NET_BIND_SERVICE
 CapabilityBoundingSet=CAP_NET_BIND_SERVICE
 NoNewPrivileges=yes
 Restart=on-failure
 RestartSec=5
 PrivateNetwork=no
 RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
 Environment=VECTOR_LOG_FORMAT=text
 ### Lines below this comment will be discarded

Запускаем службу:
# systemctl daemon-reload
# systemctl start vector
# systemctl enable vector

Проверка работы:
# journalctl -u vector -f

12. Настраиваем firewalld:
Открываем порты для внешнего доступа к сервисам Clikhouse (8123 и 9000), для SNMPtrap (162) и syslog (514):
# firewall-cmd --permanent --add-port=8123/tcp
# firewall-cmd --permanent --add-port=9000/tcp
# firewall-cmd --permanent --add-port=514/udp
# firewall-cmd --permanent --add-port=162/udp
# firewall-cmd --reload
# firewall-cmd --list-ports


13. Проверка вставки данных:
13.1 Тестовая отправка syslog:
# logger -n 127.0.0.1 -p local0.info -t vector_test "Pipeline check $(date +%s)"
Тестовая отправка snmptrap:
# snmptrap -v 2c -c my_secret_string 127.0.0.1 "" .1.3.6.1.4.1.9.9.43.2.0.1
# snmptrap -v 1  -c my_secret_string 127.0.0.1 1.3.6.1.4.1.9.9.43 192.168.100.100 6 1 0 .1.3.6.1.4.1.9.9.43.2.0.1 s "test"
13.2 Проверка записи данных в базу Clickhouse
# clickhouse-client -h localhost --query "SELECT * FROM logsdb.net_logs ORDER BY ts DESC LIMIT 5"

ПРИМЕЧАНИЕ:
Если Vector перестал записывать данные логов из файла /var/log/snmptrapd.log, то может быть он запомнил старые данные inode файла, который поменялся при ротации.
Можно очистить checkpoints сервиса vector
# systemctl stop vector
# rm -rf /var/lib/vector/*
# systemctl start vector