Резервное копирование в Linux: стратегии и инструменты
Правило 3-2-1
Любая стратегия резервного копирования должна начинаться с правила 3-2-1:
3 копии данных
2 разных типа носителей
1 копия вне офиса
Rsync: умное инкрементальное копирование
#!/usr/bin/env bash
# Скрипт резервного копирования с ротацией
BACKUP_SOURCE="/var/www"
BACKUP_DEST="/mnt/backup"
RETAIN_DAYS=30
DATE=$(date +%Y%m%d_%H%M%S)
# Создаём снэпшот через hard links (не дублируем неизменённые файлы)
rsync -avz --delete \
--link-dest="$BACKUP_DEST/latest" \
--exclude="*.log" \
--exclude="cache/" \
--exclude="tmp/" \
"$BACKUP_SOURCE/" \
"$BACKUP_DEST/$DATE/"
# Обновляем симлинк на последний бэкап
ln -sfn "$BACKUP_DEST/$DATE" "$BACKUP_DEST/latest"
# Удаляем старые бэкапы
find "$BACKUP_DEST" -maxdepth 1 -type d -mtime +$RETAIN_DAYS -exec rm -rf {} +
echo "Backup completed: $BACKUP_DEST/$DATE"
du -sh "$BACKUP_DEST/$DATE"
Borg: дедупликация и шифрование
Borg — продвинутый инструмент с дедупликацией (одинаковые блоки хранятся один раз):
# Установка
apt install borgbackup
# Инициализация репозитория с шифрованием
borg init --encryption=repokey-blake2 user@backup-server:/backups/myserver
# Создание бэкапа
borg create \
--verbose \
--filter AME \
--list \
--stats \
--show-rc \
--compression lz4 \
--exclude-caches \
--exclude '/home/*/.cache/*' \
--exclude '/var/cache/*' \
--exclude '/var/tmp/*' \
user@backup-server:/backups/myserver::myserver-$(date +%Y%m%d_%H%M) \
/etc \
/var/www \
/home \
/var/lib/mysql # осторожно с активной БД!
# Список архивов
borg list user@backup-server:/backups/myserver
# Проверка целостности
borg check user@backup-server:/backups/myserver
# Восстановление
cd /tmp/restore
borg extract user@backup-server:/backups/myserver::myserver-20240115_0300 \
var/www/myapp/public # только конкретная директория
# Ротация (хранить: 7 ежедневных, 4 недельных, 12 ежемесячных)
borg prune \
--keep-daily=7 \
--keep-weekly=4 \
--keep-monthly=12 \
user@backup-server:/backups/myserver
Бэкап MySQL без блокировок
#!/usr/bin/env bash
# Бэкап MySQL с минимальным влиянием на продакшн
DB_USER="backup"
DB_PASS="backup_password"
BACKUP_DIR="/var/backups/mysql"
DATE=$(date +%Y%m%d_%H%M)
mkdir -p "$BACKUP_DIR"
# Создаём пользователя для бэкапа (только необходимые права)
# GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER, PROCESS ON *.* TO 'backup'@'localhost';
# Бэкап всех баз
mysqldump \
--user="$DB_USER" \
--password="$DB_PASS" \
--single-transaction \
--routines \
--triggers \
--events \
--all-databases \
--master-data=2 \
| gzip > "$BACKUP_DIR/full-$DATE.sql.gz"
# Проверяем что файл не пустой
size=$(stat -c%s "$BACKUP_DIR/full-$DATE.sql.gz")
if [[ $size -lt 1000 ]]; then
echo "ERROR: Backup file too small ($size bytes)" >&2
rm "$BACKUP_DIR/full-$DATE.sql.gz"
exit 1
fi
echo "Backup created: $BACKUP_DIR/full-$DATE.sql.gz ($size bytes)"
# Ротация — удаляем старше 7 дней
find "$BACKUP_DIR" -name "full-*.sql.gz" -mtime +7 -delete
# XtraBackup для горячего бэкапа InnoDB (без --single-transaction ограничений)
# apt install percona-xtrabackup-80
# xtrabackup --backup --user="$DB_USER" --password="$DB_PASS" \
# --target-dir="$BACKUP_DIR/xtrabackup-$DATE"
Проверка восстановления — самое важное
Бэкап без проверки восстановления — не бэкап. Автоматизируйте:
#!/usr/bin/env bash
# Тест восстановления MySQL (запускать еженедельно)
BACKUP_FILE=$(ls -t /var/backups/mysql/full-*.sql.gz | head -1)
TEST_DB="restore_test_$(date +%s)"
echo "Testing restore of $BACKUP_FILE"
# Создаём тестовую базу
mysql -e "CREATE DATABASE $TEST_DB"
# Восстанавливаем
zcat "$BACKUP_FILE" | mysql "$TEST_DB"
# Проверяем количество таблиц
table_count=$(mysql -sN -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$TEST_DB'")
echo "Tables restored: $table_count"
# Удаляем тестовую базу
mysql -e "DROP DATABASE $TEST_DB"
if [[ $table_count -gt 0 ]]; then
echo "Restore test PASSED"
else
echo "Restore test FAILED!" >&2
exit 1
fi
Nginx: тюнинг и продвинутая конфигурация
Производительность nginx
# /etc/nginx/nginx.conf
user www-data;
# Одни worker per CPU core
worker_processes auto;
# Привязываем к ядрам (снижаем context switch)
worker_cpu_affinity auto;
# Максимум соединений = worker_processes * worker_connections
events {
worker_connections 4096;
use epoll; # лучший I/O multiplexer для Linux
multi_accept on; # принимаем все соединения за один раз
}
http {
# Базовые оптимизации
sendfile on;
tcp_nopush on; # отправлять заголовки и начало файла вместе
tcp_nodelay on; # отключить Nagle для активных соединений
# Таймауты
keepalive_timeout 65;
keepalive_requests 1000;
client_header_timeout 15;
client_body_timeout 15;
send_timeout 15;
# Буферы
client_body_buffer_size 128k;
client_max_body_size 50M;
client_header_buffer_size 1k;
large_client_header_buffers 4 16k;
# Сжатие
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 5;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml;
# Кэширование статики
open_file_cache max=10000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# Безопасность
server_tokens off;
more_clear_headers Server; # если установлен nginx-extras
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_conn_zone $binary_remote_addr zone=perip:10m;
}
Virtual host для PHP-приложения
# /etc/nginx/sites-available/myapp.conf
# Upstream pool с health checks
upstream php_fpm {
least_conn; # балансировка по наименее загруженному
server 127.0.0.1:9000 weight=5 max_fails=3 fail_timeout=30s;
server 127.0.0.1:9001 weight=5 max_fails=3 fail_timeout=30s;
keepalive 32; # постоянные соединения к FPM
}
# Кэш для FastCGI ответов
fastcgi_cache_path /var/cache/nginx/fastcgi
levels=1:2
keys_zone=php_cache:100m
max_size=2g
inactive=60m
use_temp_path=off;
server {
listen 80;
server_name myapp.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name myapp.example.com;
root /var/www/myapp/public;
index index.php;
# SSL
ssl_certificate /etc/letsencrypt/live/myapp.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.example.com/privkey.pem;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_stapling on;
ssl_stapling_verify on;
# Безопасность
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Логи
access_log /var/log/nginx/myapp-access.log combined buffer=512k flush=1m;
error_log /var/log/nginx/myapp-error.log warn;
# Ограничения
limit_conn perip 20;
# Статика с долгим кешированием
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
log_not_found off;
access_log off;
}
# API с rate limiting
location /api/ {
limit_req zone=api burst=20 nodelay;
try_files $uri $uri/ /index.php?$query_string;
}
location /api/auth {
limit_req zone=login burst=5 nodelay;
try_files $uri $uri/ /index.php?$query_string;
}
# PHP-FPM
location ~ \.php$ {
fastcgi_pass php_fpm;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
# Кеширование (осторожно — только для некэшируемого поставьте X-Cache-Bypass)
fastcgi_cache php_cache;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 302 60m;
fastcgi_cache_valid 404 1m;
fastcgi_cache_bypass $http_pragma $http_authorization $cookie_PHPSESSID;
fastcgi_no_cache $http_pragma $http_authorization;
add_header X-Cache-Status $upstream_cache_status;
# Буферизация
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
# Таймаут для долгих запросов
fastcgi_read_timeout 300;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Запрещаем служебные файлы
location ~ /\.(ht|git|env) {
deny all;
return 404;
}
}
Централизованное логирование: rsyslog, loki, ELK
rsyslog: маршрутизация логов
# /etc/rsyslog.conf — продвинутая конфигурация
# Шаблоны
template(name="FileFormat" type="string"
string="%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n"
)
# JSON формат для Logstash/Loki
template(name="JSONFormat" type="list") {
constant(value="{")
constant(value="\"timestamp\":\"")
property(name="timereported" dateFormat="rfc3339")
constant(value="\",\"host\":\"")
property(name="hostname")
constant(value="\",\"severity\":\"")
property(name="syslogseverity-text")
constant(value="\",\"facility\":\"")
property(name="syslogfacility-text")
constant(value="\",\"program\":\"")
property(name="programname")
constant(value="\",\"pid\":\"")
property(name="procid")
constant(value="\",\"message\":\"")
property(name="msg" format="json")
constant(value="\"}\n")
}
# Маршрутизация по приоритету
*.emerg :omusrmsg:* # все терминалы при критической ошибке
auth,authpriv.* /var/log/auth.log
mail.* -/var/log/mail.log # дефис = буферизованная запись
cron.* /var/log/cron.log
*.warn /var/log/warnings.log
# Отдельный файл для nginx
if $programname == 'nginx' then {
action(type="omfile" file="/var/log/nginx/error.log" template="FileFormat")
stop
}
# Пересылка на центральный сервер
*.* action(type="omfwd"
target="log-server.internal"
port="514"
protocol="tcp"
template="JSONFormat"
action.resumeRetryCount="-1"
queue.type="linkedList"
queue.size="50000"
queue.filename="rsyslog_queue"
queue.saveonshutdown="on"
)
Loki + Promtail: современный стек
Loki — это "Prometheus для логов", хранит логи как метрики с метками:
# /etc/promtail/promtail-config.yaml
server:
http_listen_port: 9080
positions:
filename: /var/log/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: nginx
static_configs:
- targets: [localhost]
labels:
job: nginx
env: production
__path__: /var/log/nginx/access.log
pipeline_stages:
- regex:
expression: '^(?P<remote_addr>\S+) - (?P<remote_user>\S+) \[(?P<time>[^\]]+)\] "(?P<method>\S+) (?P<path>[^\s"]+)[^"]*" (?P<status>\d+) (?P<body_bytes>\d+)'
- labels:
method:
status:
- metrics:
http_requests_total:
type: Counter
description: "Total HTTP requests"
source: status
config:
action: inc
- job_name: php-app
static_configs:
- targets: [localhost]
labels:
job: php-app
__path__: /var/www/myapp/storage/logs/*.log
pipeline_stages:
- multiline:
firstline: '^\[\d{4}-\d{2}-\d{2}'
max_wait_time: 3s
- regex:
expression: '^\[(?P<time>[^\]]+)\] (?P<env>\w+)\.(?P<level>[A-Z]+): (?P<message>.+)'
- labels:
level:
env:
Запросы LogQL (язык Loki)
# Все ошибки nginx
{job="nginx"} |= "error"
# HTTP 500 ошибки за последний час
{job="nginx"} | regex `status=(?P<status>\d+)` | status="500"
# Медленные запросы (>1 секунды)
{job="nginx"} | regex `request_time=(?P<rt>[0-9.]+)` | rt > 1.0
# Топ URL по количеству запросов
topk(10, sum by (path) (rate({job="nginx"} | json [5m])))
# Уровень ошибок в приложении
sum(rate({job="php-app", level="ERROR"}[5m])) by (level)
Ansible: управление конфигурациями Linux-серверов
Структура Ansible-проекта
ansible/
├── ansible.cfg
├── inventory/
│ ├── production/
│ │ ├── hosts.yml
│ │ └── group_vars/
│ │ ├── all.yml
│ │ ├── web.yml
│ │ └── db.yml
│ └── staging/
│ └── hosts.yml
├── roles/
│ ├── common/
│ ├── nginx/
│ ├── php/
│ └── mysql/
└── playbooks/
├── site.yml
├── deploy.yml
└── update.yml
ansible.cfg
[defaults]
inventory = inventory/production
remote_user = deploy
private_key_file = ~/.ssh/id_ed25519
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
callback_whitelist = timer, profile_tasks
forks = 20
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
Роль для hardening
# roles/common/tasks/main.yml
---
- name: Update and upgrade apt packages
apt:
upgrade: dist
update_cache: yes
cache_valid_time: 3600
- name: Install required packages
apt:
name:
- ufw
- fail2ban
- unattended-upgrades
- logrotate
- htop
- curl
- git
state: present
- name: Configure sysctl security settings
sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
state: present
reload: yes
loop: "{{ sysctl_settings }}"
- name: Configure UFW
ufw:
state: enabled
policy: deny
direction: incoming
- name: Allow SSH
ufw:
rule: allow
port: "{{ ssh_port }}"
proto: tcp
- name: Configure fail2ban
template:
src: jail.local.j2
dest: /etc/fail2ban/jail.local
owner: root
group: root
mode: '0644'
notify: restart fail2ban
- name: Configure SSH
template:
src: sshd_config.j2
dest: /etc/ssh/sshd_config
validate: 'sshd -t -f %s'
owner: root
group: root
mode: '0600'
notify: restart sshd
Идемпотентность: делаем правильно
# Создание пользователя (идемпотентно)
- name: Create deploy user
user:
name: deploy
groups: www-data
shell: /bin/bash
create_home: yes
state: present
# Копируем SSH ключ
- name: Set authorized keys
authorized_key:
user: deploy
state: present
key: "{{ lookup('file', 'files/deploy_key.pub') }}"
exclusive: yes # удалить другие ключи
# Изменение конфига только если нужно
- name: Configure PHP-FPM
template:
src: php-fpm-pool.conf.j2
dest: /etc/php/8.2/fpm/pool.d/www.conf
owner: root
group: root
mode: '0644'
notify: reload php-fpm
# Handlers (выполняются только если что-то изменилось)
# roles/php/handlers/main.yml
- name: reload php-fpm
service:
name: php8.2-fpm
state: reloaded
- name: restart php-fpm
service:
name: php8.2-fpm
state: restarted
Деплой приложения через Ansible
# playbooks/deploy.yml
---
- name: Deploy MyApp
hosts: web
serial: "30%" # Rolling update: 30% серверов одновременно
vars:
app_dir: /var/www/myapp
git_repo: git@github.com:company/myapp.git
git_branch: "{{ branch | default('main') }}"
tasks:
- name: Pull latest code
git:
repo: "{{ git_repo }}"
dest: "{{ app_dir }}"
version: "{{ git_branch }}"
force: yes
- name: Install Composer dependencies
composer:
command: install
working_dir: "{{ app_dir }}"
no_dev: yes
optimize_autoloader: yes
- name: Run migrations
command: php spark migrate --all
args:
chdir: "{{ app_dir }}"
run_once: true # только на одном сервере
- name: Clear application cache
command: php spark cache:clear
args:
chdir: "{{ app_dir }}"
- name: Reload PHP-FPM (graceful)
service:
name: php8.2-fpm
state: reloaded
- name: Warm up cache
uri:
url: "https://{{ inventory_hostname }}/health"
status_code: 200
retries: 5
delay: 2
Диагностика Linux: алгоритм поиска проблем
Методология USE
USE Method (Brendan Gregg): для каждого ресурса проверяем:
Utilization — использование (в %)
Saturation — насыщение (очереди, ожидание)
Errors — ошибки
# CPU Utilization
mpstat -P ALL 1 3
# CPU Saturation (очередь на выполнение)
vmstat 1 | awk '{print $1}' # r - run queue
# Memory Utilization
free -h
# Memory Saturation (swapping)
vmstat 1 | awk '{print $7, $8}' # si/so - swap in/out
# Disk Utilization
iostat -xz 1 | grep -E "Device|sd|nvme"
# Disk Saturation (await > service time)
iostat -xz 1 | awk 'NR>3 {print $1, $16}' # %util
# Network Utilization
sar -n DEV 1 5
60-секундный анализ сервера
# Быстрый обзор за 60 секунд (по Brendan Gregg)
uptime # load average
dmesg -T | tail -5 # ошибки ядра
vmstat -SM 1 3 # VM, CPU, I/O обзор
mpstat -P ALL 1 3 # CPU по ядрам
pidstat 1 3 # процессы
iostat -xz 1 3 # I/O дисков
free -m # память
sar -n DEV 1 3 # сеть
sar -n TCP,ETCP 1 3 # TCP метрики
top # интерактивно
Диагностика "сервер завис"
# 1. Можем ли мы что-то делать?
# Если не отвечает по SSH - физический доступ или IPMI/iLO
# 2. Что не отвечает?
ping server-ip # сеть живая?
nc -zv server-ip 22 # SSH порт открыт?
nc -zv server-ip 80 # HTTP открыт?
# 3. Загрузка
uptime
# load: 0.5 — норма
# load: = CPU cores — занят
# load: > CPU cores * 2 — перегружен
# 4. Кто виноват?
top -bn1 | head -20
ps auxwf | head -30
# 5. Есть ли OOM?
dmesg | grep -i "oom\|killed process"
journalctl -k --since "1 hour ago" | grep -i oom
# 6. Диск переполнен?
df -h
du -sh /var /tmp /home # кто занял место
# 7. Иноды кончились?
df -i
# 8. Что происходит с сетью?
ss -s # статистика сокетов
ss -tnp state time-wait | wc -l # TIME_WAIT
netstat -i # ошибки на интерфейсах
# 9. Дисковые проблемы
dmesg | grep -i "error\|fail\|i/o"
smartctl -H /dev/sda # здоровье диска
# 10. Полная картина за последний час
sar -A 1 10 # всё что собрал sar
strace и ltrace: что делает процесс
# Что делает процесс прямо сейчас
strace -p $(pgrep nginx | head -1)
# Только конкретные системные вызовы
strace -e trace=open,read,write,network -p PID
# Статистика системных вызовов за 5 секунд
strace -c -p PID -e trace=all &
sleep 5
kill %1
# ltrace — вызовы библиотечных функций
ltrace -p PID
# Запустить и трейсить
strace -e trace=network curl google.com 2>&1 | grep connect
# Дочерние процессы тоже
strace -f -p PID -o /tmp/strace.log
Анализ производительности с perf
# Профиль за 10 секунд (нужен linux-tools-generic)
perf record -F 99 -g -p $(pgrep php-fpm | head -1) -- sleep 10
perf report --stdio | head -50
# Hotspot функции
perf top -K -p $(pgrep nginx | head -1)
# Счётчики производительности
perf stat -p PID -- sleep 5
# cache-misses, branch-misses, context-switches
# Flame graph (установить FlameGraph от Brendan Gregg)
perf record -F 99 -ag -- sleep 10
perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg
Диагностика сетевых задержек
# Измеряем задержки на разных уровнях
# 1. ICMP (сеть)
ping -c 100 server-ip | tail -3
# 2. TCP handshake (OS + сеть)
hping3 -S -c 100 -p 80 server-ip | tail -5
# 3. HTTP time_to_first_byte (приложение)
curl -o /dev/null -s -w "
dns: %{time_namelookup}s
connect: %{time_connect}s
tls: %{time_appconnect}s
ttfb: %{time_starttransfer}s
total: %{time_total}s
" https://myapp.example.com
# 4. Детальная трассировка HTTP
curl -v --trace-time https://myapp.example.com 2>&1 | head -50
# 5. Tcpdump для анализа конкретного запроса
tcpdump -i eth0 -w request.pcap host client-ip and port 443
# Открываем в Wireshark для детального анализа
# 6. Статистика задержки на уровне сокета
ss -ti # socket timing info
Инструменты для экстренной диагностики: шпаргалка
# Процессы
ps auxwf # дерево процессов
pstree -pu # красивое дерево
pgrep -a nginx # найти процессы
lsof -p PID # файлы процесса
lsof -i :80 # кто слушает порт 80
fuser -n tcp 80 # pid процесса на порту
# Файловая система
lsof +D /var/log # кто держит файлы в директории
inotifywait -m /etc/passwd # слежка за изменениями
find / -newer /tmp/stamp -type f 2>/dev/null # что изменилось с timestamp
# Сеть
tcpdump -i any port 80 -nn -q
conntrack -L | wc -l # количество трекируемых соединений
nmap -sV localhost # сканируем себя
# История команд в случае инцидента
history | grep -i "rm\|mv\|chmod\|dd" | tail -20
last | head -20 # последние логины
lastb | head -10 # неудачные логины
who # кто сейчас залогинен
w # что они делают
Диагностика — это смесь знаний, методологии и опыта. Самые ценные навыки: не паниковать, следовать методологии USE, измерять прежде чем делать выводы, и помнить что 90% проблем с производительностью — это диск, память или сеть, а не код.
Create an account or sign in to leave a review
There are no reviews to display.