Демонизация сайта в systemd

В этом туториале вы сделаете небольшой супер-стабильный сайт. Если на сайте возникнет страшная ошибка, systemd запустит его заново. И даже если весь сервер выключат и включат заново, то systemd сам снова запустит ваш сайт.

Прежде чем приступить

Этот туториал требует некоторых знаний:

1. Загрузите сайт на сервер

В качестве сайта выступит одиночный HTML-файлик. Зайдите на сервер с помощью ssh, перейдите в папку /opt и скачайте заготовку сайта:

# cd /opt
# git clone https://github.com/devmanorg/phones-shop
# cd phones-shop
 Почему /opt?

Куда класть код — это старый и холиварный вопрос. Столько времени прошло, сколько форумов исписано, а одного простого ответа так и нет…

Вам точно не стоит класть код в домашний каталог суперпользователя /root/. Туда вы обычно попадаете, заходя на сервер по ssh. Если положите код в /root, то не избежите приключений с настройкой прав пользователей. Операционная система оберегает домашний каталог рута от посягательств других пользователей и запрещает им чтение файлов оттуда. Это станет проблемой при запуске Nginx, Gunicorn и других программ, часто работающих под другим пользователем.

Я советую класть код в папку /opt/{project}/. Это место специально выделено в ОС для программ, что собираются из исходников и живут обособлено от каталогов bin, lib и прочих.

Затем проверьте, что файлы скачались командой ls:

2. Запустите HTTP-сервер

Сам по себе файл в интернет не попадёт. Если набрать в браузере ip вашего сервера, он растеряется и не поймёт, что ему делать. Нужен кто-то, кто скажет серверу, какие файлы отдавать в такой ситуации. Этим кем-то и будет веб-сервер. В Python есть простое решение для таких случаев, называется http.server. Запускается он такой командой:

# python3 -m http.server --bind 164.90.239.48 --directory /opt/phones-shop 80

Проверьте, заработал ли ваш сайт. Зайдите на него через браузер:

image

Сайт работает! Но всё ли так гладко, как кажется?..

Теперь выйдите из терминала. Просто закройте окошко терминала или введите exit и нажмите Enter. Сайт перестал работать 😦

Всё потому что вы закрыли ssh-соединение, и теперь сервер остановил работу веб-сервера.

На каждое соединение сервер тратит свои мощности. Мощности у него ограниченные. Поэтому сервер всегда стремится сэкономить их, закрывая неработающие соединения. А оставлять открытые соединения небезопасно.

Нужно запустить HTTP-сервер в фоновом режиме работы. Тогда он продолжит работать, даже если вы выйдете с сервера.

3. Запустите HTTP-сервер в фоне

Программы, которые работают в фоновом режиме, называются демонами или программами в режиме демона.

Утилита systemd как раз умеет демонизировать программы, то есть запускать их в фоне. Но сначала systemd нужно настроить: сказать ему что и как демонизировать. Это делается в специальных файлах с инструкциями для systemd. Такие файлы называют юнитами.

Юниты хранятся в папке /etc/systemd/system. Перейдите в неё и создайте новый файл с названием phones-shop.service. Это и будет ваш юнит.

Запишите в него этот текст:

[Service]
ExecStart=python -m http.server --bind 164.90.239.48 --directory /opt/phones-shop 80

Все юниты выглядят примерно вот так:

[Название секции в квадратных скобках]
имя_переменной=значение

В переменной ExecStart юнит ждёт команду, которую systemd должен запустить.

Теперь запустите этот юнит:

# systemctl start phones-shop

Получилась ошибка:

Failed to start phones-shop.service: Unit phones-shop.service has a bad unit file setting.
See system logs and 'systemctl status phones-shop.service' for details.

Авторский перевод: Не удалось запустить файл phones-shop.service: в нём неверные настройки. Запустите команду systemctl status phones-shop.service , чтобы узнать что случилось.

Давайте узнаем! Запустите команду systemctl status phones-shop:

image

Юнит явно пишет всё о себе: Loaded: bad-setting (Reason: Unit phones-shop.service has a bad unit file setting.). Тут снова написано о той же ошибке, что была при запуске юнита.

Строка Active: inactive (dead) значит что юнит неактивен/“мёртв”. То есть его либо не запускали, либо запустили, но он поломался. Чуть ниже виден трейсбек поломки:

