Сетевые аномалии и системные процессы
Сетевые аномалии и системные процессы
Системное администрирование обеспечивает стабильность базовой операционной среды, на которой функционируют все прикладные сервисы. Операционная система, сетевой стек, планировщик задач и файловая система образуют фундамент, от надёжности которого зависит работоспособность вышестоящих компонентов. Скрытые дефекты на этом уровне проявляются непредсказуемо и затрагивают множество приложений одновременно.
Сетевая аномалия — отклонение в работе сетевых протоколов и служб, нарушающее штатное взаимодействие между компонентами распределённой системы.
Системный процесс — фоновая задача, выполняемая операционной системой или пользовательскими планировщиками для обеспечения работы сервисов и выполнения периодических операций.
Системный администратор рассматривает инфраструктурные проблемы как результат взаимодействия множества независимых компонентов. Каждый компонент имеет собственные часы, собственные таблицы маршрутизации, собственные ограничения ресурсов. Слаженная работа системы достигается через тщательную настройку и постоянный мониторинг каждого элемента.
Смещение времени и синхронизация часов
Смещение времени — расхождение показаний системных часов на разных узлах распределённой системы, приводящее к нарушению логики приложений, зависящих от временных меток.
Каждый физический сервер оснащён аппаратным генератором тактовых импульсов на основе кварцевого резонатора. Кристаллы имеют производственный разброс характеристик, температурную зависимость частоты и эффект старения. Разница хода в несколько миллисекунд за секунду накапливается до секунд за сутки и до минут за месяц.
Последствия временного дрейфа
Современные распределённые системы критически зависят от точности времени:
Аутентификация и токены. JWT-токены содержат временные метки iat (issued at) и exp (expiration). Сервис валидации сравнивает эти метки со своими часами. Расхождение приводит к отклонению валидных токенов или принятию просроченных.
TTL и кэширование. Записи в кэшах (Redis, Memcached) и DNS имеют время жизни. Рассинхронизация часов вызывает преждевременное удаление актуальных данных или длительное хранение устаревших.
Корреляция событий. Логи с разных серверов объединяются по временным меткам для восстановления последовательности событий. Дрейф часов меняет порядок событий и затрудняет диагностику инцидентов.
Выборы лидера. Алгоритмы консенсуса (Raft, Paxos) используют таймауты для определения живых узлов и инициирования перевыборов. Нестабильные часы вызывают бесконечные циклы перевыборов.
Идемпотентность. Ключи идемпотентности часто включают временное окно. Расхождение часов между сервисами делает ключи неэффективными.
Базы данных. Временные метки записей используются для репликации, разрешения конфликтов и аудита. Рассинхронизация нарушает порядок применения изменений.
Протокол NTP и его реализация
NTP (Network Time Protocol) — протокол синхронизации системных часов через сеть с эталонными источниками времени. Протокол компенсирует сетевые задержки и обеспечивает точность в пределах миллисекунд при стабильном сетевом соединении.
Современные дистрибутивы Linux используют две основные реализации NTP:
Chrony — современная реализация, оптимизированная для нестабильных сетей и виртуальных машин:
# /etc/chrony.conf
# Эталонные серверы времени
server 0.pool.ntp.org iburst
server 1.pool.ntp.org iburst
server 2.pool.ntp.org iburst
server 3.pool.ntp.org iburst
# Локальные часы как резервный источник
local stratum 10
# Ограничение максимальной коррекции за один шаг
maxchange 1000 0 0
# Принудительная синхронизация при большом начальном расхождении
makestep 1.0 3
# Директория для хранения измерений
logdir /var/log/chrony
# Логирование статистики
log measurements statistics tracking
# Разрешение запросов от локальной сети
allow 10.0.0.0/8
Разбор параметров конфигурации:
serverзадаёт адрес NTP-сервера для синхронизации.iburstускоряет начальную синхронизацию серией из восьми быстрых запросов.local stratumопределяет уровень локальных часов как резервного источника при недоступности внешних серверов.maxchangeограничивает разовую коррекцию времени для предотвращения резких скачков.makestepразрешает мгновенную коррекцию при первых трёх измерениях с расхождением более одной секунды.allowразрешает другим узлам сети использовать данный сервер как источник времени.
systemd-timesyncd — легковесный SNTP-клиент, встроенный в systemd:
# /etc/systemd/timesyncd.conf
[Time]
NTP=0.pool.ntp.org 1.pool.ntp.org 2.pool.ntp.org 3.pool.ntp.org
FallbackNTP=ntp.ubuntu.com
RootDistanceMaxSec=5
PollIntervalMinSec=32
PollIntervalMaxSec=2048
Монотонные часы в приложениях
Монотонные часы — источник времени, гарантирующий постоянный ход вперёд независимо от корректировок системных часов через NTP.
Системные часы (CLOCK_REALTIME) могут идти назад при синхронизации с NTP-сервером, показывающим более раннее время. Монотонные часы (CLOCK_MONOTONIC) никогда не идут назад и идеально подходят для измерения интервалов времени.
import time
# Системные часы могут идти назад при коррекции NTP
wall_time_start = time.time()
time.sleep(1)
wall_time_end = time.time()
# wall_time_end может быть меньше wall_time_start!
# Монотонные часы гарантируют постоянный ход
monotonic_start = time.monotonic()
time.sleep(1)
monotonic_end = time.monotonic()
# monotonic_end всегда больше monotonic_start
duration = monotonic_end - monotonic_start
Разные языки программирования предоставляют доступ к монотонным часам:
| Язык | Функция монотонных часов | Функция системных часов |
|---|---|---|
| Python | time.monotonic() | time.time() |
| Java | System.nanoTime() | System.currentTimeMillis() |
| C# | Stopwatch.GetTimestamp() | DateTime.UtcNow |
| Go | time.Since(start) | time.Now() |
| JavaScript | performance.now() | Date.now() |
| C/C++ | clock_gettime(CLOCK_MONOTONIC) | clock_gettime(CLOCK_REALTIME) |
Логические часы Лэмпорта
Логические часы Лэмпорта — механизм установления причинно-следственного порядка событий в распределённой системе без использования физических часов.
Каждый узел поддерживает счётчик, увеличивающийся при каждом локальном событии. При отправке сообщения узел передаёт текущее значение счётчика. Получатель устанавливает свой счётчик в максимум из собственного значения и полученного, увеличенный на единицу.
class LamportClock:
"""Реализация логических часов Лэмпорта."""
def __init__(self):
self.time = 0
self.node_id = None
def tick(self) -> int:
"""Локальное событие."""
self.time += 1
return self.time
def send(self) -> tuple:
"""Отправка сообщения."""
self.time += 1
return (self.time, self.node_id)
def receive(self, message_time: int) -> int:
"""Получение сообщения."""
self.time = max(self.time, message_time) + 1
return self.time
def happened_before(self, event_a: tuple, event_b: tuple) -> bool:
"""Проверка причинно-следственной связи."""
time_a, node_a = event_a
time_b, node_b = event_b
if time_a < time_b:
return True
if time_a == time_b and node_a < node_b:
return True
return False
Векторные часы расширяют идею Лэмпорта, отслеживая знания каждого узла о состоянии других узлов. Это позволяет точно определять параллельные события, для которых причинно-следственная связь отсутствует.
Сбои DNS-резолвинга
Сбой DNS-резолвинга — невозможность преобразовать доменное имя в IP-адрес или получение устаревшего адреса из-за кэширования.
DNS-система обеспечивает преобразование человекочитаемых имён в сетевые адреса. Распределённая природа DNS, иерархическая структура и агрессивное кэширование создают уникальные проблемы для приложений.
Архитектура DNS и точки отказа
DNS-запрос проходит через несколько уровней:
- Локальный кэш приложения или библиотеки.
- Кэш операционной системы (systemd-resolved, dnsmasq).
- Локальный DNS-резолвер провайдера или корпоративной сети.
- Рекурсивные DNS-серверы.
- Авторитетные DNS-серверы домена.
- Корневые DNS-серверы.
Каждый уровень имеет собственные таймауты, политики кэширования и механизмы обработки ошибок. Проблема на любом уровне влияет на все приложения, использующие этот путь разрешения имён.
Источники проблем
Негативное кэширование. Ошибки разрешения имён кэшируются на срок, указанный в поле minimum SOA-записи зоны (обычно от 5 минут до нескольких часов). Временная недоступность DNS-сервера приводит к долгосрочным сбоям даже после восстановления сервиса.
Долгий TTL. Записи с большим временем жизни (TTL) кэшируются на всех уровнях, делая невозможным быстрое переключение трафика при аварийном переводе на резервный дата-центр.
Кэширование в приложении. HTTP-клиенты и пулы соединений часто резолвят имя один раз при создании и продолжают использовать полученный IP-адрес бесконечно, игнорируя изменения в DNS.
Отсутствие резервных серверов. Приложение или система используют единственный DNS-сервер, недоступность которого останавливает все сетевые операции.
IPv4/IPv6 dual-stack. Современные резолверы возвращают оба типа адресов. Приложение пытается подключиться к недоступному IPv6-адресу, тратя время на таймаут перед переключением на IPv4.
Round-robin записи. Домен имеет несколько A-записей для балансировки нагрузки. Клиент получает все адреса и должен самостоятельно реализовать failover.
Стратегии устойчивого резолвинга
Периодическое обновление адресов в пулах соединений:
// Apache HttpClient с периодическим обновлением DNS
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager(
30, TimeUnit.SECONDS // Время жизни соединения
);
// Настройка eviction для устаревших соединений
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.evictExpiredConnections()
.evictIdleConnections(60, TimeUnit.SECONDS)
.setRetryHandler((exception, executionCount, context) -> {
// Повторная попытка при UnknownHostException
if (exception instanceof UnknownHostException && executionCount < 3) {
// Принудительное обновление DNS-кэша
java.security.Security.setProperty(
"networkaddress.cache.ttl", "0"
);
return true;
}
return false;
})
.build();
Резервные DNS-резолверы на уровне приложения:
import dns.resolver
import dns.exception
from typing import List, Optional
class ResilientResolver:
"""Устойчивый DNS-резолвер с несколькими источниками."""
def __init__(self):
self.resolvers = []
# Основной резолвер - системный
primary = dns.resolver.Resolver()
primary.timeout = 2.0
primary.lifetime = 4.0
self.resolvers.append(primary)
# Резервные публичные DNS
for nameservers in [
['8.8.8.8', '8.8.4.4'], # Google Public DNS
['1.1.1.1', '1.0.0.1'], # Cloudflare DNS
['9.9.9.9', '149.112.112.112'] # Quad9 DNS
]:
resolver = dns.resolver.Resolver(configure=False)
resolver.nameservers = nameservers
resolver.timeout = 2.0
resolver.lifetime = 4.0
self.resolvers.append(resolver)
def resolve(self, hostname: str, rdtype: str = 'A') -> List[str]:
"""Разрешение имени с попытками через несколько серверов."""
last_exception = None
for resolver in self.resolvers:
try:
answers = resolver.resolve(hostname, rdtype)
return [rdata.to_text() for rdata in answers]
except dns.exception.DNSException as e:
last_exception = e
continue
raise ResolutionFailed(
f"Не удалось разрешить {hostname}: {last_exception}"
)
def resolve_with_cache(
self,
hostname: str,
cache_ttl: int = 300
) -> List[str]:
"""Разрешение с локальным кэшированием."""
cache_key = f"{hostname}:{self._cache_namespace}"
cached = self._cache.get(cache_key)
if cached and not cached.is_expired():
return cached.addresses
addresses = self.resolve(hostname)
self._cache.set(cache_key, addresses, ttl=cache_ttl)
return addresses
Предварительное разрешение для критических зависимостей:
package resolver
import (
"context"
"net"
"sync"
"time"
)
type CachedResolver struct {
cache map[string]*cacheEntry
mutex sync.RWMutex
resolver *net.Resolver
refreshInterval time.Duration
}
type cacheEntry struct {
addresses []string
expiresAt time.Time
refreshing bool
}
func NewCachedResolver(refreshInterval time.Duration) *CachedResolver {
return &CachedResolver{
cache: make(map[string]*cacheEntry),
resolver: &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{Timeout: 2 * time.Second}
return d.DialContext(ctx, network, address)
},
},
refreshInterval: refreshInterval,
}
}
func (r *CachedResolver) Resolve(ctx context.Context, host string) ([]string, error) {
r.mutex.RLock()
entry, exists := r.cache[host]
r.mutex.RUnlock()
if exists && time.Now().Before(entry.expiresAt) {
// Кэш актуален
return entry.addresses, nil
}
if exists && entry.refreshing {
// Другая горутина уже обновляет
return entry.addresses, nil
}
// Разрешение имени
addrs, err := r.resolver.LookupHost(ctx, host)
if err != nil {
if exists {
// Возврат устаревших данных при ошибке
return entry.addresses, nil
}
return nil, err
}
// Обновление кэша
r.mutex.Lock()
r.cache[host] = &cacheEntry{
addresses: addrs,
expiresAt: time.Now().Add(r.refreshInterval),
refreshing: false,
}
r.mutex.Unlock()
// Планирование фонового обновления
go r.backgroundRefresh(host)
return addrs, nil
}
func (r *CachedResolver) backgroundRefresh(host string) {
ticker := time.NewTicker(r.refreshInterval / 2)
defer ticker.Stop()
for range ticker.C {
ctx, cancel := context.WithTimeout(
context.Background(),
5*time.Second,
)
addrs, err := r.resolver.LookupHost(ctx, host)
cancel()
if err == nil {
r.mutex.Lock()
r.cache[host] = &cacheEntry{
addresses: addrs,
expiresAt: time.Now().Add(r.refreshInterval),
refreshing: false,
}
r.mutex.Unlock()
}
}
}
Мониторинг DNS-резолвинга
Системный администратор настраивает непрерывный мониторинг доступности DNS:
#!/bin/bash
# Мониторинг DNS-резолвинга
DOMAINS=(
"api.company.com"
"database.internal"
"cache.internal"
"external-payment-gateway.com"
)
DNS_SERVERS=(
"8.8.8.8"
"1.1.1.1"
"local-dns-01"
"local-dns-02"
)
for domain in "${DOMAINS[@]}"; do
for server in "${DNS_SERVERS[@]}"; do
start_time=$(date +%s%N)
if dig @"$server" "$domain" +short +time=2 +tries=1 > /dev/null 2>&1; then
end_time=$(date +%s%N)
duration_ms=$(( (end_time - start_time) / 1000000 ))
echo "dns_resolution_success{domain=\"$domain\",server=\"$server\"} 1"
echo "dns_resolution_duration_ms{domain=\"$domain\",server=\"$server\"} $duration_ms"
else
echo "dns_resolution_success{domain=\"$domain\",server=\"$server\"} 0"
echo "ALERT: DNS resolution failed for $domain via $server" | send_alert
fi
done
done
Зомби-процессы и управление процессами
Зомби-процесс — завершённый процесс, запись о котором сохраняется в таблице процессов до момента, пока родительский процесс не считает его код завершения.
Операционная система Linux поддерживает иерархию процессов, где каждый процесс имеет родителя. При завершении дочернего процесса ядро сохраняет минимальную информацию о нём: идентификатор процесса (PID), код завершения и статистику использования ресурсов. Эта информация предназначена для родительского процесса, который должен вызвать системный вызов wait() для её получения.
Механизм возникновения зомби
Жизненный цикл процесса включает несколько состояний:
R (Running) - процесс выполняется или ожидает выполнения
S (Sleeping) - процесс ожидает события (I/O, сигнал)
D (Disk sleep) - процесс ожидает I/O, не реагирует на сигналы
T (Stopped) - процесс остановлен сигналом
Z (Zombie) - процесс завершён, ожидает чтения статуса родителем
X (Dead) - процесс полностью удалён из таблицы
Переход в состояние зомби происходит следующим образом:
- Дочерний процесс вызывает
exit()или получает сигнал завершения. - Ядро освобождает все ресурсы процесса: память, файловые дескрипторы, сетевые сокеты.
- Ядро сохраняет структуру
task_structс кодом завершения и статистикой. - Ядро отправляет родителю сигнал
SIGCHLD. - Процесс переходит в состояние
Z(Zombie). - Родитель вызывает
wait()илиwaitpid()для получения статуса. - Ядро удаляет структуру процесса из таблицы.
Если родитель игнорирует сигнал SIGCHLD или завершается раньше дочернего процесса, зомби остаётся в таблице до перезагрузки системы или_until_ усыновления процессом init.
Опасность массового появления зомби
Одиночный зомби-процесс занимает минимальные ресурсы — несколько сотен байт в таблице процессов. Проблемы возникают при массовом появлении зомби:
Исчерпание PID-пространства. Linux имеет ограниченное количество доступных идентификаторов процессов (обычно 32768 или 4194304). Массовые зомби занимают PID'ы, делая невозможным создание новых процессов.
Утечка файловых дескрипторов. Зомби удерживает файловые дескрипторы, открытые до момента завершения. При большом количестве зомби система исчерпывает лимит открытых файлов.
Затруднённая диагностика. Таблица процессов, заполненная зомби, усложняет поиск реально работающих процессов.
Правильная обработка дочерних процессов
Родительский процесс обязан корректно обрабатывать завершение дочерних процессов:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
volatile sig_atomic_t child_exited = 0;
void sigchld_handler(int signo) {
child_exited = 1;
}
void reap_children() {
int status;
pid_t pid;
// Сбор всех завершённых дочерних процессов
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
if (WIFEXITED(status)) {
printf("Процесс %d завершился с кодом %d\n",
pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Процесс %d убит сигналом %d\n",
pid, WTERMSIG(status));
}
}
}
int main() {
// Регистрация обработчика SIGCHLD
struct sigaction sa;
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
sigaction(SIGCHLD, &sa, NULL);
// Создание дочерних процессов
for (int i = 0; i < 10; i++) {
pid_t pid = fork();
if (pid == 0) {
// Дочерний процесс
sleep(rand() % 5);
exit(i);
} else if (pid > 0) {
printf("Создан дочерний процесс %d\n", pid);
} else {
perror("fork failed");
exit(1);
}
}
// Основной цикл с обработкой сигналов
while (1) {
pause(); // Ожидание сигнала
if (child_exited) {
reap_children();
child_exited = 0;
}
}
return 0;
}
Разбор ключевых элементов:
sigactionрегистрирует обработчик сигналаSIGCHLDс флагамиSA_RESTARTиSA_NOCLDSTOP.SA_NOCLDSTOPпредотвращает отправку сигнала при остановке (но не завершении) дочерних процессов.waitpidс флагомWNOHANGвозвращает управление немедленно, если нет завершённых процессов.- Цикл
whileнеобходим, так как несколько процессов могут завершиться до обработки сигнала. - Макросы
WIFEXITED,WEXITSTATUS,WIFSIGNALED,WTERMSIGизвлекают информацию о способе завершения.
Автоматическое предотвращение зомби
Двойной fork — техника, при которой дочерний процесс немедленно создаёт собственного потомка и завершается:
pid_t pid = fork();
if (pid == 0) {
// Первый дочерний процесс
pid_t grandchild = fork();
if (grandchild == 0) {
// Второй дочерний процесс (внук)
// Выполняет полезную работу
do_work();
exit(0);
} else if (grandchild > 0) {
// Первый дочерний процесс завершается
// Внук становится сиротой и усыновляется init
exit(0);
}
}
// Родитель ждёт завершения первого дочернего процесса
waitpid(pid, NULL, 0);
// Первый дочерний процесс собран, зомби нет
// Внук работает независимо, его собирает init
Игнорирование SIGCHLD указывает ядру автоматически собирать завершённые дочерние процессы:
signal(SIGCHLD, SIG_IGN);
// Все дочерние процессы автоматически собираются ядром
for (int i = 0; i < 100; i++) {
if (fork() == 0) {
do_work();
exit(0);
}
}
// Зомби не появляются
Системные супервизоры
Современные системы инициализации автоматически управляют жизненным циклом процессов:
# /etc/supervisor/conf.d/worker.conf
[program:worker]
command=/opt/app/bin/worker --config /etc/app/worker.yaml
directory=/opt/app
user=appuser
autostart=true
autorestart=true
startsecs=10
startretries=3
exitcodes=0,2
stopsignal=TERM
stopwaitsecs=30
stdout_logfile=/var/log/app/worker.stdout.log
stderr_logfile=/var/log/app/worker.stderr.log
environment=ENVIRONMENT="production"
systemd как супервизор сервисов:
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application Service
After=network.target postgresql.service
Wants=postgresql.service
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/server --config /etc/myapp/config.yaml
ExecReload=/bin/kill -HUP $MAINPID
# Автоматический перезапуск при сбое
Restart=on-failure
RestartSec=10s
StartLimitInterval=60s
StartLimitBurst=3
# Ограничение ресурсов
LimitNOFILE=65536
LimitNPROC=4096
MemoryMax=4G
CPUQuota=200%
# Логирование
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
[Install]
WantedBy=multi-user.target
Зависшие cron-задачи и оркестрация фоновых работ
Cron-задача — периодическое задание, выполняемое системным планировщиком cron в заданное время или с заданным интервалом.
Классический cron предоставляет простой механизм запуска задач по расписанию, но lacks многих возможностей, необходимых для надёжного выполнения фоновых операций в production-среде.
Ограничения классического cron
Отсутствие контроля параллелизма. Cron запускает задачу по расписанию независимо от того, завершился ли предыдущий запуск. При замедлении задачи несколько экземпляров выполняются одновременно, создавая конкуренцию за ресурсы.
Отсутствие таймаутов. Задача выполняется бесконечно до завершения или до принудительного останова. Зависшая задача потребляет ресурсы неопределённо долго.
Ограниченное логирование. Вывод задач отправляется по email пользователю или теряется. Отсутствует централизованный сбор логов и метрик выполнения.
Отсутствие retry-механизмов. Сбой задачи требует ручного вмешательства для повторного запуска.
Сложность мониторинга. Отсутствуют встроенные механизмы оповещения о сбоях или превышении времени выполнения.
Типичные проблемы cron-задач
Накопление зависших экземпляров. Задача, выполняющаяся дольше интервала запуска, порождает множество параллельных процессов:
# crontab - выполнение каждые 5 минут
*/5 * * * * /opt/app/scripts/sync_data.sh
# Если sync_data.sh выполняется 15 минут,
# через час будет 12 параллельных экземпляров
Конкуренция за ресурсы. Параллельные экземпляры задачи обращаются к одним файлам, базам данных или внешним API, вызывая взаимные блокировки и ошибки.
Утечка ресурсов. Незавершённые задачи удерживают файловые дескрипторы, соединения с базами данных, временные файлы.
Невозможность graceful shutdown. При остановке системы cron-задачи получают сигнал SIGTERM без возможности корректного завершения работы.
Современные системы оркестрации задач
Celery для Python-приложений:
from celery import Celery
from celery.schedules import crontab
from datetime import timedelta
app = Celery(
'tasks',
broker='redis://localhost:6379/0',
backend='redis://localhost:6379/1'
)
# Конфигурация
app.conf.update(
# Таймауты выполнения
task_time_limit=3600, # Максимум 1 час
task_soft_time_limit=3000, # Предупреждение через 50 минут
# Повторные попытки
task_acks_late=True,
worker_prefetch_multiplier=1,
# Расписание
beat_schedule={
'sync-catalog-every-hour': {
'task': 'tasks.sync_catalog',
'schedule': crontab(minute=0), # Каждый час в :00
'options': {'expires': 3000} # Задача истекает через 50 минут
},
'cleanup-temp-files-daily': {
'task': 'tasks.cleanup_temp_files',
'schedule': crontab(hour=3, minute=0), # Ежедневно в 03:00
},
'generate-reports-weekly': {
'task': 'tasks.generate_weekly_reports',
'schedule': crontab(day_of_week='monday', hour=6, minute=0),
},
},
# Результат выполнения
result_expires=timedelta(days=7),
)
@app.task(
bind=True,
max_retries=3,
default_retry_delay=60,
autoretry_for=(ConnectionError, TimeoutError),
retry_backoff=True,
retry_backoff_max=600,
retry_jitter=True
)
def sync_catalog(self):
"""Синхронизация каталога с внешним API."""
try:
# Проверка единственного экземпляра через Redis
lock_key = f"lock:sync_catalog"
lock = self.app.backend.client.lock(lock_key, timeout=3500)
if not lock.acquire(blocking=False):
self.retry(countdown=300)
return
try:
external_api = ExternalCatalogAPI()
items = external_api.fetch_all_items()
processed = 0
for batch in chunked(items, 100):
process_batch(batch)
processed += len(batch)
# Обновление прогресса
self.update_state(
state='PROGRESS',
meta={
'processed': processed,
'total': len(items),
'percent': processed * 100 // len(items)
}
)
return {'status': 'success', 'processed': processed}
finally:
lock.release()
except Exception as exc:
# Автоматический перезапуск с экспоненциальной задержкой
raise self.retry(exc=exc)
Kubernetes CronJob для контейнерных сред:
apiVersion: batch/v1
kind: CronJob
metadata:
name: data-sync
namespace: production
spec:
schedule: "0 */2 * * *" # Каждые 2 часа
concurrencyPolicy: Forbid # Запрет параллельных запусков
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
startingDeadlineSeconds: 600 # Дедлайн запуска 10 минут
jobTemplate:
spec:
activeDeadlineSeconds: 3600 # Максимум 1 час выполнения
backoffLimit: 3 # До 3 попыток
template:
spec:
restartPolicy: OnFailure
containers:
- name: sync
image: company/data-sync:latest
command: ["/app/sync", "--full"]
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: database-credentials
key: url
volumeMounts:
- name: config
mountPath: /etc/sync
readOnly: true
volumes:
- name: config
configMap:
name: sync-config
systemd timers как замена cron:
# /etc/systemd/system/data-sync.timer
[Unit]
Description=Run data sync every 2 hours
Requires=data-sync.service
[Timer]
OnCalendar=*-*-* 00/2:00:00
Persistent=true
RandomizedDelaySec=300
AccuracySec=1min
[Install]
WantedBy=timers.target
# /etc/systemd/system/data-sync.service
[Unit]
Description=Data synchronization service
After=network.target
[Service]
Type=oneshot
User=syncuser
WorkingDirectory=/opt/sync
ExecStart=/opt/sync/bin/sync --full
# Таймауты
TimeoutStartSec=3600
TimeoutStopSec=300
# Ограничения ресурсов
MemoryMax=2G
CPUQuota=200%
TasksMax=100
# Логирование
StandardOutput=journal
StandardError=journal
SyslogIdentifier=data-sync
# Безопасность
ProtectSystem=strict
ProtectHome=true
NoNewPrivileges=true
PrivateTmp=true
Заполнение диска и управление пространством
Заполнение диска — исчерпание свободного пространства на файловой системе, ведущее к невозможности записи логов, временных файлов, кэша и чекпоинтов.
Приложение, исчерпавшее дисковое пространство, теряет способность выполнять множество штатных операций. Запись логов прекращается, кэш перестаёт обновляться, транзакции баз данных не могут записать WAL-файлы, приложения не могут создать временные файлы.
Источники заполнения
Бесконтрольные логи. Лог-файлы, растущие без ротации, способны занять всё доступное пространство за несколько дней активной работы. Одно приложение может генерировать гигабайты логов ежедневно.
Временные файлы. Приложения создают временные файлы для обработки больших объёмов данных. Отсутствие очистки после завершения операции ведёт к накоплению мусора.
Кэши на диске. Дисковые кэши требуют явной политики вытеснения устаревших записей. Без ограничений кэш растёт бесконечно.
Дампы памяти. Автоматически создаваемые heap-dump-файлы при ошибках OutOfMemoryError занимают объём, равный размеру кучи приложения.
Core dumps. Файлы дампов памяти при падении процессов могут достигать гигабайтов.
Журналы баз данных. WAL-файлы PostgreSQL, binlog MySQL, redo log Oracle растут непрерывно и требуют периодической очистки.
Файлы репликации. Реплики баз данных накапливают сегменты журналов до момента их применения.
Система ротации логов
logrotate — стандартный инструмент Linux для управления лог-файлами:
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
# Периодичность ротации
daily
# Количество сохраняемых архивов
rotate 30
# Сжатие старых логов
compress
delaycompress
# Обработка отсутствующих файлов
missingok
# Пропуск пустых файлов
notifempty
# Создание нового файла с заданными правами
create 0640 myapp myapp
# Ротация при достижении размера
size 100M
maxsize 500M
# Общий скрипт для всех файлов
sharedscripts
# Команда после ротации
postrotate
systemctl reload myapp > /dev/null 2>&1 || true
endscript
}
# Отдельная конфигурация для access-логов
/var/log/myapp/access.log {
daily
rotate 90
compress
delaycompress
missingok
notifempty
create 0644 myapp myapp
# Специальная обработка для nginx
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
endscript
}
# Логи с высокой интенсивностью записи
/var/log/myapp/api.log {
hourly
rotate 48
compress
delaycompress
missingok
notifempty
size 500M
create 0640 myapp myapp
sharedscripts
postrotate
/usr/bin/killall -HUP rsyslogd 2>/dev/null || true
endscript
}
Разбор параметров конфигурации:
dailyзадаёт ежедневную ротацию файлов.hourlyзадаёт ежечасную ротацию для интенсивных логов.rotate Nсохраняет N архивных копий перед удалением.compressвключает сжатие старых логов через gzip.delaycompressоткладывает сжатие на один цикл, позволяя дочитать файл.missingokпредотвращает ошибки при отсутствии лог-файла.notifemptyпропускает ротацию пустых файлов.sizeиmaxsizeзадают пороги ротации по размеру файла.createзадаёт права и владельца нового файла.sharedscriptsвыполняетpostrotateодин раз для всех файлов.postrotateвыполняет команду после ротации для переоткрытия файлов.
Мониторинг дискового пространства
Предупреждение о скором заполнении диска должно срабатывать задолго до достижения критического уровня:
#!/bin/bash
# /usr/local/bin/check_disk_space.sh
THRESHOLD_WARNING=80
THRESHOLD_CRITICAL=90
THRESHOLD_EMERGENCY=95
EXCLUDE_FS="tmpfs|devtmpfs|squashfs|overlay"
check_filesystem() {
local mount_point=$1
local usage=$2
local available=$3
local total=$4
local metric_name="disk_usage_percent{mount=\"$mount_point\"}"
echo "$metric_name $usage"
if [ $usage -ge $THRESHOLD_EMERGENCY ]; then
echo "CRITICAL: $mount_point заполнен на ${usage}% (свободно: $available)"
send_page_alert "Disk space emergency on $mount_point"
trigger_cleanup "$mount_point"
elif [ $usage -ge $THRESHOLD_CRITICAL ]; then
echo "CRITICAL: $mount_point заполнен на ${usage}%"
send_alert "Disk space critical on $mount_point"
elif [ $usage -ge $THRESHOLD_WARNING ]; then
echo "WARNING: $mount_point заполнен на ${usage}%"
send_warning "Disk space warning on $mount_point"
fi
}
# Проверка всех файловых систем
df -P | grep -vE "^Filesystem|$EXCLUDE_FS" | while read line; do
filesystem=$(echo "$line" | awk '{print $1}')
total=$(echo "$line" | awk '{print $2}')
used=$(echo "$line" | awk '{print $3}')
available=$(echo "$line" | awk '{print $4}')
usage=$(echo "$line" | awk '{print $5}' | tr -d '%')
mount_point=$(echo "$line" | awk '{print $6}')
check_filesystem "$mount_point" "$usage" "$available" "$total"
done
# Проверка inode (количество файлов)
df -iP | grep -vE "^Filesystem|$EXCLUDE_FS" | while read line; do
mount_point=$(echo "$line" | awk '{print $6}')
inode_usage=$(echo "$line" | awk '{print $5}' | tr -d '%')
if [ "$inode_usage" -ge $THRESHOLD_CRITICAL ]; then
echo "CRITICAL: Inodes exhausted on $mount_point (${inode_usage}%)"
send_alert "Inode exhaustion on $mount_point"
fi
done
Автоматическая очистка
Скрипт аварийной очистки при достижении критического порога:
#!/bin/bash
# /usr/local/bin/emergency_cleanup.sh
MOUNT_POINT=$1
LOG_FILE="/var/log/emergency_cleanup.log"
log_action() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}
log_action "Начало аварийной очистки $MOUNT_POINT"
# 1. Очистка старых логов
log_action "Удаление логов старше 7 дней"
find "$MOUNT_POINT/var/log" -name "*.log.gz" -mtime +7 -delete
find "$MOUNT_POINT/var/log" -name "*.log.?" -mtime +3 -delete
# 2. Очистка временных файлов
log_action "Удаление временных файлов старше 1 дня"
find "$MOUNT_POINT/tmp" -type f -mtime +1 -delete
find "$MOUNT_POINT/var/tmp" -type f -mtime +1 -delete
# 3. Очистка кэша пакетного менеджера
if command -v apt-get &> /dev/null; then
log_action "Очистка apt cache"
apt-get clean
fi
if command -v yum &> /dev/null; then
log_action "Очистка yum cache"
yum clean all
fi
# 4. Удаление старых ядер (Ubuntu/Debian)
if [ -f /etc/debian_version ]; then
log_action "Удаление старых ядер"
dpkg --list | grep linux-image | awk '{ print $2 }' | \
sort -V | sed -n '/'"$(uname -r | sed "s/\([0-9.-]*\)-\([^0-9]\+\)/\1/")"'/q;p' | \
xargs sudo apt-get -y purge
fi
# 5. Очистка Docker (если установлен)
if command -v docker &> /dev/null; then
log_action "Очистка неиспользуемых Docker-ресурсов"
docker system prune -af --volumes
fi
# 6. Очистка journal-логов systemd
if command -v journalctl &> /dev/null; then
log_action "Ограничение размера journal"
journalctl --vacuum-size=500M
journalctl --vacuum-time=7d
fi
log_action "Аварийная очистка завершена"
Политики управления пространством для баз данных
PostgreSQL — управление WAL-файлами и логами:
-- Настройка архивации WAL
ALTER SYSTEM SET wal_level = 'replica';
ALTER SYSTEM SET archive_mode = 'on';
ALTER SYSTEM SET archive_command = 'test ! -f /archive/%f && cp %p /archive/%f';
ALTER SYSTEM SET max_wal_size = '4GB';
ALTER SYSTEM SET min_wal_size = '1GB';
ALTER SYSTEM SET wal_keep_size = '2GB';
-- Автоматическая очистка старых WAL после применения на репликах
ALTER SYSTEM SET wal_recycle = 'on';
-- Настройка log rotation
ALTER SYSTEM SET log_rotation_age = '1d';
ALTER SYSTEM SET log_rotation_size = '100MB';
ALTER SYSTEM SET log_truncate_on_rotation = 'on';
MySQL — управление binlog:
-- Автоматическое удаление старых binlog
SET GLOBAL expire_logs_days = 7;
SET GLOBAL binlog_expire_logs_seconds = 604800; -- 7 дней
-- Ограничение размера binlog
SET GLOBAL max_binlog_size = 104857600; -- 100MB
-- Ручная очистка
PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 7 DAY);
Диагностика системных аномалий
Выявление проблем в системных процессах требует специализированных инструментов и методологий.
Анализ процессов и ресурсов
htop — интерактивный монитор процессов:
# Запуск htop с сортировкой по использованию памяти
htop --sort-key=PERCENT_MEM
# Фильтрация процессов по имени
htop --filter="postgres"
# Отображение дерева процессов
htop --tree
pidstat — статистика процессов от sysstat:
# Мониторинг CPU всех процессов каждые 5 секунд
pidstat -u 5
# Мониторинг памяти
pidstat -r 5
# Мониторинг I/O
pidstat -d 5
# Статистика по конкретному процессу
pidstat -p 12345 1
strace — трассировка системных вызовов:
# Трассировка всех системных вызовов процесса
strace -p <pid>
# Трассировка с временными метками
strace -T -p <pid>
# Трассировка только сетевых вызовов
strace -e trace=network -p <pid>
# Трассировка файловых операций
strace -e trace=file -p <pid>
# Подсчёт системных вызовов
strace -c -p <pid>
Анализ сетевых проблем
tcpdump — захват сетевого трафика:
# Захват трафика на определённом порту
tcpdump -i eth0 port 5432 -w postgres_traffic.pcap
# Захват DNS-трафика
tcpdump -i eth0 port 53 -vv
# Захват с фильтром по хосту
tcpdump -i eth0 host api.external-service.com
# Отображение в человекочитаемом формате
tcpdump -i eth0 -A port 80
ss — анализ сокетов:
# Все TCP-соединения
ss -t -a
# Соединения с указанием процесса
ss -t -a -p
# UDP-сокеты
ss -u -a
# Статистика сокетов
ss -s
# Соединения в состоянии TIME-WAIT
ss -t -a state time-wait
# Соединения с определённым портом
ss -t -a '( dport = :443 or sport = :443 )'
mtr — комбинированный traceroute и ping:
# Непрерывный мониторинг маршрута
mtr api.external-service.com
# Отчёт с 100 пакетами
mtr --report --report-cycles 100 api.external-service.com
# Без разрешения DNS-имён
mtr -n api.external-service.com
Анализ дискового пространства
ncdu — интерактивный анализатор использования диска:
# Сканирование корневой файловой системы
ncdu /
# Сканирование с исключением директорий
ncdu --exclude /proc --exclude /sys /
# Экспорт результатов в JSON
ncdu -o disk_usage.json /
du с агрегацией:
# Топ-20 крупнейших директорий
du -h / | sort -rh | head -20
# Крупнейшие файлы
find / -type f -exec du -h {} + | sort -rh | head -20
# Использование по директориям первого уровня
du -sh /* 2>/dev/null | sort -rh
lsof для поиска открытых файлов:
# Файлы, открытые конкретным процессом
lsof -p <pid>
# Процессы, использующие конкретный файл
lsof /var/log/myapp/application.log
# Удалённые файлы, всё ещё открытые процессами
lsof +L1
# Сетевые соединения процесса
lsof -i -p <pid>
Системные журналы
journalctl для анализа логов systemd:
# Логи конкретного сервиса
journalctl -u myapp.service
# Логи за последний час
journalctl --since "1 hour ago"
# Логи с определённым приоритетом
journalctl -p err
# Логи с временными метками
journalctl -u myapp.service --output=short-precise
# Непрерывный мониторинг
journalctl -u myapp.service -f
# Логи конкретного PID
journalctl _PID=12345
# Логи за период
journalctl --since "2026-05-20" --until "2026-05-22"
Таблица системных аномалий и решений
| Аномалия | Признак | Инструмент диагностики | Решение |
|---|---|---|---|
| Смещение времени | Ошибки аутентификации, некорректный порядок событий | chronyc tracking, timedatectl | NTP-синхронизация, монотонные часы |
| Сбои DNS | UnknownHostException, таймауты подключений | dig, mtr, tcpdump port 53 | Резервные резолверы, кэширование с TTL |
| Зомби-процессы | Состояние Z в ps, исчерпание PID | ps aux, top, /proc/[pid]/status | Обработка SIGCHLD, systemd-супервизоры |
| Зависшие cron | Множественные экземпляры, конкуренция за ресурсы | ps, pgrep, логи cron | Celery, Kubernetes CronJob, systemd timers |
| Заполнение диска | ENOSPC ошибки, невозможность записи | df, du, ncdu, lsof +L1 | logrotate, политики очистки, мониторинг |
Принципы устойчивого системного администрирования
Эффективное управление системными процессами опирается на набор фундаментальных принципов.
Принцип синхронизации времени
Все узлы распределённой системы синхронизированы с надёжными источниками времени. Приложения используют монотонные часы для измерения интервалов.
Принцип наблюдаемости
Каждый системный процесс экспонирует метрики выполнения, логирует ключевые события и предоставляет механизмы проверки состояния.
Принцип ограничения ресурсов
Все фоновые задачи имеют явные ограничения по времени выполнения, потреблению памяти и процессорного времени.
Принцип идемпотентности
Периодические задачи допускают безопасное повторное выполнение без побочных эффектов.
Принцип graceful degradation
При дефиците ресурсов система снижает интенсивность фоновых операций, сохраняя работоспособность критичных сервисов.
Принцип автоматического восстановления
Сбои системных процессов автоматически обнаруживаются и устраняются через перезапуск или переключение на резервные механизмы.
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Администрирование как системная практика - управление доступами, обновлениями, резервным копированием и стабильностью IT-инфраструктуры. Установка ОС - это когда мы ставим её на чистый или отформатированный компьютер. Если аппаратное обеспечение — это тело инфраструктуры, то программное обеспечение — её нервная система. Без ПО железо остаётся набором нефункциональных компонентов. Настройка и обслуживание серверов - развёртывание узла в инфраструктуре, базовая конфигурация и контроль стабильной эксплуатации. Групповые политики Windows - централизованное применение настроек в домене и управление конфигурацией рабочих станций. Конфигурация рабочих станций - настройка сетевых параметров, подключение к инфраструктуре и стандартизация клиентской среды. Сетевые подключения и диагностика - карта узлов, роль коммутаторов и базовые методы поиска проблем в гетерогенной сети. При обращении к сетевому ресурсу в адресной строке проводника Windows используется UNC-путь (Universal Naming Convention) — Например — FAMILYPCMovies PRINTER01HP_LaserJet Проброс портов — это явное исключение из стандартного поведения NAT, которое блокирует все входящие соединения. Он не включается автоматически и требует ручной конфигурации. Планирование и автоматизация задач - как использовать планировщики для регулярных операций, резервного копирования и обслуживания систем. Диагностика и обработка системных ошибок - как выявлять корневые причины сбоев и восстанавливать работоспособность сервисов. Администрирование баз данных - резервное копирование, обслуживание, контроль производительности и безопасность эксплуатационной среды.Администрирование
Установка и первоначальная настройка ОС
ИТ-инфраструктура
Настройка и обслуживание серверов
Групповые политики в Windows
Конфигурация рабочих станций
Сетевые подключения и диагностика
Организация домашней сети
NAT и проброс портов
Планирование и автоматизация задач
Диагностика и обработка системных ошибок
Работа с базами данных в администрировании