Продвинутый

Навыки реальных инженеров: практика из .agents directory

Алексей Кузнецов
Алексей Кузнецов
Системный администратор14 ноября 2026 г.11 мин чтения

Статья раскрывает реальные инженерные навыки, взятые из .agents directory, с примерами конфигураций, автоматизации и лучших практик для системных инженеров.

Навыки реальных инженеров: практика из .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 остаётся базой для развёртывания как полноценных ВМ, так и лёгких контейнеров. Пример создания виртуальной машины, названной в честь персонажа советского мультфильма, выглядит так:

bash
qm create 101 \
  --name WINNIE-PUH \
  --memory 4096 \
  --cores 2 \
  --net0 virtio,bridge=vmbr0 \
  --ide2 local-lvm:cloud-init

После установки ОС удобно использовать LXC‑контейнеры для сервисов, которые требуют изоляции, но не нуждаются в полной виртуализации. Пример конфигурации Docker‑Compose для небольшого стека (Nginx + Nextcloud + Plex) выглядит так:

yaml
version: "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:

Запуск стека происходит одной командой:

bash
docker compose up -d

Для оркестрации небольших кластеров Kubernetes‑платформ, таких как k3s, установка выглядит просто:

bash
curl -sfL https://get.k3s.io | sh -

После этого можно разворачивать манифесты в виде Deployment‑ов, а для автоматического обновления использовать kubectl rollout restart. Всё это обеспечивает предсказуемость конфигураций, о которой я часто говорю коллегам.

Хранение и резервное копирование

Второй ключевой аспект — надёжное хранилище и система резервного копирования. На моём домашнем сервере на базе TrueNAS (48 ТБ полезного объёма) используется ZFS с включённым сnapshotting. Пример создания моментального снимка:

bash
zfs snapshot tank/data@$(date +%Y-%m-%d)

Для автоматической репликации данных между двумя TrueNAS‑узлами удобно настроить zfs send/zfs receive в cron:

bash
0 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 часа):

cron
0 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:

bash
sudo 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:

bash
sudo sed -i 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart sshd

Теперь вход возможен только по приватному ключу, что исключает brute‑force атаки.

Настройка fail2ban – автоматически добавляет правила iptables при обнаружении множества неудачных попыток входа:

bash
sudo 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 строк логов именно этого сервиса, используйте:

bash
sudo journalctl -u sensor-collector.service -n 50 --no-pager

Метрики и алерты

Мониторинг — это не просто красивые графики в Grafana. Это инструмент, который говорит вам, когда пора вмешаться, прежде чем система превратится в «тыкву». В моей практике мониторинг строится на принципе: «Если ты не можешь это измерить, ты не можешь этим управлять».

Для домашнего сервера оптимальным стеком остается связка Prometheus + Node Exporter + Grafana. Node Exporter собирает низкоуровневые метрики (загрузка CPU, использование дисков, температура), а Prometheus забирает их по расписанию.

Однако сбор метрик бесполезен без алертинга. Главная ошибка новичков — завалить себя уведомлениями. Если ваш телефон разрывается от сообщений каждые пять минут, вы быстро научитесь их игнорировать. Это называется «усталость от алертов» (alert fatigue), и это опасно.

Я разделяю алерты на два типа:

  1. Critical: Требуют немедленного вмешательства (например, упал диск в ZFS-пуле или закончилось место на системном разделе).
  2. Warning: Можно посмотреть завтра утром (например, температура процессора выше средней, но в пределах нормы).

Пример конфигурации правила в Prometheus (alerting_rules.yml), которое реагирует на критическое заполнение диска:

yaml
groups:
- 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) не будет проигнорирована. Без этих настроек скрипт может продолжить выполнение с неверными данными, что часто приводит к повреждению конфигураций или потере важных файлов.

Поделиться:TelegramX / TwitterVK