Навыки реальных инженеров: практика из .agents directory
Статья раскрывает реальные инженерные навыки, взятые из .agents directory, с примерами конфигураций, автоматизации и лучших практик для системных инженеров.
Ключевые компетенции инженера
Техническая документация и код
Во-первых, документация — это не послесловие, а часть кода. Она хранится в том же репозитории, проходит review и version‑ируется вместе с проектом.
README должен содержать минимум:
- цель проекта;
- список зависимостей;
- пример запуска;
- ссылки на конфиги и скрипты.
yaml# docker-compose.yml
version: "3.9"
services:
web:
image: nginx:1.25-alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
restart: unless-stopped
ini# /etc/nginx/nginx.conf
server {
listen 80;
server_name example.local;
location / {
root /var/www/html;
index index.html;
}
# логируем запросы в journal
access_log syslog:server=unix:/dev/log,tag=nginx;
}
systemd‑юнит — пример простого сервиса, который гарантирует автозапуск и автоматический рестарт:
ini# /etc/systemd/system/myapp.service
[Unit]
Description=MyApp service
After=network.target
[Service]
ExecStart=/usr/local/bin/myapp
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Комментарии в скриптах делают их самодокументирующимися. Например, bash‑скрипт для резервного копирования:
bash#!/usr/bin/env bash
set -euo pipefail
# Создаём директорию для бэкапа с текущей датой
BACKUP_DIR="/backup/$(date +%F)"
mkdir -p "$BACKUP_DIR"
# Копируем файлы, сохраняем права и ссылки
rsync -a --delete /etc/myapp/ "$BACKUP_DIR/etc/"
rsync -a /var/lib/myapp/ "$BACKUP_DIR/var/"
echo "Бэкап завершён: $BACKUP_DIR"
Всё это должно находиться в репозитории, а не в отдельном документе, иначе будет расти разрыв между кодом и его описанием.
Автоматизация и CI/CD
Автоматизация — это способ избавиться от «ручных» действий и обеспечить предсказуемость. Основные инструменты: Ansible для конфигурации, CI/CD‑платформы для сборки и деплоя.
Ansible‑playbook для установки Docker и Docker‑Compose на Ubuntu:
yaml# playbook.yml
- hosts: servers
become: true
vars:
docker_version: "24.0.6"
tasks:
- name: Устанавливаем зависимости
apt:
name:
- ca-certificates
- curl
- gnupg
- lsb-release
state: present
update_cache: yes
- name: Добавляем официальный репозиторий Docker
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
- name: Добавляем репозиторий Docker
apt_repository:
repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
state: present
- name: Устанавливаем Docker
apt:
name: "docker-ce={{ docker_version }}~3ubuntu1"
state: present
update_cache: yes
- name: Устанавливаем Docker‑Compose
get_url:
url: https://github.com/docker/compose/releases/download/2.27.0/docker-compose-linux-x86_64
dest: /usr/local/bin/docker-compose
mode: '0755'
CI/CD‑пример — pipeline в GitLab, который собирает образ, проверяет его, а затем деплоит на сервер через SSH:
yaml# .gitlab-ci.yml
stages:
- build
- test
- deploy
build_image:
stage: build
image: docker:24.0.6
services:
- docker:24.0.6-dind
script:
- docker build -t myapp:$CI_COMMIT_SHA .
artifacts:
paths:
- docker-compose.yml
docker_test:
stage: test
image: docker:24.0.6
services:
- docker:24.0.6-dind
script:
- docker run --rm myapp:$CI_COMMIT_SHA docker version
deploy:
stage: deploy
image: curlimages/curl:8.5.0
variables:
SSH_PRIVATE_KEY: "$SSH_PRIVATE_KEY"
before_script:
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- ssh-keyscan -H my.server.com >> ~/.ssh/known_hosts
script:
- docker pull myregistry.com/myapp:$CI_COMMIT_SHA
- ssh root@my.server.com "docker stop myapp && docker rm myapp && docker run -d --name myapp -p 80:80 myregistry.com/myapp:$CI_COMMIT_SHA"
only:
- main
Здесь важно, что каждый шаг idempotent: повторный запуск не ломает систему, а только приносит изменения. Тесты в pipeline гарантируют, что собранный образ работает, а деплой происходит без простоя.
Документация Docker
Ansible official docs
GitLab CI/CD pipeline
Эти практики позволяют держать инфраструктуру в порядке, быстро реагировать на изменения и минимизировать риск «пожара» за счёт системного подхода.
Инфраструктурные решения
Инфраструктурные решения
Виртуализация и контейнеры
Во-первых, виртуализация в Proxmox VE остаётся базой для развёртывания как полноценных ВМ, так и лёгких контейнеров. Пример создания виртуальной машины, названной в честь персонажа советского мультфильма, выглядит так:
bashqm create 101 \ --name WINNIE-PUH \ --memory 4096 \ --cores 2 \ --net0 virtio,bridge=vmbr0 \ --ide2 local-lvm:cloud-init
После установки ОС удобно использовать LXC‑контейнеры для сервисов, которые требуют изоляции, но не нуждаются в полной виртуализации. Пример конфигурации Docker‑Compose для небольшого стека (Nginx + Nextcloud + Plex) выглядит так:
yamlversion: "3.8"
services:
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
nextcloud:
image: nextcloud:latest
environment:
- MYSQL_PASSWORD=secret
volumes:
- nextcloud_data:/var/www/html
plex:
image: plexinc/pms-docker:latest
ports:
- "32400:32400"
volumes:
- ./plex/config:/config
volumes:
nextcloud_data:
Запуск стека происходит одной командой:
bashdocker compose up -d
Для оркестрации небольших кластеров Kubernetes‑платформ, таких как k3s, установка выглядит просто:
bashcurl -sfL https://get.k3s.io | sh -
После этого можно разворачивать манифесты в виде Deployment‑ов, а для автоматического обновления использовать kubectl rollout restart. Всё это обеспечивает предсказуемость конфигураций, о которой я часто говорю коллегам.
Хранение и резервное копирование
Второй ключевой аспект — надёжное хранилище и система резервного копирования. На моём домашнем сервере на базе TrueNAS (48 ТБ полезного объёма) используется ZFS с включённым сnapshotting. Пример создания моментального снимка:
bashzfs snapshot tank/data@$(date +%Y-%m-%d)
Для автоматической репликации данных между двумя TrueNAS‑узлами удобно настроить zfs send/zfs receive в cron:
bash0 3 * * * /sbin/zfs send -R tank/data@$(date +\%Y-\%m-\%d) | ssh user@backup-host zfs receive -F tank/backup/data
Если требуется более гибкое решение, я предпочитаю restic — оно поддерживает шифрование и удалённые репозитории. Пример скрипта резервного копирования restic:
bash#!/bin/bash
export RESTIC_REPOSITORY=/backup/restic
export RESTIC_PASSWORD=strong_password
restic backup /tank/data --exclude /tank/data/old
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
Запись в cron (ежедневно в 2 часа):
cron0 2 * * * /usr/local/bin/restic_backup.sh >> /var/log/restic.log 2>&1
Таким образом, хранение и резервное копирование становятся частью инфраструктурного процесса, а не after‑thought. Всё описанное выше — это проверенные практики, которые я применяю в своей лаборатории и рекомендую коллегам.
Безопасность и контроль доступа
Безопасность и контроль доступа
### Сетевые правила и firewall
Во-первых, для домашнего сервера проще начать с ufw – он уже включён в большинство дистрибутивов и позволяет задать правила в понятном виде.
Если требуется более гибкая настройка, лучше использовать nftables – современный заменяющий iptables, который поддерживает сложные цепочки и легко интегрируется в автоматизацию.
Пример базовой конфигурации nftables – разрешаем SSH (22) и HTTP (80) только из локальной подсети 192.168.1.0/24, остальной трафик блокируем:
bash# создаём таблицу и цепочку filter
sudo nft add table inet filter
sudo nft 'add chain inet filter input { type filter hook input priority 0; policy drop; }'
# разрешаем локальный трафик (loopback)
sudo nft add rule inet filter input iif "lo" accept
# разрешаем traffic из локальной подсети к SSH и HTTP
sudo nft add rule inet filter input ip saddr 192.168.1.0/24 tcp dport 22 accept
sudo nft add rule inet filter input ip saddr 192.168.1.0/24 tcp dport 80 accept
# включаем логирование отклонённых пакетов (по желанию)
sudo nft add rule inet filter input log prefix "nftables: " group 0
# сохраняем правила (на Ubuntu)
sudo nft list ruleset > /etc/nftables.conf
Важно: после изменения правил проверяйте их через
sudo nft list ruleset. Ошибки в синтаксисе приведут к блокировке всех соединений, поэтому лучше тестировать в отдельном терминале.
Для простых задач удобно использовать ufw:
bashsudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp
sudo ufw allow 80/tcp
sudo ufw enable
Источник: официальная документация nftables – https://wiki.nftables.org/
### Аутентификация и авторизация
Система аутентификации в домашнем сервере обычно строится вокруг SSH. Надёжнее всего отключить парольный вход и использовать ключи, а также добавить механизм блокировки повторных попыток.
Отключаем password‑auth в sshd_config:
bashsudo sed -i 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
Теперь вход возможен только по приватному ключу, что исключает brute‑force атаки.
Настройка fail2ban – автоматически добавляет правила iptables при обнаружении множества неудачных попыток входа:
bashsudo apt install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo sed -i 's/^#\[sshd\]/\[sshd\]/' /etc/fail2ban/jail.local
sudo sed -i 's/^#enabled = true/enabled = true/' /etc/fail2ban/jail.local
sudo systemctl restart fail2ban
Конфигурация jail.local (пример):
ini[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
Для централизованной авторизации в небольших сетях удобно использовать LDAP или FreeIPA. Пример простого LDAP‑конфигурационного файла для sshd (включается через PAM):
bash# /etc/ssh/sshd_config
AuthenticationMethods publickey,keyboard-interactive
PubkeyAuthentication yes
PasswordAuthentication no
bash# /etc/pam.d/sshd
auth required pam_unix.so
auth sufficient pam_ldap.so
После перезапуска sshd (sudo systemctl restart sshd) пользователи смогут входить как по ключу, так и по LDAP‑учётным записям.
Практическая точка зрения: если сервер находится в домашней сети, достаточно локального пользователя с ключом и fail2ban. Для более крупных инфраструктурных сред рекомендуется интегрировать LDAP/SSO и мониторить логи через centralized syslog.
Источник: OpenSSH manual – https://man.openbsd.org/sshd_config
Итоги
- Firewall – начните с ufw для простоты, переходите на nftables, когда понадобится гибкость.
- SSH – отключайте password‑auth, используйте ключи, включайте fail2ban.
- Центральная авторизация – LDAP/FreeIPA подходит, если планируется управление несколькими серверами.
Соблюдая эти практики, вы обеспечите предсказуемость и надёжность доступа к своим домашним серверам.
Мониторинг и управление состоянием
Системные сервисы и systemd
Когда мы говорим об управлении состоянием сервера, первой точкой опоры должен быть systemd. Я часто вижу, как инженеры пытаются запускать свои скрипты или контейнеры через crontab или простые screen/tmux сессии. С практической точки зрения — это путь к непредсказуемости. Если процесс упадет из-за нехватки памяти или ошибки в коде, cron о нём не узнает, а ваш сервис просто перестанет работать.
Для обеспечения надёжности я всегда упаковываю любое кастомное решение в unit-файл. Это даёт нам контроль: автоматический перезапуск, правильный порядок запуска зависимостей и, что самое важное, интеграцию с системным журналом.
Рассмотрим пример. Допустим, у нас есть небольшой сервис на Python, который собирает данные с датчиков в нашей домашней лаборатории. Вместо того чтобы запускать его вручную, создадим unit-файл /etc/systemd/system/sensor-collector.service:
ini[Unit]
Description=Sensor Data Collector Service
# Ждём, пока поднимется сеть, чтобы сервис не упал при старте
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=sensor-user
Group=sensor-group
WorkingDirectory=/opt/sensor-collector
# Указываем путь к виртуальному окружению, чтобы не загрязнять систему
ExecStart=/opt/sensor-collector/venv/bin/python main.py
# Если сервис упадет, systemd поднимет его через 5 секунд
Restart=always
RestartSec=5s
# Ограничиваем ресурсы, чтобы один процесс не «съел» весь сервер
MemoryLimit=256M
CPUQuota=10%
[Install]
WantedBy=multi-user.target
После создания файла нужно обновить конфигурацию systemd и активировать сервис:
bash# Перечитываем конфигурацию, чтобы systemd увидел новый файл
sudo systemctl daemon-reload
# Включаем автозапуск при загрузке системы
sudo systemctl enable sensor-collector.service
# Запускаем сервис прямо сейчас
sudo systemctl start sensor-collector.service
Если что-то пойдёт не так, не нужно гадать, что случилось. Все логи будут доступны через journalctl. Чтобы посмотреть последние 50 строк логов именно этого сервиса, используйте:
bashsudo journalctl -u sensor-collector.service -n 50 --no-pager
Метрики и алерты
Мониторинг — это не просто красивые графики в Grafana. Это инструмент, который говорит вам, когда пора вмешаться, прежде чем система превратится в «тыкву». В моей практике мониторинг строится на принципе: «Если ты не можешь это измерить, ты не можешь этим управлять».
Для домашнего сервера оптимальным стеком остается связка Prometheus + Node Exporter + Grafana. Node Exporter собирает низкоуровневые метрики (загрузка CPU, использование дисков, температура), а Prometheus забирает их по расписанию.
Однако сбор метрик бесполезен без алертинга. Главная ошибка новичков — завалить себя уведомлениями. Если ваш телефон разрывается от сообщений каждые пять минут, вы быстро научитесь их игнорировать. Это называется «усталость от алертов» (alert fatigue), и это опасно.
Я разделяю алерты на два типа:
- Critical: Требуют немедленного вмешательства (например, упал диск в ZFS-пуле или закончилось место на системном разделе).
- Warning: Можно посмотреть завтра утром (например, температура процессора выше средней, но в пределах нормы).
Пример конфигурации правила в Prometheus (alerting_rules.yml), которое реагирует на критическое заполнение диска:
yamlgroups:
- name: disk_alerts
rules:
- alert: DiskSpaceCritical
# Проверяем свободное место на всех смонтированных разделах
expr: (node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100 < 10
for: 5m
labels:
severity: critical
annotations:
summary: "Мало места на диске: {{ $labels.mountpoint }}"
description: "Свободного места осталось меньше 10% на {{ $labels.device }} ({{ $labels.mountpoint }})."
Здесь параметр for: 5m критически важен. Он защищает от «дребезга» — кратковременных скачков использования ресурсов, которые не являются проблемой. Мы реагируем только тогда, когда состояние сохраняется в течение пяти минут.
Для получения более глубокого понимания того, как правильно проектировать системы мониторинга, рекомендую изучить документацию Prometheus по Alertmanager. Это поможет настроить группировку алертов, чтобы вместо десяти сообщений об одном и том же сбое вы получили одно структурированное уведомление.
Часто задаваемые вопросы
Где лучше хранить конфигурационные файлы и скрипты
С практической точки зрения, всё, что управляет вашей инфраструктурой, должно находиться в системе контроля версий. Если вы храните конфиги в репозитории вместе с кодом, вы получаете предсказуемость: вы всегда знаете, какая версия настройки соответствует конкретной версии приложения. Это исключает ситуацию, когда после перезагрузки сервера сервис не поднимается из-за того, что кто-то вручную изменил файл в /etc/, но забыл об этом.
Зачем использовать journald для логирования сервисов
Использование journald через StandardOutput=journal в systemd-юнитах позволяет централизованно управлять логами и упрощает их поиск. Вам не нужно настраивать отдельные файлы для каждого микросервиса и следить за их ротацией — за это берёт на себя систему systemd-journald. С помощью команды journalctl вы можете быстро отфильтровать логи конкретного юнита, что значительно ускоряет диагностику при возникновении ошибок.
Как обеспечить надёжность запуска сервиса
Для обеспечения стабильности работы важно правильно настроить секцию [Service] в systemd-юните. Параметр Restart=on-failure гарантирует, что сервис будет перезапущен только в случае реальной ошибки, а не при штатной остановке. Также рекомендую использовать RestartSec, чтобы дать системе небольшую паузу перед повторной попыткой запуска — это предотвращает бесконечный цикл перезагрузок, если проблема носит затяжной характер.
Почему важно использовать флаги set -euo pipefail в скриптах
В Bash-скриптах использование set -euo pipefail превращает скрипт из «набора команд» в надёжный инструмент автоматизации. Флаг -e заставляет скрипт немедленно завершиться при любой ошибке, -u запрещает использование необъявленных переменных, а -o pipefail гарантирует, что ошибка в середине конвейера (pipe) не будет проигнорирована. Без этих настроек скрипт может продолжить выполнение с неверными данными, что часто приводит к повреждению конфигураций или потере важных файлов.