/etc/systemd/system/phones-shop.service:2: Executable "python" not found in path "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

Значит, что systemd не смог найти python в папке /usr/bin/.

Действительно, в этой папке есть только python3 и python3.8.

Эту проблему легко исправить. Замените в настройках python на python3:

[Service]
ExecStart=python3 -m http.server --bind 164.90.239.48 --directory /opt/phones-shop 80
 Если вы набрали не тот ip

вам напишут с такой просьбой запустить systemctl daemon-reload:

Warning: The unit file, source configuration file or drop-ins of phones-shop.service changed on disk. Run ‘systemctl daemon-reload’ to reload units.

Так и поступите.

Повторите запуск юнита:

# systemctl start phones-shop

Терминал молчит. Это хороший знак? Снова проверьте состояние файла:

# systemctl status phones-shop

image

Самая важная здесь строка Active: active (running). Это значит, что файл запущен. Обратите внимание, что и текст выделен зелёным, а в прошлый раз был красным. Это хороший знак.

Зелёный цвет - это хороший знак.

Проверьте сайт в браузере. Убедитесь, что сайт работает. Затем закройте терминал или наберите команду exit и затем Enter.

# exit
logout
Connection to 164.90.239.48 closed.

Проверьте сайт в браузере. Теперь он должен работать даже если вы закрыли терминал.

4. Добавьте в автозапуск

Сервер иногда приходится перезапускать из-за обновления ПО и изменения настроек. Иногда владелец сервера, который вы арендуете, сам его выключает на пару минут, чтобы обносить софт, закрыть свежие уязвимости, и самостоятельно перезапускает сервер. Такое бывает даже на крупных хостингах, они отключают сервер всего на пару секунд и прислают письма с извинениями на почту.

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

# reboot now

Проверьте сайт в браузере:

image

Сайт снова перестал работать 😦

Это потому что вы не сказали systemd запускать ваш файл, если сервер включается или перезагружается. Это можно исправить:

[Service]
ExecStart=python3 -m http.server --bind 164.90.239.48 --directory /opt/phones-shop 80

[Install]
WantedBy=multi-user.target

Строка [Install] Означает блок настроек при запуске юнита. В этом блоке мы добавили одну настройку: WantedBy=multi-user.target.

Таргет — это группа процессов, которые запустятся вместе, пачкой. multi-user.target — это таргет, который запускается сразу при старте сервера. Получается, что настройка WantedBy=multi-user.target добавляет ваш юнит в таргет, который запустится при рестарте сервера.

Осталась последняя команда, чтобы всё заработало. Она включит ваш юнит в список автозагрузки. Введите команду:

# systemctl enable phones-shop
Created symlink /etc/systemd/system/multi-user.target.wants/phones-shop.service → /etc/systemd/system/phones-shop.service.

Эта команда создаёт симлинк вашего юнита. Симлинк — это прямая ссылка на файл, что-то вроде ярлыка .exe в Windows. Теперь, если вы захотите убрать ваш файл из автозагрузки, то будет достаточно написать systemctl disable phones-shop. Это удобно, потому что не надо лезть в файл юнита и чего-то исправлять, всё отключается одной командой.

Теперь снова перезагрузите сервер с помощью команды reboot now и проверьте свой сайт в браузере. Он должен быть запущен, даже после перезагрузки.

5. Имитация сбоя

Плохая новость: программы иногда ломаются. Такое бывает даже с очень опытными разработчиками и даже в очень больших компаниях.

Чтобы понять, работает автозапуск или нет, нужно как-то сымитировать поломку программы. На Windows это довольно просто сделать: нужно нажать Ctrl+Shift+Esc, откроется диспетчер задач. Там выбираете любой процесс и жмёте “снять задачу”.

В Linux можно провернуть то же самое, причём из терминала. Для этого есть специальная команда kill, но сначала нужно узнать PID вашего юнита. PID — это ProcessID, то есть id процесса. Все процессы можно получить в терминале этой командой:

# ps -aux

Выведется полотно из всех процессов, что запущены на сервере:

В таком полотне совершенно невозможно что-либо найти. Поможет поиск по ключевым словам, тоже команда linux, которая называется grep. Вместе эти команды запускаются так:

# ps -aux | grep "ключевые слова для поиска"

Попробуйте найти PID вашего процесса по названию юнита:

