$ devops_cheatsheet.sh
⚙️Процессы
LINUX
ps aux
Все запущенные процессы. Колонки: USER, PID, %CPU, %MEM, COMMAND
ps aux | grep nginx
Найти процессы по имени. grep отфильтрует строки
top -bn1 | head -20
Снимок нагрузки прямо сейчас. %CPU — кто жрёт процессор
pgrep -f miner
Найти PID по части имени команды. Вернёт число(а)
kill 1234
Вежливо попросить завершиться (сигнал 15 SIGTERM)
kill -9 1234
Принудительно убить, нельзя проигнорировать (SIGKILL)
pkill -f miner.py
Убить процесс по имени, без поиска PID
Порядок: сначала kill, если не помогло — kill -9
💾Диск и место
LINUX
df -h
Место на всех дисках. Use% — заполненность. Если >90% — проблема
df -h /
Место только на корневом разделе
du -sh /var/*
Размер каждой папки в /var. -s=итого, -h=читаемо
du -sh /* | sort -rh | head -10
Топ-10 самых больших папок в системе
truncate -s 0 /var/log/syslog
Очистить файл не удаляя его. Безопаснее чем rm для логов
ls -lh /var/log/
Список файлов с размерами. Ищем подозрительно большие
Filesystem Size Used Avail Use% Mounted /dev/vda1 20G 18G 824M 96% / ← проблема! /dev/vda2 500M 312M 152M 68% /boot ← норм
🌐Сеть и порты
СЕТЬ
ss -tlnp
Какие порты слушают и какая программа. t=TCP l=listen n=числа p=процесс
ss -tlnp | grep 8080
Кто занял конкретный порт
netstat -tlnp
То же что ss, старый вариант
curl -s http://localhost:8080/
Проверить что сервис отвечает. -s = без прогресс-бара
curl -I http://localhost/
Только заголовки ответа (HTTP-статус, сервер...)
ping -c 3 8.8.8.8
Проверить доступность хоста
Iptables (файрвол)
iptables -L INPUT -n
Правила файрвола для входящего трафика
iptables -A INPUT -p tcp --dport 2222 -j ACCEPT
Открыть порт 2222
netfilter-persistent save
Сохранить правила iptables после перезагрузки
Timeout vs Refused: timeout = файрвол DROP (молча выбросил). connection refused = порт закрыт, сервис не слушает
🔧systemd — управление сервисами
SYSTEMD
systemctl status nginx
Состояние сервиса + последние логи. Главная команда диагностики
systemctl start nginx
Запустить прямо сейчас
systemctl stop nginx
Остановить
systemctl restart nginx
Перезапустить (stop + start)
systemctl reload nginx
Перечитать конфиг без остановки (если поддерживается)
systemctl enable nginx
Добавить в автозапуск при загрузке
systemctl disable nginx
Убрать из автозапуска
systemctl --failed
Показать все упавшие сервисы. Первое что смотреть
systemctl daemon-reload
Перечитать unit-файлы после редактирования. Обязательно после изменений
journalctl -u nginx -n 50
Последние 50 строк лога сервиса
journalctl -u nginx -f
Следить за логом в реальном времени (-f = follow)
● nginx.service - nginx web server Active: active (running) since ... ← хорошо Active: failed (Result: exit-code) ← плохо, смотрим лог ниже
enable ≠ start. enable = будет стартовать при загрузке. start = запустить сейчас. Нужны оба!
📄Шаблон unit-файла
SYSTEMD
# /etc/systemd/system/myapp.service [Unit] Description=My Web Application After=network.target # ждать сети [Service] Type=simple User=www-data # не root! WorkingDirectory=/opt/app ExecStart=/opt/app/start.sh Restart=on-failure # перезапуск при падении RestartSec=5 [Install] WantedBy=multi-user.target
После создания файла
systemctl daemon-reload
1. Перечитать файлы
systemctl enable --now myapp
2. Включить И запустить одной командой
🔀nginx
NGINX
nginx -t
Проверить конфиг на ошибки. Запускать ВСЕГДА перед reload/restart
nginx -T | grep error
Показать весь конфиг с включёнными файлами
systemctl reload nginx
Применить конфиг без прерывания соединений
Шаблон конфига /etc/nginx/conf.d/app.conf
server { listen 80; server_name _; # любой домен location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
Частые ошибки
unknown directive
Опечатка в названии директивы. Смотри точное написание в документации
Address already in use
Порт 80 уже занят. ss -tlnp | grep :80 — кем?
Permission denied /var/log/nginx
Неправильный владелец папки логов. chown -R www-data /var/log/nginx
Алгоритм: nginx -t → исправить → nginx -t → systemctl reload nginx → curl localhost/
🔑SSH
SSH
sshd -t
Проверить конфиг sshd на ошибки (как nginx -t)
systemctl restart sshd
Применить изменения. Текущая сессия не обрвётся
Два порта в /etc/ssh/sshd_config
# было: #Port 22 # стало: Port 22 Port 2222
Проверка авторизованных ключей
cat ~/.ssh/authorized_keys
Ключи которым разрешён вход. Чужой ключ = backdoor!
cat /root/.ssh/authorized_keys
Ключи для root. Особенно подозрительно
> /root/.ssh/authorized_keys
Очистить файл (записать пустоту)
Параметры sshd_config
PermitRootLogin no
Запретить вход напрямую под root
PasswordAuthentication no
Только ключи, без паролей
⚠ Осторожно: не отключай PasswordAuthentication пока не убедился что ключ работает!
🚀Загрузка системы
BOOT
fstab — диски при загрузке
cat /etc/fstab
Список дисков которые монтируются при старте
blkid
Реальные UUID всех дисков. Сверяй с fstab!
findmnt --verify
Проверить fstab на ошибки. Должно быть "0 errors"
mount -a
Примонтировать всё из fstab прямо сейчас (тест)
# ОПАСНАЯ строка в fstab: UUID=99999999-DEAD-BEEF /mnt/data ext4 defaults 0 2 ↑ такого UUID нет → система зависнет при загрузке! # Исправление — закомментировать: #UUID=99999999-DEAD-BEEF /mnt/data ext4 defaults 0 2
GRUB — загрузчик
cat /etc/default/grub
Настройки GRUB. Смотри GRUB_TIMEOUT
GRUB_TIMEOUT=5
Показывать меню 5 секунд. 0 = опасно (нельзя выбрать запасное ядро)
update-grub
Применить изменения GRUB. Запускать после редактирования
Порядок действий при boot-проблеме: 1) Проверить fstab через blkid 2) Проверить GRUB_TIMEOUT 3) findmnt --verify 4) update-grub
🐍Python + venv
LINUX
python3 -m venv venv
Создать виртуальное окружение в папке venv
source venv/bin/activate
Войти в окружение. Появится (venv) в приглашении
pip install -r requirements.txt
Установить все зависимости из файла
pip list
Посмотреть что установлено
deactivate
Выйти из окружения
apt-get install -y python3-pip python3-venv
Установить pip и venv если нет
gunicorn
gunicorn --bind 0.0.0.0:8080 --workers 2 app:application
Запустить приложение. app=имя файла, application=переменная Flask
🕐Cron + места persistence
LINUX
Cron — планировщик задач
crontab -l
Показать задачи текущего пользователя
crontab -l -u root
Показать задачи root
crontab -e
Редактировать свои задачи
crontab -r -u root
Удалить ВСЕ задачи root. Осторожно!
# Формат cron: * * * * * команда │ │ │ │ └─ день недели (0-7) │ │ │ └─── месяц (1-12) │ │ └───── день (1-31) │ └─────── час (0-23) └───────── минута (0-59) */5 * * * * — каждые 5 минут 0 3 * * * — каждый день в 03:00 @reboot — при каждом старте системы
Где искать зловреда (persistence)
crontab -l -u root
1. Cron root
ls /etc/cron.d/ /etc/cron.daily/
2. Системные крон-задачи
cat /root/.ssh/authorized_keys
3. Чужие SSH-ключи
find /tmp /var/tmp -name "*.py" -o -name "*.sh" 2>/dev/null
4. Скрипты во временных папках
systemctl list-units --type=service | grep -v "loaded active"
5. Подозрительные сервисы
cat /etc/rc.local
6. Старый способ автозапуска
📜Bash: основы скриптинга
BASH
Структура скрипта
#!/bin/bash # обязательно первая строка set -e # останавливаться при ошибке VARIABLE="значение" # переменная (без пробелов у =) RESULT=$(команда) # результат команды в переменную DATE=$(date +"%Y%m%d_%H%M%S") # дата/время echo "$VARIABLE" # вывод переменной echo "[$(date)] сообщение" # лог с временем
Условия
if [ $? -eq 0 ]; then # если код выхода = 0 (успех) echo "OK" else echo "Ошибка" >&2 # писать в stderr exit 1 fi # Операторы сравнения чисел: -eq равно -ne не равно -gt больше -lt меньше -ge ≥ -le ≤ # Строки: [ "$A" = "$B" ] равны [ "$A" != "$B" ] не равны [ -z "$A" ] пустая строка [ -f "/path" ] файл существует [ -d "/path" ] директория существует
Циклы
# for for i in {1..5}; do echo $i done # while + read (читать строки) ls -1 /var/backups/*.gz | while read FILE; do rm -f "$FILE" done
tar — архивы
tar -czf arch.tar.gz /opt/app
c=создать, z=сжать, f=файл
tar -xzf arch.tar.gz
x=распаковать
tar -tzf arch.tar.gz
t=посмотреть содержимое
📁Файлы, права, поиск
LINUX
Права доступа
chmod +x script.sh
Дать право на выполнение (запуск)
chmod 755 file
rwxr-xr-x: владелец всё, остальные читают и запускают
chmod 644 file
rw-r--r--: владелец читает/пишет, остальные только читают
chown www-data:www-data /opt/app
Сменить владельца файла/папки
chown -R www-data /opt/app
-R = рекурсивно (вся папка со вложениями)
ls -la
Подробный список с правами, владельцем, размером
Поиск файлов
find /tmp -name "*.py" 2>/dev/null
Найти все .py файлы в /tmp
find / -name "authorized_keys" 2>/dev/null
Найти все authorized_keys в системе
grep -r "miner" /etc/ 2>/dev/null
Найти слово miner во всех файлах в /etc
sed — редактирование файлов
sed -i 's/старое/новое/' file
Заменить первое вхождение в файле. -i = изменить файл
sed -i 's/старое/новое/g' file
Заменить ВСЕ вхождения (g = global)
sed -i '/строка/d' file
Удалить строку содержащую «строка»
🔍Логи и отладка
ОТЛАДКА
journalctl -u webapp -n 100
Последние 100 строк лога сервиса
journalctl -xe
Общий системный лог с объяснениями. Смотреть при падении
tail -f /var/log/nginx/error.log
Следить за ошибками nginx в реальном времени
tail -100 /var/log/syslog
Последние 100 строк системного лога
dmesg | tail -20
Сообщения ядра. Ошибки железа, диски
strace -p 1234
Следить за системными вызовами процесса. Глубокая отладка
lsof -p 1234
Какие файлы открыл процесс
lsof -i :8080
Кто использует порт 8080
Алгоритм диагностики упавшего сервиса
1. systemctl status myapp ← статус + последние строки лога 2. journalctl -u myapp -n 50 ← полный лог 3. cat /etc/systemd/system/myapp.service ← конфиг unit 4. bash /opt/app/start.sh ← запустить вручную, видеть ошибку 5. ss -tlnp | grep 8080 ← не занят ли порт
💿Готовый скрипт бэкапа
BASH
#!/bin/bash SOURCE="/opt/app" DEST="/var/backups/app" MAX=5 TS=$(date +"%Y%m%d_%H%M%S") FILE="${DEST}/backup_${TS}.tar.gz" mkdir -p "$DEST" tar -czf "$FILE" -C "$(dirname $SOURCE)" "$(basename $SOURCE)" if [ $? -ne 0 ]; then echo "ОШИБКА: бэкап не создан" >&2; exit 1 fi # Ротация: удалить старые если > MAX COUNT=$(ls -1 "$DEST"/backup_*.tar.gz 2>/dev/null | wc -l) if [ "$COUNT" -gt "$MAX" ]; then EXCESS=$((COUNT - MAX)) ls -1t "$DEST"/backup_*.tar.gz | tail -n "$EXCESS" | xargs rm -f fi echo "OK: $FILE (всего: $(ls -1 $DEST/backup_*.tar.gz | wc -l))"
chmod +x /opt/backup.sh && /opt/backup.sh
Сделать исполняемым и запустить
0 3 * * * /opt/backup.sh >> /var/log/backup.log 2>&1
В cron: каждый день в 03:00 с логированием
Финальная проверка
ЧЕКЛИСТ
# Прогони перед тем как написать "готово" ### Сервисы живые? systemctl status nginx webapp sshd | grep -E "Active:|●" ### Сайт отвечает? curl -s http://localhost/ | head -1 ### SSH на двух портах? ss -tlnp | grep sshd ### Автозапуск настроен? systemctl is-enabled nginx webapp sshd ### Диск в норме? df -h / | tail -1 ### Зловред мёртв? ps aux | grep -i miner | grep -v grep crontab -l -u root ### fstab валиден? findmnt --verify ### Файрвол настроен? iptables -L INPUT -n | grep -E "22|80|2222"
Правило: каждое исправление → проверь результат сразу. Не копи изменения вслепую.
💡Как вести себя на тесте
ТЕСТ
✓ Сначала осмотр — потом действие ps aux, df -h, systemctl --failed, top ✓ Пиши в Slack КАЖДЫЙ шаг "Вижу что nginx упал, смотрю journalctl..." "Нашёл занятый порт 8080, убиваю PID 2101" "Проверил — curl возвращает 200" ✓ Если застрял — напиши об этом "Смотрю /etc/fstab, пока не понимаю почему..." ✓ После каждого исправления — проверь Починил nginx → nginx -t → systemctl status nginx → curl ✓ Ищи больше чем просят Нашёл что-то лишнее? Напиши. Это плюс. ✗ Не молчи и не торопись ✗ Не применяй изменения без проверки ✗ Не удаляй файлы без понимания зачем ✗ Не делай rm -rf без крайней необходимости
🐳Docker
DOCKER
docker ps
Запущенные контейнеры. -a = все включая остановленные
docker images
Локальные образы
docker run -d --name app -p 8080:80 nginx:alpine
-d=фон, --name=имя, -p=порт хост:контейнер
docker logs -f app
Следить за логами. --tail 100 = последние 100 строк
docker exec -it app sh
Войти в контейнер. Если нет sh — попробуй bash
docker stop app && docker rm app
Остановить и удалить. docker rm -f app = принудительно
docker build -t myapp:1.0 .
Собрать образ из Dockerfile в текущей папке
docker inspect app
Подробная информация: сети, volumes, env, IP
Очистка
docker system prune -f
Удалить остановленные контейнеры + dangling образы
docker system prune -af
+ все неиспользуемые образы. Осторожно!
docker volume prune -f
Удалить неиспользуемые volumes
Dockerfile — минимальный шаблон
FROM python:3.11-slim # базовый образ WORKDIR /app # рабочая директория COPY requirements.txt . RUN pip install -r requirements.txt # слой кэшируется COPY . . ENV PORT=8080 # переменная окружения EXPOSE 8080 # документация (не открывает порт!) CMD ["python", "app.py"] # команда запуска
Порядок в Dockerfile важен: редко меняющееся — выше (кэш переиспользуется), часто меняющееся — ниже.
📦Docker Compose
DOCKER
docker compose up -d
Запустить все сервисы в фоне
docker compose down
Остановить и удалить контейнеры
docker compose down -v
+ удалить volumes. Данные БД пропадут!
docker compose logs -f app
Логи конкретного сервиса
docker compose ps
Состояние всех сервисов
docker compose exec app sh
Войти в контейнер сервиса
docker compose build --no-cache
Пересобрать образы без кэша
docker compose pull
Обновить образы из registry
Минимальный docker-compose.yml
services: app: build: . ports: - "8080:8080" environment: - DB_HOST=db depends_on: - db restart: unless-stopped db: image: postgres:16 environment: POSTGRES_PASSWORD: secret volumes: - pgdata:/var/lib/postgresql/data volumes: pgdata:
☸️kubectl
K8S
kubectl get pods -n ns
Поды в неймспейсе. -A = во всех сразу
kubectl logs -f pod-name -n ns
Логи пода в реальном времени. -c = конкретный контейнер
kubectl describe pod pod-name -n ns
Подробный статус: причина CrashLoop/Pending, события
kubectl exec -it pod-name -n ns -- sh
Войти в под (или bash)
kubectl apply -f file.yaml
Применить манифест (создать или обновить)
kubectl delete -f file.yaml
Удалить ресурсы из манифеста
kubectl rollout restart deployment/myapp -n ns
Перезапустить деплоймент (rolling update)
kubectl port-forward pod/myapp 8080:80 -n ns
Пробросить порт пода на localhost:8080
kubectl get events -n ns --sort-by='.lastTimestamp'
События неймспейса по времени. Главный инструмент отладки
kubectl top pods -n ns
CPU и память подов (нужен metrics-server)
Диагностика упавшего пода
1. kubectl get pods -A | grep -v Running ← что не ок? 2. kubectl describe pod POD -n NS ← причина в Events 3. kubectl logs POD -n NS --previous ← лог прошлого запуска 4. kubectl get events -n NS --sort-by='.lastTimestamp'
🌿Git
GIT
git status
Изменённые и неотслеживаемые файлы
git log --oneline -10
Последние 10 коммитов в одну строку
git diff
Что изменилось (не staged). --staged = что в индексе
git stash
Спрятать незакоммиченные изменения
git stash pop
Достать последний stash
git checkout -b feature/branch
Создать и переключиться на новую ветку
git reset --soft HEAD~1
Отменить последний коммит, оставить изменения
git reset --hard HEAD~1
Отменить коммит и выбросить изменения. Необратимо!
git cherry-pick abc1234
Применить конкретный коммит в текущую ветку
git fetch origin && git rebase origin/main
Обновить ветку поверх main без merge-коммита
git reset --hard опасна: изменения пропадут. Сначала сделай git stash если не уверен.
🌍HTTP статус-коды
СЕТЬ
2xx — Успех 200 OK — всё хорошо 201 Created — ресурс создан (POST) 204 No Content — успех, тела нет (DELETE) 3xx — Редирект 301 Moved Permanently — постоянный редирект (меняй ссылки) 302 Found — временный редирект 304 Not Modified — кэш актуален, тело не отправляется 4xx — Ошибка клиента 400 Bad Request — кривой запрос (синтаксис, параметры) 401 Unauthorized — нужна авторизация 403 Forbidden — авторизован, но доступа нет 404 Not Found — ресурса нет 405 Method Not Allowed — метод (POST/GET) не поддерживается 429 Too Many Requests — rate limit 5xx — Ошибка сервера 500 Internal Server Error — упал backend, смотри логи 502 Bad Gateway — nginx не достучался до backend 503 Service Unavailable — сервис перегружен или упал 504 Gateway Timeout — backend ответил слишком медленно
502 в nginx = upstream не отвечает. Проверь: запущен ли сервис, правильный ли порт в proxy_pass.
📊Память и ресурсы
LINUX
free -h
RAM: total / used / available. available — реально свободна для приложений
vmstat 1 5
CPU, память, I/O каждую секунду, 5 раз. wa=ожидание диска
iostat -x 1 3
Дисковый I/O. %util близко к 100% = диск перегружен
uptime
Load average за 1 / 5 / 15 минут
nproc
Количество CPU-ядер. Норма load average < этого числа
cat /proc/meminfo | grep -E "MemTotal|MemAvailable"
Память в байтах напрямую из ядра
# Load average — что это: uptime → load average: 0.5, 0.7, 0.4 1м 5м 15м # Число = длина очереди к CPU 1.0 на 4 ядра ← 25% нагрузки, отлично 8.0 на 4 ядра ← очередь в 2× длиннее ядер, тормоза
🔩Переменные окружения
LINUX
env
Все переменные окружения текущего процесса
printenv PATH
Значение конкретной переменной
export MY_VAR=value
Установить для текущей сессии и дочерних процессов
echo $MY_VAR
Прочитать значение
unset MY_VAR
Удалить переменную
source .env
Загрузить переменные из файла в текущий шелл
MY_VAR=test ./script.sh
Передать переменную только этому запуску (не в сессию)
В systemd unit-файле
[Service] Environment="DB_HOST=localhost" "PORT=8080" # прямо в unit EnvironmentFile=/opt/app/.env # или из файла # Формат .env для systemd (без кавычек, без export): DB_HOST=localhost DB_PASSWORD=secret PORT=8080
👤Пользователи и sudo
LINUX
whoami && id
Кто я и в каких группах
useradd -m -s /bin/bash username
-m=создать домашнюю папку, -s=shell
passwd username
Установить пароль пользователю
usermod -aG sudo username
Добавить в группу. -a=append (не затирать другие группы!)
usermod -aG docker username
Добавить в группу docker (нужен re-login)
su - username
Переключиться на пользователя (с его окружением)
sudo -u www-data whoami
Выполнить команду от имени другого пользователя
visudo
Редактировать /etc/sudoers безопасно (проверяет синтаксис)
grep username /etc/passwd
Запись: логин:x:UID:GID:коммент:home:shell
⚠ usermod без -a: usermod -G docker user удалит все остальные группы! Всегда используй -aG.
📡curl — расширенный
СЕТЬ
curl -v http://example.com
Verbose: все заголовки запроса и ответа
curl -L http://example.com
Следовать редиректам (301/302)
curl -w "%{http_code}" -o /dev/null -s http://
Только HTTP-код ответа. Удобно в скриптах
curl -s http://api/ | jq .
Красивый вывод JSON (apt install jq)
curl -o file.tar.gz http://example.com/file
Скачать файл
POST запросы
# JSON POST curl -X POST \ -H "Content-Type: application/json" \ -d '{"key":"value"}' \ http://api/endpoint # С авторизацией curl -H "Authorization: Bearer TOKEN" http://api/ # Форма curl -d "user=admin&pass=secret" http://example.com/login # Тест без DNS (обход резолвинга) curl --resolve example.com:443:1.2.3.4 https://example.com/
🖥️tmux
LINUX
tmux new -s work
Создать именованную сессию
tmux attach -t work
Подключиться к сессии. tmux ls = список
tmux kill-session -t work
Завершить сессию
Сочетания клавиш (префикс Ctrl+B)
Ctrl+B, D — отсоединиться (сессия продолжает жить) Ctrl+B, C — новое окно Ctrl+B, N / P — следующее / предыдущее окно Ctrl+B, 0..9 — переключиться на окно по номеру Ctrl+B, % — разделить вертикально Ctrl+B, " — разделить горизонтально Ctrl+B, ← → ↑ ↓ — переключиться между панелями Ctrl+B, Z — развернуть/свернуть панель на весь экран Ctrl+B, [ — режим прокрутки (выход — Q)
Сессии переживают disconnect. Запусти долгую команду в tmux — можно закрыть терминал и вернуться через attach.