| Cron | Кому шлется |
|---|---|
| autodiscount | |
| chown deployer | |
| crontab системный (s3_sync, chown_deployer, sf3_email_archive_restart, ipset_netban_import_bannedDB, email_sync) | StratyukI |
| crontab | VoronkovA, +1h StratyukI |
| daily_cleanup | |
| delete_vehicles_sold | Backend, StratyukI |
| export'ы | VoronkovA, LevinN, +1h StratyukI |
| import'ы | VoronkovA, LevinN, +1h StratyukI |
| isa_chown | |
| isa_clean | |
| leads | |
| model-from-mobile_lookup_sync | |
| mongo_query | |
| portal-sync | |
| todo_run | Backend, StratyukI |
| vehicle_cleaner |
| Cron | Кому шлется |
|---|---|
| cars-co-za | Backend, StratyukI |
90% Warning
90% Warning
50% Warning
90% Critical
80% Warning
90% Warning
| Хост | Кому шлется |
|---|---|
| K8S-Load-Balancer | Kirill, StratyukI |
| gsz-mail | Kirill, StratyukI |
| sy-jenkins-node | Kirill, StratyukI |
| sy-zabbix | Kirill, StratyukI |
| sy-dev | Kirill, StratyukI |
| sy-live | Kirill, StratyukI |
| sy-rabbitmq | Kirill, StratyukI |
| sa-live | Kirill, StratyukI |
| sy-redmine | Kirill, StratyukI |
| sy-harbor | Kirill, StratyukI |
| sy-jenkins | Kirill, StratyukI |
| sy-logs | Kirill, StratyukI |
| sy-mail | Kirill, StratyukI |
| sy-mongo | Kirill, StratyukI |
| sy-lnx-parser1 | StratyukI |
| sy-lnx-parser2 | StratyukI, BiletskiyS(только на отвал хоста) |
| sy-lnx-parser3 | StratyukI, BiletskiyS(только на отвал хоста) |
sy-jenkins
Check symfio live sites
Сайты проверяются каждые две минуты. Запускаются параллельные пинги curl'ом.
sy-jenkins
Все проблемные сайты попадают в общий список и далее он весь отправляется при проблемах:
Warning: Дилерские сайты имеют проблемыWarning: Дилерские сайты имеют проблемыWarning: Дилерские сайты имеют проблемыWarning: Дилерские сайты имеют проблемыwarning уже как Critical: Массовая проблема с дилерскими сайтами, что наличествует массовая проблемаKirill
StratyukI
sites.info[{$HTTP_API_URLS},{$PING_COUNT},{$PING_TIMEOUT},{$PING_TIME_THRESHOLD},{$PING_SIZE_THRESHOLD},{$PING_JOBS},{$PING_DELAY}]
| Макрос | Значение | Описание |
|---|---|---|
https://api.live.symfio.de/ api/v3/backend/dealership/list?filters_id=admin_tools_dealership_live |
URL endpoint'а, с которого получаем список дилерских сайтов | |
| 3 | Количество пингов на один сайт | |
| 0.1 | Задержка между сериями пинга для уменьшения нагрузки на хост в плане ресурсов (ЦПУ и сеть) | |
| 10 | Сколько запускать параллельных процессов curl для пинга сайтов | |
| 20 | Количество сайтов, если проблема уже массовая | |
| 2000 | Порог размера страницы. Если меньше, то ворнинг | |
| 3 | Время, в секундах, в течении которого curl будет ждать ответа | |
| 0.9 | Порог времени ответа от сайта. Если больше, то ворнинг (пока тестово) |
/etc/zabbix/zabbix_agent2.d/ping_site.conf
UserParameter=sites.info[*],/etc/zabbix/scripts/ping_site.sh --url $1 --pings $2 --timeout $3 --time-threshold $4 --size-threshold $5 --jobs $6 --delay $7
/etc/zabbix/scripts/ping_site.sh
#!/bin/bash
# Параметры по умолчанию
PING_COUNT=3
TIMEOUT=5
TIME_THRESHOLD=1.0
SIZE_THRESHOLD=1000
JOBS=5
DELAY=0.1
API_URL=""
# Парсинг аргументов
while [[ "$#" -gt 0 ]]; do
case "$1" in
--url) API_URL="$2"; shift ;;
--pings) PING_COUNT="$2"; shift ;;
--timeout) TIMEOUT="$2"; shift ;;
--time-threshold) TIME_THRESHOLD="$2"; shift ;;
--size-threshold) SIZE_THRESHOLD="$2"; shift ;;
--jobs) JOBS="$2"; shift ;;
--delay) DELAY="$2"; shift ;;
*) echo "Неизвестный параметр: $1" >&2; exit 1 ;;
esac
shift
done
# Проверка зависимостей
for cmd in curl jq parallel dig; do
if ! command -v "$cmd" &>/dev/null; then
echo "❌ Требуется '$cmd'" >&2
exit 1
fi
done
TMP_DIR=$(mktemp -d)
ERROR_LOG=$(mktemp)
# Экспортируем TMP_DIR для использования в parallel
export TMP_DIR
# Получение списка сайтов
SITE_LIST=$(curl -s --http2 --keepalive-time 20 "$API_URL" | jq -c '.data[] | {url: .url, locale: .locale, domain: (.url | sub("https?://"; "") | sub("/+$"; "") | gsub("[^a-zA-Z0-9.-]"; "_"))}')
if [[ -z "$SITE_LIST" ]]; then
echo "❌ Не удалось получить список сайтов или он пустой" >> "$ERROR_LOG"
exit 1
fi
# Логируем количество сайтов
echo "🔍 Получено сайтов: $(echo "$SITE_LIST" | wc -l)" >> "$ERROR_LOG"
# Функция проверки сайта (последовательные пинги)
fetch_info() {
local url="$1"
local locale="$2"
local domain="$3"
local full_url="${url}/${locale}"
# Проверка корректности URL
if [[ ! "$full_url" =~ ^https?:// ]]; then
echo "⚠️ Некорректный URL: $full_url" >> "$ERROR_LOG"
jq -n '{}'
return 1
fi
# Логируем формируемый URL и имя файла
echo "🔍 Проверка URL: $full_url, запись в $TMP_DIR/${domain}_{#}.json" >> "$ERROR_LOG"
local host=$(echo "$url" | awk -F[/:] '{print $4}')
local ip=$(dig +short "$host" | head -n 1)
local max_time=0
local min_size=999999999
local total_time=0
local status_ok=true
local code_list=()
local success_count=0
# Последовательные запросы для одного сайта
for ((i=1; i<=PING_COUNT; i++)); do
result=$(curl -s -o /dev/null -w '%{http_code} %{size_download} %{time_total}' \
--max-time "$TIMEOUT" --http2 "$full_url" 2>>"$ERROR_LOG")
read -r code size time <<< "$result"
# Логируем результат curl
echo "🔍 Пинг $i для $full_url: код=$code, размер=$size, время=$time" >> "$ERROR_LOG"
if [[ -z "$code" || "$code" == "000" ]]; then
echo "⚠️ Ошибка curl для $full_url (пинг $i): код $code" >> "$ERROR_LOG"
code_list+=("000")
status_ok=false
continue
fi
code_list+=("$code")
if [[ "$code" == "200" ]]; then
total_time=$(awk "BEGIN {print $total_time + $time}")
((success_count++))
if (( $(awk "BEGIN {print ($time > $max_time)}") )); then
max_time="$time"
fi
if (( size < min_size )); then
min_size="$size"
fi
else
status_ok=false
fi
# Задержка между пингами
sleep "$DELAY"
done
# Расчет метрик
local avg_time="null"
local min_size_result="$min_size"
if (( success_count > 0 )); then
avg_time=$(awk "BEGIN {printf \"%.3f\", $total_time / $success_count}")
else
max_time="null"
min_size_result="null"
fi
# Определение проблем
local problem_reason=()
if [[ "$status_ok" == false && "$success_count" -eq 0 ]]; then
problem_reason+=("status")
fi
if [[ "$status_ok" == false && "$success_count" -gt 0 ]]; then
problem_reason+=("status_rare")
fi
if [[ "$max_time" != "null" && $(awk "BEGIN {print ($max_time > $TIME_THRESHOLD)}") -eq 1 ]]; then
problem_reason+=("time")
fi
if [[ "$min_size_result" != "null" && "$min_size_result" -lt "$SIZE_THRESHOLD" ]]; then
problem_reason+=("size")
fi
# Логируем метрики для отладки
echo "🔍 Метрики для $full_url: http_codes=${code_list[*]}, max_time=$max_time, min_size=$min_size_result, reasons=${problem_reason[*]}" >> "$ERROR_LOG"
# Формирование JSON
jq -n \
--arg url "$full_url" \
--arg ip "${ip:-null}" \
--argjson avg_time "${avg_time:-null}" \
--argjson min_size "${min_size_result:-null}" \
--argjson max_time "${max_time:-null}" \
--argjson pings "$success_count" \
--argjson attempts "$PING_COUNT" \
--argjson codes "$(printf '%s\n' "${code_list[@]}" | jq -R . | jq -s .)" \
--argjson reasons "$(printf '%s\n' "${problem_reason[@]}" | jq -R . | jq -s .)" \
'{
url: $url,
ip: $ip,
avg_time: $avg_time,
max_time: $max_time,
min_size: $min_size,
http_codes: $codes,
reasons: $reasons,
pings_success: $pings,
pings_total: $attempts
}'
}
export -f fetch_info
export PING_COUNT TIMEOUT TIME_THRESHOLD SIZE_THRESHOLD ERROR_LOG DELAY TMP_DIR
# Обработка всех сайтов
# Примечание: Убедись, что выполнена команда 'parallel --citation' для подавления сообщения о цитировании
echo "$SITE_LIST" | jq -r '. as $s | "\($s.url | rtrimstr("/")) \($s.locale) \($s.domain)"' \
| parallel -j "$JOBS" --colsep ' ' --silent 'fetch_info {1} {2} {3} > "$TMP_DIR/{3}_{#}.json"' > /dev/null 2>&1
# Сбор статистики
if [[ -n $(find "$TMP_DIR" -type f -name '*.json') ]]; then
all_results=$(find "$TMP_DIR" -type f -name '*.json' -exec cat {} \; | jq -s '. | map(select(.url != null))')
else
echo "❌ Нет JSON-файлов в $TMP_DIR" >> "$ERROR_LOG"
all_results="[]"
fi
# Логируем $all_results и его длину для отладки
echo "$all_results" > "$TMP_DIR/all_results.json"
echo "🔍 Общее количество сайтов: $(echo "$all_results" | jq 'length')" >> "$ERROR_LOG"
echo "🔍 Количество сайтов без проблем: $(echo "$all_results" | jq '[.[] | select(.reasons == [])] | length')" >> "$ERROR_LOG"
get_by_reason() {
local key="$1"
case "$key" in
time)
echo "$all_results" | jq -r --arg reason "$key" '
map(select(.reasons | index($reason))) as $problems |
{
count: ($problems | length),
list: ($problems | map(.ip + ": " + (.max_time | tostring) + ": " + .url))
}
'
;;
size)
echo "$all_results" | jq -r --arg reason "$key" '
map(select(.reasons | index($reason))) as $problems |
{
count: ($problems | length),
list: ($problems | map(.ip + ": " + (.min_size | tostring) + ": " + .url))
}
'
;;
status|status_rare)
echo "$all_results" | jq -r --arg reason "$key" '
map(select(.reasons | index($reason))) as $problems |
{
count: ($problems | length),
list: ($problems | map(.ip + ": " + (.http_codes | tostring) + ": " + .url))
}
'
;;
*)
echo '{"count": 0, "list": []}'
;;
esac
}
# Итоговый отчет
jq -n \
--argjson all "$all_results" \
--argjson status "$(get_by_reason "status")" \
--argjson status_rare "$(get_by_reason "status_rare")" \
--argjson time "$(get_by_reason "time")" \
--argjson size "$(get_by_reason "size")" \
'{
"total_sites": ($all | length),
"problem_sites": {
"status": {
"count": $status.count,
"list": $status.list
},
"status_rare": {
"count": $status_rare.count,
"list": $status_rare.list
},
"time": {
"count": $time.count,
"list": $time.list
},
"size": {
"count": $size.count,
"list": $size.list
}
},
"stats": {
"success_rate": (
($all | map(select(
(.reasons | type == "array") and
(.reasons | all(. == ""))
)) | length) as $ok |
($all | length) as $total |
if $total > 0 then ($ok / $total * 100) else 0 end
),
"avg_response_time": (
($all | map(.avg_time | select(. != null) | tonumber) | add) as $sum |
($all | map(.avg_time | select(. != null)) | length) as $count |
if $count > 0 then ($sum / $count) else null end
),
"min_page_size": (
($all | map(.min_size | select(. != null) | tonumber) | min) as $min |
if $min != null then $min else null end
)
}
}'
rm -f "$ERROR_LOG"
rm -rf "$TMP_DIR"
sy-live
MongoDB Query by Zabbix agent
sy-live
sy-mail
sy-mongo
Высылается количество проблемных записей count в БД. Также в новой конфигурации высылаются сами проблемные записи в количестве до 100, чтобы можно было сразу охватить взглядом проблему. count > 0 Warning count = 0 Recovery
/etc/zabbix/zabbix_agent2.d/mongodb_check_field_duplicate.conf
UserParameter=fields.duplicate[*],/usr/bin/python3.6 /sy/scripts/zabbix/check_field_duplicate.py -o $1 -p $2 -u $3 -w $4 -a $5 -d $6 -c $7 -f $8
/sy/scripts/zabbix/check_field_duplicate.py
from pymongo import MongoClient
import urllib.parse
import re
from bson.son import SON
import argparse
import json
# Подключение к MongoDB
host = ""
port = ""
user = ""
password = ""
dbaname = ''
dbname = ""
collection = ""
field = ""
parser = argparse.ArgumentParser(description='Укажи параметры MongoDB')
parser.add_argument('-o', '--host', type=str, default=host, help='Адрес сервера MongoDB')
parser.add_argument('-p', '--port', type=int, default=port, help='Порт сервера MongoDB')
parser.add_argument('-u', '--user', type=str, default=user, help='Пользователь')
parser.add_argument('-w', '--password', type=str, default=password, help='Пароль пользователя')
parser.add_argument('-a', '--dba', type=str, default=dbaname, help='БД авторизации пользователя')
parser.add_argument('-d', '--database', type=str, default=dbname, help='Имя БД')
parser.add_argument('-c', '--collection', type=str, default=collection, help='Коллекция')
parser.add_argument('-f', '--field', type=str, default=field, help='Поле документа')
parser.add_argument('-s', '--simple', type=int, default=None, help='Упрощенное подключение')
args = parser.parse_args()
if args.field:
client = MongoClient(f'mongodb://{urllib.parse.quote_plus(args.user)}:{urllib.parse.quote_plus(args.password)}@{args.host}:{args.port}/{args.dba}')
if args.simple is not None:
client = MongoClient(f'mongodb://{host}:{port}/{args.dba}')
db = client[args.database]
collection = db[args.collection]
# Определение агрегации
pipeline = [
{"$group": {
"_id": f'${args.field}',
"count": {"$sum": 1}
}},
{"$project": {
"_id": 0,
"value": "$_id",
"count": 1
}}
]
# Выполнение агрегации
result = list(collection.aggregate(pipeline))
#print(result)
# Обработка результата
n = 0
arc = []
for doc in result:
if doc["count"] > 1:
n = 1
#print (doc)
arc.append({"value": doc["value"], "count": doc["count"]})
if n == 0 or arc is None:
arc.append({"value": 0, "count": 0})
response_json = json.dumps(arc)
print(response_json)
mongodb_check_field_exists.conf
UserParameter=fields.exists[*],/usr/bin/python3.6 /sy/scripts/zabbix/check_field_exists.py -o $1 -p $2 -u $3 -w $4 -a $5 -d $6 -c $7 -f $8
/sy/scripts/zabbix/check_field_exists.py
# -*- coding: utf-8 -*-
from pymongo import MongoClient
import urllib.parse
import re
from bson.son import SON
import argparse
import json
# Подключение к MongoDB
host = ""
port = ""
user = ""
password = ""
dbaname = ''
dbname = ""
collection = ""
field = ""
parser = argparse.ArgumentParser(description='Укажи параметры MongoDB')
parser.add_argument('-o', '--host', type=str, default=host, help='Адрес сервера MongoDB')
parser.add_argument('-p', '--port', type=int, default=port, help='Порт сервера MongoDB')
parser.add_argument('-u', '--user', type=str, default=user, help='Пользователь')
parser.add_argument('-w', '--password', type=str, default=password, help='Пароль пользователя')
parser.add_argument('-a', '--dba', type=str, default=dbaname, help='БД авторизации пользователя')
parser.add_argument('-d', '--database', type=str, default=dbname, help='Имя БД')
parser.add_argument('-c', '--collection', type=str, default=collection, help='Коллекция')
parser.add_argument('-f', '--field', type=str, default=field, help='Поле документа')
parser.add_argument('-s', '--simple', type=int, default=None, help='Упрощенное подключение')
args = parser.parse_args()
if args.field:
client = MongoClient(f'mongodb://{urllib.parse.quote_plus(args.user)}:{urllib.parse.quote_plus(args.password)}@{args.host}:{args.port}/{args.dba}')
if args.simple is not None:
client = MongoClient(f'mongodb://{host}:{port}/{args.dba}')
db = client[args.database]
collection = db[args.collection]
result = collection.count_documents({"$or": [{args.field: ""}, {args.field: None}, {args.field: {"$exists": False}}]})
# Вывод результата в JSON
response = {"count": result}
response_json = json.dumps(response)
print(response_json)
UserParameter=fields.hierarchy[*],/usr/bin/python3.6 /sy/scripts/zabbix/check_field_hierarchy.py -o $1 -p $2 -u $3 -w $4 -a $5 -d $6 -c $7 -f1 $8 -f2 $9
/sy/scripts/zabbix/check_field_hierarchy.py
from pymongo import MongoClient
import urllib.parse
import re
from bson.son import SON
import argparse
import json
# Подключение к MongoDB
host = ""
port = ""
user = ""
password = ""
dbaname = ''
dbname = ""
collection = ""
field1 = ""
field2 = ""
parser = argparse.ArgumentParser(description='Укажи параметры MongoDB')
parser.add_argument('-o', '--host', type=str, default=host, help='Адрес сервера MongoDB')
parser.add_argument('-p', '--port', type=int, default=port, help='Порт сервера MongoDB')
parser.add_argument('-u', '--user', type=str, default=user, help='Пользователь')
parser.add_argument('-w', '--password', type=str, default=password, help='Пароль пользователя')
parser.add_argument('-a', '--dba', type=str, default=dbaname, help='БД авторизации пользователя')
parser.add_argument('-d', '--database', type=str, default=dbname, help='Имя БД')
parser.add_argument('-c', '--collection', type=str, default=collection, help='Коллекция')
parser.add_argument('-f1', '--field1', type=str, default=field1, help='Поле документа, которое проверяется на совпадение со вторым полем')
parser.add_argument('-f2', '--field2', type=str, default=field2, help='Поле документа, которое проверяется на совпадение с первым полем')
#parser.add_argument('-s', '--simple', type=int, default=None, help='Упрощенное подключение')
args = parser.parse_args()
if args.field1 and args.field2:
client = MongoClient(
f'mongodb://{urllib.parse.quote_plus(args.user)}:{urllib.parse.quote_plus(args.password)}@{args.host}:{args.port}/{args.dba}')
# if args.simple is not None:
# client = MongoClient(f'mongodb://{host}:{port}/{args.dba}')
db = client[args.database]
collection = db[args.collection]
result = list(collection.find({"enabled": True,
args.field1: {"$exists": True},
args.field1: {"$nin": ["", None, []]},
args.field2: {"$exists": True},
args.field2: {"$nin": ["", None, []]},
"$where": f'this.{args.field1} == this.{args.field2}[0].site'},
{"host": 1, args.field1: 1, args.field2: 1, "updatedAt": 1}))
arc = []
n = 0
if result:
for doc in result:
n += 1
arc.append({"_id": str(doc["_id"]), "host": doc["host"], args.field1: doc[args.field1], args.field2: doc[args.field2], "updatedAt": str(doc["updatedAt"])})
arc[0]["count"] = n
else:
arc.append({"count": 0})
response_json = json.dumps(arc)
print(response_json)
UserParameter=fields.idrecursion[*],/usr/bin/python3.6 /sy/scripts/zabbix/check_field_id_infinite-recursion.py -o $1 -p $2 -u $3 -w $4 -a $5 -d $6 -c $7
/sy/scripts/zabbix/check_field_id_infinite-recursion.py
# -*- coding: utf-8 -*-
from pymongo import MongoClient, errors
import urllib.parse
from bson.objectid import ObjectId
import argparse
import json
# Подключение к MongoDB
host = ""
port = ""
user = ""
password = ""
dbaname = ''
dbname = ""
collection = ""
maxSevSelDelay = 3
all_arr = []
child_arr = []
arc = []
parser = argparse.ArgumentParser(description='Укажи параметры MongoDB')
parser.add_argument('-o', '--host', type=str, default=host, help='Адрес сервера MongoDB')
parser.add_argument('-p', '--port', type=int, default=port, help='Порт сервера MongoDB')
parser.add_argument('-u', '--user', type=str, default=user, help='Пользователь')
parser.add_argument('-w', '--password', type=str, default=password, help='Пароль пользователя')
parser.add_argument('-a', '--dba', type=str, default=dbaname, help='БД авторизации пользователя')
parser.add_argument('-d', '--database', type=str, default=dbname, help='Имя БД')
parser.add_argument('-c', '--collection', type=str, default=collection, help='Коллекция')
args = parser.parse_args()
if args.database and args.collection:
try:
client = MongoClient(f'mongodb://{urllib.parse.quote_plus(args.user)}:{urllib.parse.quote_plus(args.password)}@{args.host}:{args.port}/{args.dba}', serverSelectionTimeoutMS=maxSevSelDelay)
db = client[args.database]
dbnames = client.list_database_names()
idx = [x for x,y in enumerate(dbnames) if y == db.name]
if idx:
collection = db[args.collection]
client.server_info()
filter = {"name": collection.name}
list_of_collections = db.list_collection_names(filter = filter)
if list_of_collections:
n = 0
child_arr = list(collection.find({"parentId":{"$nin":["", None]}}, {"host":1, "parentId":1, "subsites":1, "updatedAt":1}))
all_arr = list(collection.find({"parentId":{"$nin":["", None]}}, {"host":1, "parentId":1, "subsites":1, "updatedAt":1}))
for parent in child_arr:
for x, y in enumerate(all_arr):
if (y["_id"] == ObjectId(parent["parentId"])) and (y["parentId"] == str(parent["_id"])):
n += 1
arc.append({"_id": str(parent["_id"]), "parentId": str(parent["parentId"]), "host": str(parent["host"])})
break
else:
found = False
if len(arc) > 0:
arc[0]["count"] = n
else:
arc.append({"count": 0})
response_json = json.dumps(arc)
print(response_json)
else:
print("Collection is absent")
else:
print("Database is absent")
except errors.ServerSelectionTimeoutError as err:
print(err)
MongoDB Query by Zabbix agent
sy-jenkins
sy-live
sy-mongo
Высылается количество проблемных записей count в БД. Также в новой конфигурации высылаются сами проблемные записи в количестве до 100, чтобы можно было сразу охватить взглядом проблему. count > 0 and list <> "" Warning: MongoDB Query: Query not empty count = 0 and list <> "" Recovery
StratyukI
LevinN
sy-live
sy-jenkins
sy-mongo
/etc/zabbix/zabbix_agent2.d/mongodb_query.conf
UserParameter=mongodb.query.info[*],/sy3/scripts/zabbix/mongodb_run.sh -H $1 -P $2 -u $3 -p $4 -a $5 -f $6
sy-live /sy/scripts/zabbix/mongodb_run.sh sy-jenkins /sy3/scripts/zabbix/mongodb_run.sh sy-mongo /etc/zabbix/zabbix_agent2.d/scripts/mongodb_run.sh
#!/bin/bash
while getopts "H:P:u:p:a:f:" opt; do
case ${opt} in
H ) HOST=$OPTARG ;;
P ) PORT=$OPTARG ;;
u ) USER=$OPTARG ;;
p ) PASS=$OPTARG ;;
a ) AUTH_DB=$OPTARG ;;
f ) QUERY_FILE=$OPTARG ;;
\? ) echo "Usage: $0 -H host -P port -u user -p password -a auth_db -f query_file"; exit 1 ;;
esac
done
if [ -z "$QUERY_FILE" ]; then
echo "Укажи файл с запросами через -f"
exit 1
fi
if ! command -v mongosh >/dev/null 2>&1; then
echo "Ошибка: mongosh не найден"
exit 1
fi
echo "["
first=1
query_name=""
while IFS= read -r line || [ -n "$line" ]; do
[ -z "$line" ] && continue
# Если строка начинается с "#", это название запроса
if [[ "$line" == \#* ]]; then
line="${line#\# }" # Убираем символ '#' и пробел
line=$(echo -E "$line" | sed 's/^[ \t]*//;s/[ \t]*$//')
query_name="$line"
read -r query_body || break # Читаем следующую строку как тело запроса
fi
# Получить count
count=$(mongosh --quiet \
--host "$HOST" \
--port "$PORT" \
--username "$USER" \
--password "$PASS" \
--authenticationDatabase "$AUTH_DB" \
--eval "$query_body.toArray().length" 2>/dev/null | tail -n 1)
# Получить список результатов как строк в формате key1:val1:key2:val2
list_result=$(mongosh --quiet \
--host "$HOST" \
--port "$PORT" \
--username "$USER" \
--password "$PASS" \
--authenticationDatabase "$AUTH_DB" \
--eval "($query_body.toArray()).slice(0, 100).map(doc => Object.entries(doc).filter(([k]) => k !== '_id').map(([k,v]) => v).join(':')).join('\n')")
# <<< Безопасно заэкранируем для JSON >>>
query_json=$(printf '%s' "$query_name" | jq -Rs .)
list_json=$(printf '%s' "$list_result" | jq -Rs .)
# Если нужно хранить list как МАССИВ строк:
# list_json=$(printf '%s' "$list_result" | jq -R -s -c 'split("\n") | map(select(length>0))')
# Печатаем блок JSON
if [ $first -eq 1 ]; then
first=0
else
echo ","
fi
echo "{"
echo " \"query\": $query_json,"
echo " \"count\": $count,"
echo " \"list\": $list_json"
echo "}"
# Очистка
query_name=""
done < "$QUERY_FILE"
echo "]"
sy-live /etc/zabbix/zabbix_agent2.d/mongodb_query.conf sy-jenkins /sy3/scripts/zabbix/mongodb_queries.txt sy-mongo /etc/zabbix/zabbix_agent2.d/scripts/mongodb_queries.txt
Отделил логику от рабочего скрипта. Сделал универсальный скрипт, чтобы можно было кидать команды js для mongodb без существенного переделывания (забывается потом). Все сделано по аналогии со скриптом проверок MySQL. Есть скрипт и файл команд. В файле команд, т.к. вывод в json и нужна универсальность, добавлено название для данных(действия) типа:
# Hierarchy
db.getSiblingDB("isa_live").page__sites.find({"enabled":true, "parentId":{$exists: true}, "parentId":{$nin:["",null,[]]}, "subsites":{"$exists": true}, "subsites":{$nin:["",null,[]]}, $where: "this.parentId == this.subsites[0].site"}, {"host":1,"parentId":1,"subsites":1,"updatedAt":1})
db.getSiblingDB("isa_live").page__sites.find({"enabled":true, "parentId":{$exists: true}, "parentId":{$nin:["",null,[]]}, "subsites":{"$exists": true}, "subsites":{$nin:["",null,[]]}, $where: "this.parentId == this.subsites[0].site"}, {"host":1,"parentId":1,"subsites":1,"updatedAt":1})
db.getSiblingDB("isa_live").vehicles.find({$or:[{"idSimple":""},{"idSimple":null},{"idSimple":{$exists:false}}]})
db.getSiblingDB("isa_live").page__sites.find({$or:[{"settings.idSimple":""},{"settings.idSimple":null},{"settings.idSimple":{$exists:false}}]})
db.getSiblingDB("isa_live").page__sites.find({$or:[{"host":""},{"host":null},{"host":{$exists:false}}]})
db.getSiblingDB("isa_live").vehicles.aggregate([ { $group: {_id: "$idSimple", count: { $sum: 1 }} }, { $match: {_id: { $ne: null }, count: { $gt: 1 }} }, { $project: {_id: 0, value: "$_id", count: 1} }, { $sort: { count: -1 } } ])
db.getSiblingDB("isa_live").page__sites.aggregate([ { $group: {_id: "$settings.idSimple", "count": {$sum: 1}} }, { $match: {_id: { $ne: null }, count: { $gt: 1 }} }, { $project: {_id: 0, "value": "$_id", "count": 1} }, { $sort: { count: -1 } } ])
db.getSiblingDB("isa_live").page__sites.aggregate([ { $group: {_id: "$host", "count": {$sum: 1}} }, { $match: {_id: { $ne: "" }, count: { $gt: 1 }} }, { $project: {_id: 0, "value": "$_id", "count": 1} }, { $sort: { count: -1 } } ])
db.getSiblingDB("isa_sy3").page__sites.aggregate([ { $match: { parentId: { $nin: ["", null] } } }, { $lookup: {from: "page__sites", let: { parentIdStr: "$parentId", childId: "$_id" }, pipeline: [ { $match: { $expr: { $and: [ { $eq: ["$_id", { $convert: { input: "$$parentIdStr", to: "objectId", onError: null, onNull: null } }] }, { $eq: ["$parentId", { $toString: "$$childId" }] } ]}}}], as: "mutual"} }, { $match: { mutual: { $ne: [] } } }, { $project: {_id: 1, parentId: 1, child_id: { $arrayElemAt: ["$mutual._id", 0] }, child_parentId: { $arrayElemAt: ["$mutual.parentId", 0] } } } ])
db.getSiblingDB("sy_purchase_assist").ads.aggregate([{ $lookup: { from: "market_report_v2", localField: "_id", foreignField: "ad", as: "market_match" } }, { $match: { date_finish: { $gt: new Date() }, "specs.mileage": { $lt: 120000 }, $or: [{market_match: { $size: 0 }}, {"market_match.report.status": { $ne: 3 }}] } }, { $addFields: { hours_passed: { $round: { $divide: [{ $subtract: [new Date(), "$created_at"] }, 3600000] } }, market_report_status: { $cond: { if: { $eq: [{ $size: "$market_match" }, 0] }, then: "NO_REPORT", else: { $arrayElemAt: ["$market_match.report.status", 0] }}}} }, { $project: { _id: 1, created_at: 1, date_finish: 1, provider: 1, stock: 1, "specs.mileage": 1, hours_passed: 1, market_report_status: 1, external_url: 1 } }, { $sort: { hours_passed: 1 } }])
MR2: No market-report in 6h (Warning: ads без market_report > 6 часов) Проверка существования спарсенного маркет-репорта
db.getSiblingDB("sy_purchase_assist").ads.find({
date_finish: {$gt: new Date()},
"specs.mileage": {$lt: 120000},
_id: {$nin: db.getSiblingDB("sy_purchase_assist").market_report_v2.distinct("ad")},
created_at: {$lt: new Date(Date.now() - 6 * 60 * 60 * 1000)}
}).map(function(doc) {
return {
id: doc._id.toString(),
created_at: doc.created_at,
hours_passed: Math.round((new Date() - doc.created_at) / 3600000),
provider: doc.provider,
external_url: doc.external_url,
"specs.mileage": doc.specs.mileage,
market_report_status: "NO_REPORT"
}
})
MR2: No market-report in 12h (Critical: ads без market_report > 12 часов) Проверка существования спарсенного маркет-репорта
db.getSiblingDB("sy_purchase_assist").ads.find({
date_finish: {$gt: new Date()},
"specs.mileage": {$lt: 120000},
_id: {$nin: db.getSiblingDB("sy_purchase_assist").market_report_v2.distinct("ad")},
created_at: {$lt: new Date(Date.now() - 12 * 60 * 60 * 1000)}
}).map(function(doc) {
return {
id: doc._id.toString(),
created_at: doc.created_at,
hours_passed: Math.round((new Date() - doc.created_at) / 3600000),
provider: doc.provider,
external_url: doc.external_url,
"specs.mileage": doc.specs.mileage,
market_report_status: "NO_REPORT"
}
})
MR2: Error in market-report in 6h (Warning: ads с report.status ≠ 3 > 6 часов) Проверка того, что маркет-репорт спарсен без ошибок
db.getSiblingDB("sy_purchase_assist").ads.find({
date_finish: {$gt: new Date()},
"specs.mileage": {$lt: 120000},
_id: {$in: db.getSiblingDB("sy_purchase_assist").market_report_v2.distinct("ad", {"report.status": {$ne: 3}})},
created_at: {$lt: new Date(Date.now() - 6 * 60 * 60 * 1000)}
}, {
_id: 1, created_at: 1, date_finish: 1, provider: 1, stock: 1, "specs.mileage": 1, external_url: 1
}).sort({created_at: 1}).map(function(doc) {
var reportStatus = db.getSiblingDB("sy_purchase_assist").market_report_v2.findOne({ad: doc._id}).report.status;
return {
id: doc._id.toString(),
created_at: doc.created_at,
date_finish: doc.date_finish,
hours_passed: Math.round((new Date() - doc.created_at) / 3600000),
provider: doc.provider,
stock: doc.stock,
"specs.mileage": doc.specs.mileage,
market_report_status: reportStatus,
external_url: doc.external_url
}
})
MR2: Error in market-report in 12h (Critical: ads с report.status ≠ 3 > 12 часов) Проверка того, что маркет-репорт спарсен без ошибок
db.getSiblingDB("sy_purchase_assist").ads.find({
date_finish: {$gt: new Date()},
"specs.mileage": {$lt: 120000},
_id: {$in: db.getSiblingDB("sy_purchase_assist").market_report_v2.distinct("ad", {"report.status": {$ne: 3}})},
created_at: {$lt: new Date(Date.now() - 12 * 60 * 60 * 1000)}
}, {
_id: 1, created_at: 1, date_finish: 1, provider: 1, stock: 1, "specs.mileage": 1, external_url: 1
}).sort({created_at: 1}).map(function(doc) {
var reportStatus = db.getSiblingDB("sy_purchase_assist").market_report_v2.findOne({ad: doc._id}).report.status;
return {
id: doc._id.toString(),
created_at: doc.created_at,
date_finish: doc.date_finish,
hours_passed: Math.round((new Date() - doc.created_at) / 3600000),
provider: doc.provider,
stock: doc.stock,
"specs.mileage": doc.specs.mileage,
market_report_status: reportStatus,
external_url: doc.external_url
}
})
Особенности оптимизированных запросов:
$nin и $in вместо медленных $lookup операцийObjectId в строки через .toString()_id в id для совместимости с bash-скриптамиMongoDB node by Zabbix agent 2 for symfio
Nginx by Zabbix agent
RabbitMQ node by HTTP for Rabbit1 cluster
Systemd by Zabbix agent 2
`sy-zabbix`
`sy-dev`
`sy-live`
`sy-rabbitmq`
`sa-live`
`sy-redmine`
`sy-harbor`
`sy-jenkins`
`sy-mail`
`sy-mongo`
`sy-lnx-parser1`
`sy-lnx-parser2`
`sy-lnx-parser3`
| Проверка | Что проверяет |
|---|---|
| Connection to MongoDB is unavailable | Проверяет коннект с именем и паролем |
| Nginx: Service response time is too high | Ping по порту в течении 5 минут был дольше 10 секунд |
| RabbitMQ: Service response time is too high | Ping по порту в течении 5 минут был дольше 10 секунд |
Проверяет состояние сервиса, если сервис упал, то следует Warning
Kirill
StratyukI
RabbitMQ node by HTTP for Rabbit1 cluster - измененный, фильтруются очереди и хосты, которые не нужно мониторить
sy-rabbitmq
Проверяет есть ли консьюмеры в очереди. Если ни одного нет, то пытается рестартить, если консьюмеры не подключились, то ворнинг.
| Очередь | Шаги | Время | Кому |
|---|---|---|---|
[dealer_tenant.prod_de][cache] |
1ч20мин | StratyukI | |
[dealer_tenant.prod_de][global_process] |
1ч00мин | StratyukI | |
[dealer_tenant.prod_de][invalidation] |
1ч00мин | StratyukI | |
[dealer_tenant.prod_de][site] |
1ч00мин | StratyukI | |
[parser][cars_co_za] |
рестарты консьюмера |
1ч20мин | BiletskiyS StratyukI |
[symfio.prod][auction_dms] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][isa-lead-answer] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][parser_dealer_review] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][parser_mobile_de] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3cache] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3clickhouse-writer] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3cloud] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3documents] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3email-archive] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3exporter] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3google-calendar] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3image-proxy] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3import-action] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3importer] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3lead-workflow] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3list-massmailer] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3logger] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3mailer] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3massmailer] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3media-processor] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3mercure-pusher] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3new_frontend] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3push-notification] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3retrieve_ads] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3s3] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3seo-vehicle] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3social-media] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3statistic_importer] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3todo] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3vehicle-delete] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3vehicle-market-report] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio.prod][sf-3vehicle-rating] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][autotrader_co_za] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][cars_co_za] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][isa-lead-answer] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3cache] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3clickhouse-writer] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3documents] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3email-archive] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3exporter] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3google-calendar] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3image-proxy] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3import-action] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3importer] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3lead-workflow] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3list-massmailer] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3logger] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3mailer] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3massmailer] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3media-processor] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3push-notification] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3s3] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3social-media] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3vehicle-decoder] |
рестарты консьюмера |
1ч00мин | StratyukI |
[symfio_za.prod][sf-3vehicle-delete] |
рестарты консьюмера |
1ч00мин | StratyukI |
Мониторятся очереди, у которых есть сообщения больше определенного порога. Если очередь при этом увеличивается или не уменьшается в процессе времени (тренд не на уменьшение), то после попыток рестарта очереди шлется ворнинг. Для проверки используется функция monoinc в режиме weak рассчитывает на основании макроса {$RABBITMQ.MESSAGES.MAX.TTIME}, по-умолчанию, #10 (10 последних значений), что по-умолчанию, означает 10 минут.
| Очередь | Шаги | Время | Кому |
|---|---|---|---|
[dealer_tenant.prod_de][global_process] |
2ч10мин | Backend StratyukI |
|
[dealer_tenant.prod_de][cache] |
2ч10мин | Backend StratyukI |
|
[dealer_tenant.prod_de][invalidation] |
2ч10мин | Backend StratyukI |
|
[dealer_tenant.prod_de][site] |
2ч10мин | Backend StratyukI |
|
[parser][cars_co_za] |
рестарты консьюмера |
2ч20мин | Backend StratyukI |
[parser][mobile_parser_reviews] |
рестарты консьюмера |
5ч08мин | BiletskiyS BlinovA StratyukI |
[parser][mobile_parse_hosts] |
рестарты консьюмера |
5ч08мин | BiletskiyS BlinovA StratyukI |
[parser][mobile_parse_items] |
рестарты консьюмера |
5ч08мин | BiletskiyS BlinovA StratyukI |
[symfio.prod][parser_mobile_de] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][auction_dms] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][isa-lead-answer] |
рестарты консьюмера |
2ч10мин | Backend BlinovA StratyukI |
[symfio.prod][parser_dealer_review] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3cache] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3clickhouse-writer] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3cloud] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3documents] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3email-archive] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3exporter] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3google-calendar] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3image-proxy] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3import-action] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3importer] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3lead-workflow] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3list-massmailer] |
рестарты консьюмера |
2ч10мин | Backend BlinovA StratyukI |
[symfio.prod][sf-3logger] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3mailer] |
рестарты консьюмера |
2ч10мин | Backend BlinovA StratyukI |
[symfio.prod][sf-3massmailer] |
рестарты консьюмера |
2ч10мин | Backend BlinovA StratyukI |
[symfio.prod][sf-3media-processor] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3mercure-pusher] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3new_frontend] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3push-notification] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3retrieve_ads] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3s3] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3seo-vehicle] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3social-media] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3statistic_importer] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3todo] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3vehicle-delete] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3vehicle-market-report] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio.prod][sf-3vehicle-rating] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][autotrader_co_za] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][cars_co_za] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][isa-lead-answer] |
рестарты консьюмера |
2ч10мин | Backend Kirill StratyukI |
[symfio_za.prod][sf-3cache] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3clickhouse-writer] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3documents] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3email-archive] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3exporter] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3google-calendar] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3image-proxy] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3import-action] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3importer] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3lead-workflow] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3list-massmailer] |
рестарты консьюмера |
2ч10мин | Backend Kirill StratyukI |
[symfio_za.prod][sf-3logger] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3mailer] |
рестарты консьюмера |
2ч10мин | Backend Kirill StratyukI |
[symfio_za.prod][sf-3massmailer] |
рестарты консьюмера |
2ч10мин | Backend Kirill StratyukI |
[symfio_za.prod][sf-3media-processor] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3push-notification] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3s3] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3social-media] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3vehicle-delete] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
[symfio_za.prod][sf-3vehicle-decoder] |
рестарты консьюмера |
2ч10мин | Backend StratyukI |
При отсутствии delayed-очереди ее пересоздают заново. В случае проблем, идет ворнинг.
| Очередь | Шаги | Время | Кому |
|---|---|---|---|
[symfio.prod][sf-3clickhouse-writer-delayed] |
0ч30мин | Backend StratyukI |
|
[symfio.prod][sf-3exporter_delayed] |
Пересоздание очереди |
0ч30мин | Backend StratyukI |
[symfio.prod][sf-3image-proxy-delayed] |
Пересоздание очереди |
0ч30мин | Backend StratyukI |
[symfio_za.prod][sf-3clickhouse-writer-delayed] |
0ч30мин | Backend StratyukI |
MySQL Query by Zabbix agent
sy-jenkins
Отрабатываются последовательно запросы к MySQL-базам. Результаты компонуются в JSON.
sy-jenkins
/etc/zabbix/zabbix_agent2.d/mongodb_query.conf
UserParameter=mysql.fields.query[*],/usr/local/bin/python3.9 /etc/zabbix/scripts/mysql_runq.py -o $1 -p $2 -u $3 -w $4 -f $5
/etc/zabbix/scripts/task.sql
SELECT UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(MAX(parse_last)) AS parse_hosts_t FROM mobile_parser.hosts
SELECT UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(MAX(parsed_at)) AS parse_items_t FROM mobile_parser.items
SELECT COUNT(*) AS body_type_count FROM mmcodes.body_type WHERE 1
SELECT COUNT(*) AS mmcodes_count FROM mmcodes.mmcodes WHERE 1
SELECT COUNT(*) AS options_name_count FROM mmcodes.options_name WHERE
SELECT COUNT(*) AS options_price_count FROM mmcodes.options_price WHERE 1
SELECT COUNT(*) AS vehicle_options_count FROM mmcodes.vehicle_options WHERE 1
SELECT COUNT(*) AS vehicle_options_new_count FROM mmcodes.vehicle_options_new WHERE 1
SELECT COUNT(*) AS vehicle_type_count FROM mmcodes.vehicle_type WHERE 1
SELECT COUNT(*) AS mmcodes_logs_last_5_days FROM mmcodes.logs WHERE data >= NOW() - INTERVAL 5 DAY
SELECT COUNT(id) AS auctions_items_count, id, url FROM athloncarplaza_dev.auctions_items WHERE parse_time > date_sub(now(),INTERVAL 1 WEEK)
SELECT TIMESTAMPDIFF(HOUR, MAX(parsed_at), NOW()) AS hours_since_last_report FROM market_report_v2.reports WHERE parsed_at IS NOT NULL
SELECT TIMESTAMPDIFF(HOUR, MAX(updated_at), NOW()) AS hours_since_last_update FROM market_report_v2.requests
/etc/zabbix/scripts/mysql_runq.py
import mysql.connector
import urllib.parse
import argparse
import json
# Подключение к MySQL
host = ""
port = ""
user = ""
password = ""
dbname = "mysql"
#table = ""
file = ""
#field = ""
parser = argparse.ArgumentParser(description='Укажи параметры MySQL')
parser.add_argument('-o', '--host', type=str, default=host, help='Адрес сервера MySQL')
parser.add_argument('-p', '--port', type=int, default=port, help='Порт сервера MySQL')
parser.add_argument('-u', '--user', type=str, default=user, help='Пользователь')
parser.add_argument('-w', '--password', type=str, default=password, help='Пароль пользователя')
parser.add_argument('-d', '--database', type=str, default=dbname, help='Имя БД')
#parser.add_argument('-t', '--table', type=str, default=table, help='Таблица')
parser.add_argument('-f', '--file', type=str, default=file, help='Файл запроса .sql')
args = parser.parse_args()
result = {}
if args.file:
with open(args.file, 'r') as f:
queries = f.readlines()
#db_query = lines
#print(lines)
try:
with mysql.connector.connect(
host=args.host,
port=args.port,
database = args.database,
user=urllib.parse.quote_plus(args.user),
password=urllib.parse.quote_plus(args.password)
) as connection:
for query in queries:
with connection.cursor() as cursor:
cursor.execute(query)
resn = dict(zip(cursor.column_names, cursor.fetchone()))
result.update(resn)
except mysql.connector.Error as e:
print(e)
arc_json = json.dumps(result)
print(arc_json)
SSL certificate by Zabbix agent 2
RabbitMQ node by HTTP for Rabbit1 cluster
sy-redmine
sy-jenkins
{$SSLCERT.EXPIRY.WARN} Если меньше, то Warning
{$SSLCERT.WEBSITE.FILE} Путь к файлу проверяемых доменов по сертификатам
Kirill
StratyukI
sy-redmine
/etc/zabbix/zabbix_agent2.d/get_cert_info.conf
UserParameter=sslcert.info[*],/usr/bin/python /etc/zabbix/scripts/get_cert_info.py $1
sy-redmine
/etc/zabbix/scripts/get_cert_info.py
import sys
import ssl, socket
import json
from datetime import datetime
arg = sys.argv[1:][0]
arc = []
with open(arg, 'r') as f:
hostnames = f.read().splitlines()
now = datetime.utcnow().timestamp()
for hostname in hostnames:
ctx = ssl.create_default_context()
s = ctx.wrap_socket(socket.socket(), server_hostname=hostname)
s.connect((hostname, 443))
cert = s.getpeercert()
subject = dict(x[0] for x in cert['subject'])
issuer = dict(x[0] for x in cert['issuer'])
notbefore = ssl.cert_time_to_seconds(cert['notBefore'])
arc.append({'name': subject['commonName'], 'country': issuer['countryName'], 'orgname': issuer['organizationName'], 'cn': issuer['commonName'],
'version': cert['version'], 'snum': cert['serialNumber'], 'datebef': cert['notBefore'], 'timestampbef': ssl.cert_time_to_seconds(cert['notBefore']),
'dateaft': cert['notAfter'], 'timestampaft': ssl.cert_time_to_seconds(cert['notAfter']), 'daystampdiff': round(int(ssl.cert_time_to_seconds(cert['notAfter']) - now)/86400),
'altname': cert['subjectAltName']})
arc_json = json.dumps(arc)
print (arc_json)
/etc/zabbix/scripts/cert.conf
symfio.de
zabbix.symfio.net
jenkins.symfio.net
task.symfio.net
hub.symfio.net
kessler-haag-auto.de
autoseredin.de
dev-server.symfio.net
rancher.symfio.de
rabbitmq.symfio.net
Zabbix server health
sy-zabbix
Value cache в Zabbix работает в режиме low memory mode, это означает, что серверу не хватает памяти для хранения последних значений метрик. В итоге он начинает лезть за значениями метрик в БД, начинаются существенные тормоза, также могут пропускаться значения поступающих метрик.Kirill
StratyukI
Детект распределённого SSH-брутфорса по логу. Добавлено в оба варианта шаблона, висит на ~30 хостах парка (см. Redmine #16341, #16418).
Linux by Zabbix agent
Linux by Zabbix agent active
| Item | Ключ | Тип |
|---|---|---|
| SSH: Invalid user enumeration | log[{SSH.LOG.PATH},{SSH.ENUMERATION.PATTERN}] | Zabbix agent active (log) |
| SSH: Failed password attempts | log[{SSH.LOG.PATH},{SSH.FAILED.PATTERN}] | Zabbix agent active (log) |
| SSH: User enumeration rate in 5m | ssh.enumeration.rate | Calculated |
| SSH: Failed authentication rate in 5m | ssh.failed.rate | Calculated |
Два слоя. Связь между ними сделана НЕ через dependent-item, а через ссылку по ключу в формуле:
log[/var/log/secure,Invalid user] — агент тейлит лог, regex делает САМ агент, каждая совпавшая строка = одно значение в истории. Сам он ничего не считает.ssh.enumeration.rate = count(//log[{$SSH.LOG.PATH},{$SSH.ENUMERATION.PATTERN}], 5m). НЕ перечитывает файл и НЕ повторяет regex. //log[...] — это ссылка на тот log-item по его полному ключу (// = тот же хост). Ключ того item'а буквально и есть log[/var/log/secure,Invalid user], поэтому паттерн «торчит» в формуле как часть ИМЕНИ, а не как повторный матчинг. count() считает, сколько значений log-item накопил за 5m.Почему так, а не наглядное count(ssh.log.enum): в Zabbix item адресуется полным ключом, а у log-мониторинга путь+паттерн вшиты в ключ — отсюда нечитаемое count(//log[...]). Почему calculated, а не dependent: dependent трансформирует каждое значение (1→1) и не умеет агрегировать по окну; «сколько строк за 5m» = только calculated. Удалишь/переименуешь log-item — calculated станет unsupported (master_itemid=NULL, связь держится на совпадении ключа).
| Триггер | Severity | Выражение |
|---|---|---|
| SSH: High rate of user enumeration in 5m | Warning | last(ssh.enumeration.rate) > {$SSH.ENUMERATION.THRESHOLD} and nodata(ssh.enumeration.rate,5m)=0 |
| SSH: High number of failed authentication attempts in 5m | Average | last(ssh.failed.rate) > {$SSH.FAILED.THRESHOLD} and nodata(ssh.failed.rate,5m)=0 |
Вторая часть nodata(self,5m)=0 — guard «есть свежие данные» (не «failed=0»): чтобы триггер не сработал по застрявшему last(), когда item перестал получать данные.
| Макрос | Значение |
|---|---|
| Invalid user | |
| 80 | |
| Failed (password|none) | |
| 50 | |
| 5m | |
| /var/log/secure |
Агент должен читать {$SSH.LOG.PATH} — пользователь zabbix в группе adm. На RKE2-нодах путь переопределён хостовым макросом на /var/log/auth.log.