# ps -aux | grep phones-shop

Вот что получилось у нас:

Нашлось 2 процесса. Первый — наш юнит, а второй — собственно процесс поиска через grep. Судя по всему, нужный PID — это 1004.

Теперь, когда у вас есть PID вашего юнита, убейте его процесс:

# kill 1004

Зайдите на ваш сайт. Кажется, он упал 😦

6. Автозапуск на случай сбоя

Теперь стало понятно, что в случае сбоя ваша программа просто упадёт. Сайт не будет работать, пока кто-нибудь не зайдёт на сервер и не запустит его заново. Это совсем не дело, почему он не может перезапуститься сам?..

Надо сказать юниту: “Если файл перестал работать, то файл нужно перезапустить”. Это довольно просто поправить:

[Service]
ExecStart=python3 -m http.server --bind 164.90.239.48 --directory /opt/phones-shop 80
Restart=always

[Install]
WantedBy=multi-user.target

Настройка Restart=always значит “перезапускай юнит, если он не работает”. Эта настройка хороша только для сайтов, чатботов и прочих программ, которые никогда не останавливаются. Если же вам нужно создать юнит с обычной программой, которая однажды закончится, то вам больше подойдёт Restart=on-abort. Тогда systemd перезапустит юнит только в том случае, если он упал с ошибкой, а если он просто завершился, программа закончилась, то systemd оставит юнит в покое.

Теперь, когда вы поправили файл юнита, перезапустите его в systemd:

# systemctl start phones-shop
Warning: The unit file, source configuration file or drop-ins of phones-shop.service changed on disk. Run 'systemctl daemon-reload' to reload units.

Авторский перевод: Внимание: юнит-файл phones-shop.service был изменен. Запустите команду systemctl daemon-reload, чтобы перезагрузить ваш файл.

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

Прим. автора

Очень мило со стороны systemd просто подсказать вам следующую команду: systemctl daemon-reload. Вы можете узнать что она делает на SO, но если вкратце, то это “мягкая перезагрузка”. При обычной перезагрузке systemd полностью останавливает юнит и запускает его заново. При “мягкой” же перезагрузке он не останавливает юнит, а пытается подключить к нему изменения прямо “на ходу”. Например, чтобы добавить настройку Restart=always, на самом деле юнит перезапускать не надо. systemd просто сообщит процессу юнита, что если что-то случится, то стоит перезапуститься.

Мы и до этого изменяли в файл, но ни разу не запускали эту команду. В те разы мы полностью перезагружали сервер, поэтому systemd тоже перезапускался и запускал уже обновленный файл.

Запустите команду, которую просил systemd:

# systemctl daemon-reload

Повторите запуск юнита:

# systemctl start phones-shop

Ещё раз найдите PID вашего процесса (PID поменялся!) и прервите работу файла:

# ps -aux | grep phones-shop
# kill 1052

Проверьте сайт в браузере. Он должен работать. Круто! Теперь сайт работает во всех трёх случаях: после закрытия терминала, после перезагрузки сервера и даже после сбоя в демоне.

7. Что делать, если всё плохо

В этом туториале вы работали с простой программой, где и поломаться-то было почти нечему. Но с сайтами на Django всё бывает куда сложнее. Тут и там вылазят баги, которых не ждёшь. А где посмотреть трейсбек? Раньше он был в консоли, но теперь-то вашу программу запускает systemd!

Systemd всегда пишет логи. Их можно посмотреть в утилите journalctl. Загляните, что там происходит: image

Это все логи. Прямо все-все-все. На продакшн-сервере их будет так много, что ориентироваться в них не получится. Для этого логам добавили фильтры:

  • journalctl -b — записи с последнего запуска системы.
  • journalctl -u phones-shop — записи по юниту phones-shop
  • journalctl -f -u phones-shop — история работы по юниту в режиме реального времени. Периодически на экране будут появляться новые записи.

Попробуйте отсортированный вывод, он гораздо полезнее:

Читать дальше

  1. SystemD Target. Группируем unit’ы
  2. Туториал от 4te.me
  3. Туториал от DigitalOcean
  4. Статья на Хабрахабре
  5. Документация systemd
  6. How to enable a virtualenv in a systemd service unit?

Попробуйте бесплатные уроки по Python

Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.

Переходите на страницу учебных модулей «Девмана» и выбирайте тему.