Демоны / Настройка супервизора systemd

И так в прошлом задании мы сделали консольное приложение которое мониторит файлик и реагирует на его изменения.

У нас есть небольшое неудобство, заключающееся в том, что нам приходится запускать наше приложение вручную.

Чтобы полноценно демонизировать процесс его надо подключить к супервизору. Супервизор — это главный процесс в linux который мониторит все процессы, подчищает некорректно удаленные, а также управляет различными сервисами, логирует их исполнение, следит чтобы они перезапускались в случае непредвиденных ситуаций и т.п.

Для управления супервизором есть две основные команды.

Например, можно запустить команду systemctl list-units --type=service и увидеть список всех сервисов доступных в системе

В крайнем левом столбце видно название сервисов. Каждому сервису как правило сопоставлена некоторая программа, которую супервизор должен запустить

Чтобы супервизор мог понять кого запускать. Есть специальные файлик описывающие сервис. Мы можем быстро посмотреть файлик сопоставленный сервису если напишем команду

sudo systemctl cat ИМЯ_СЕРВИСА

например можно глянуть сервис который ответственен за общение VirtualBox и нашего линукса

sudo systemctl cat vboxadd.service

В выводе видим две наиболее интересных строчки. Первая это путь к файлу в котором содержится описание сервиса /lib/systemd/system/vboxadd.service. Скоро мы сделаем свой такой.

И вторая это строчка, начинающаяся с ExecStart которая собственно и указывает которую команду надо запустить при старте сервиса.

Можно глянуть кстати, что лежит в папке /lib/systemd/system и увидеть, что там действительно есть такой файлик и еще куча других

ls /lib/systemd/system

каждый отвечает за какой-то свой сервис

Мы можем отредактировать каждый файлик либо прямо через nano (или какой-то у вас любимый редактор), типа

sudo nano /lib/systemd/system/vboxadd.service

либо используя systemctl, вот так

sudo systemctl edit --full vboxadd.service

Регистрация сервиса

И так, чтобы наш сервис подключить к супервизору создадим файлик my_daemon.service:

и теперь опишем в него сервис

[Unit]
Description=Заклинатель книг  # название сервиса

[Service]
User=m  # под каким юзером запускать
# рабочая директория, в ней будет искаться config.txt
WorkingDirectory=/home/m/projects/daemon/bin/Debug/net5.0 
# путь к скомпилированному файлу
ExecStart=/home/m/projects/daemon/bin/Debug/net5.0/daemon -f config.txt

[Install]
WantedBy=multi-user.target # хотим, чтобы запускалось при запуске системы

теперь нам надо подключить этот файлик к супервизору.

Сначала надо положить его в папку /etc/systemd/system, чтобы проще было редактировать мы просто создадим ссылку на наш файл вот так:

sudo ln -s $PWD/my_daemon.service /etc/systemd/system

когда создаете ссылку на файл очень важно, чтобы путь к исходному файлу был полный.

Переменная $PWD как раз хранит полный путь текущей папки.

теперь надо перегрузить кеш сервисов

sudo systemctl daemon-reload

моя программа выглядит вот так

using System;
using System.IO;
using System.Runtime.InteropServices;

namespace daemon
{
    class Program
    {
        static void Main(string[] args)
        {
            using var watcher = new FileSystemWatcher(@"/home/m");
            watcher.NotifyFilter = NotifyFilters.LastWrite;
            watcher.Filter = "file.txt"; 

            watcher.Changed += OnChanged;
            watcher.EnableRaisingEvents = true;

            Console.WriteLine("Нажмите Enter для выхода");
            Console.ReadLine();
        }

        private static void OnChanged(object sender, FileSystemEventArgs e)
        {
            if (e.ChangeType != WatcherChangeTypes.Changed)
            {
                return;
            }
            Console.WriteLine($"Изменился: {e.FullPath}");
        }
    }
}

то есть ожидает нажатия клавиши юзером. У нашей программы юзера нет, так что посмотрим, как оно отработает.

Момент истины, запускаем наш сервис:

sudo systemctl start my_daemon.service

теперь попробуем узнать статус нашего сервиса

sudo systemctl status my_daemon.service

получится что-то такое

то есть наш процесс успешно отработал, не стал ждать ввода от пользователя и успешно завершился. Его состояние inactive, то бишь не работает.

А мы вроде как хотели, чтобы он там сидел всякие файлы мониторил.

Реализуем бесконечный цикл

Короче, дело в том, что сервисам недоступен поток ввода, поэтому программа не останавливается в ожидании ввода, а просто завершает свою работу.

В общем чтобы она не умирала, надо воспользоваться древним приемом с использованием бесконечного цикла типа так:

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading; # работа с потоками

namespace daemon
{
    class Program
    {
        static void Main(string[] args)
        {
            using var watcher = new FileSystemWatcher(@"/home/m");
            watcher.NotifyFilter = NotifyFilters.LastWrite;
            watcher.Filter = "file.txt"; 

            watcher.Changed += OnChanged;
            watcher.EnableRaisingEvents = true;

            while(true) {
                Thread.Sleep(2000);
                Console.WriteLine("Мониторю");
            }

            //Console.WriteLine("Нажмите Enter для выхода");
            //Console.ReadLine();
        }

        private static void OnChanged(object sender, FileSystemEventArgs e)
        {
            if (e.ChangeType != WatcherChangeTypes.Changed)
            {
                return;
            }
            Console.WriteLine($"Изменился: {e.FullPath}");
        }
    }
}

собираем

dotnet build

и запускаем сервис по новой

sudo systemctl start my_daemon.service

Во, теперь процесс в состоянии running и начинает писать в лог. Можно даже чего-нибудь в файл записать и увидеть, как сервис на это реагирует

По умолчанию правда выводится не весь лог, а только примерно последние 10 записей.

Смотрим лог

Чтобы посмотреть полный лог, надо воспользоваться утилитой journalctl.

Для просмотра лога по своему юниту надо ввести вот такую команду

journalctl -u my_daemon.service

чтобы выйти из лога нажмите q

если хочется смотреть лог в режиме реального времени, то можно добавит флаг -f (–follow)

journalctl -u my_daemon.service -f

кстати, чтобы остановить процесс воспользуетесь командой

sudo systemctl stop my_daemon.service

в логе зафиксируется момент и причина остановки.

И еще один важный момент чтобы ваш демон действительно запустился при перезапуске системы надо активировать сервис командой enable

sudo systemctl enable my_daemon.service

можно попробовать перезапустить систему и проверить что сервис действительно перезапустился.

4.2

Настроить супервизор systemd для запуска вашего демона. В логи демона фиксировать моменты изменения данных в файле, дублировать, то что выводете в файл, в лог.