Запуск демона приложения через systemd
Серверные приложения, написанные, к примеру, на nodejs или на python, держат всегда запущенными. Если приложение вдруг упало, нужно его переподнять.
Кто-то запускает приложения через supervisor (написан на python) или pm2 (nodejs) - это такие демоны как раз для запуска приложений. Но на вашем сервере скорее всего уже есть systemd, который тоже умеет это делать. Зачем тогда устанавливать и настраивать supervisor или pm2?
Я не говорю о больших энтерпрайз-решениях, там свои инструменты для таких задач. Я говорю о небольших или хобби-проектах.
Рассмотрю запуск nodejs-приложения через systemd. Расскажу про перезапуск, ограничение прав и сбор логов.
Для примера попробую запустить такое приложение, написанное на nodejs:
const http = require('http');
// Get MYAPP_PORT from environment variable
const MYAPP_PORT = process.env.MYAPP_PORT;
http.createServer((req, res) => {
if (req.url === '/kill') {
// App die on uncaught error and print stack trace to stderr
throw new Error('Someone kills me');
}
if (req.method === 'POST') {
// App print this message to stderr, but is still alive
console.error(`Error: Request ${req.method} ${req.url}`);
res.writeHead(405, { 'Content-Type': 'text/plain' });
res.end('405 Method Not Allowed');
return;
}
// App print this message to stdout
console.log(`Request ${req.method} ${req.url}`);
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('200 OK');
})
.listen(MYAPP_PORT);
Этот скрипт запускает веб-сервер. Он слушает порт, который задан в переменной окружения MYAPP_PORT
.
Запущу приложение, чтобы проверить:
MYAPP_PORT=3000 node index.js
Приложение на все гет-запросы отвечает «200 OK»:
curl 'http://localhost:3000'
На пост-запросы отвечает «405 Method Not Allowed»:
curl -X POST 'http://localhost:3000'
И при запросе /kill
вообще умирает:
curl 'http://localhost:3000/kill'
Сервис-юнит
Systemd оперирует понятиями юнитов. Юнитом может быть процесс-демон, устройство, или даже список других юнитов. В руководстве man systemd
в секции «Concepts» описано, какие есть типы юнитов, и для чего они нужны.
Мне понадобится самый первый — Service unit. Он позволяет запустить процесс в режиме демона и перезапускать процесс, если он упал. В руководстве man systemd.unit
описано, как писать юниты в целом, а в man systemd.service
— как писать сервис-юниты.
Файлы своих юнитов нужно класть в /etc/systemd/system/
. Я назову свой /etc/systemd/system/myapp.service
. Минимальная конфигурация для сервиса может быть такая:
[Service]
Environment=MYAPP_PORT=3000
ExecStart=/usr/local/bin/node /usr/local/www/myapp/index.js
В поле Environment нужные приложению переменные окружения. В ExecStart команда для запуска приложения. /usr/local/bin/node
— это полный путь к nodejs, его можно узнать командой:
which node
А /usr/local/www/myapp/index.js
— полный путь к моему приложению.
Пробую запустить сервис:
sudo systemctl start myapp.service
Проверю статус:
sudo systemctl status myapp.service
● myapp.service
Loaded: loaded (/etc/systemd/system/myapp.service; static; vendor preset: enabled)
Active: active (running) since Fri 2017-08-04 21:50:14 MSK; 5s ago
Main PID: 3815 (node)
Tasks: 6
Memory: 7.7M
CPU: 80ms
CGroup: /system.slice/myapp.service
└─3815 /usr/local/bin/node /usr/local/www/myapp/index.js
Aug 04 21:50:14 ubuntu16 systemd[1]: Started myapp.service.
Ну типа работает. Дёрну курлом, чтобы проверить:
curl 'http://localhost:3000/'
200 OK
Действительно, работает.
Автоматический перезапуск
Если приложение по какой-то причине умрёт, то оно не поднимется. Вот я дёргаю убивающий эндпойнт:
curl 'http://localhost:3000/kill'
curl: (52) Empty reply from server
И проверяю статус приложения:
sudo systemctl status myapp.service
● myapp.service
Loaded: loaded (/etc/systemd/system/myapp.service; static; vendor preset: enabled)
Active: failed (Result: exit-code) since Fri 2017-08-04 21:51:46 MSK; 5s ago
Process: 3815 ExecStart=/usr/local/bin/node /usr/local/www/myapp/index.js (code=exited, status=1/FA
Main PID: 3815 (code=exited, status=1/FAILURE)
Aug 04 21:51:46 ubuntu16 node[3815]: ^
Aug 04 21:51:46 ubuntu16 node[3815]: Error: Someone kills me
Aug 04 21:51:46 ubuntu16 node[3815]: at Server.http.createServer (/usr/local/www/myapp/index.js:1
Aug 04 21:51:46 ubuntu16 node[3815]: at emitTwo (events.js:106:13)
Aug 04 21:51:46 ubuntu16 node[3815]: at Server.emit (events.js:191:7)
Aug 04 21:51:46 ubuntu16 node[3815]: at HTTPParser.parserOnIncoming [as onIncoming] (_http_server
Aug 04 21:51:46 ubuntu16 node[3815]: at HTTPParser.parserOnHeadersComplete (_http_common.js:99:23
Aug 04 21:51:46 ubuntu16 systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILUR
Aug 04 21:51:46 ubuntu16 systemd[1]: myapp.service: Unit entered failed state.
Aug 04 21:51:46 ubuntu16 systemd[1]: myapp.service: Failed with result 'exit-code'.
Чтобы systemd переподнимал упавшее приложение, нужно добавить опцию Restart. У неё есть несколько значений, подробно
описанных в man systemd.service
. Значение always
будет в любой ситуации перезапускать
приложение:
[Service]
Environment=MYAPP_PORT=3000
ExecStart=/usr/local/bin/node /usr/local/www/myapp/index.js
Restart=always
Когда изменили файл юнита, надо заставлять systemd перечитать его, чтобы он использовал уже новый файл:
sudo systemctl daemon-reload
Снова запущу сервис, дёрну убивающую ручку и проверю, переподнимется ли приложение:
sudo systemctl start myapp.service
curl 'http://localhost:3000/kill'
curl: (52) Empty reply from server
curl 'http://localhost:3000/'
200 OK
curl 'http://localhost:3000/kill'
curl: (52) Empty reply from server
curl 'http://localhost:3000/'
200 OK
Ну вот. Теперь упавший процесс переподнимается.
Ограничение прав
Пользователя и группу, от имени которых будет запущен процесс, можно установить переменными User и Group:
[Service]
Environment=MYAPP_PORT=3000
User=www-data
Group=www-data
ExecStart=/usr/local/bin/node /usr/local/www/myapp/index.js
Restart=always
Запись и просмотр логов
Здорово, если ваше приложение пишет логи в файлы. Но если оно пишет в стандартный вывод, то через systemd можно собирать эти записи. По умолчанию они и так куда-то собираются. Куда именно — зависит от ваших настроек. У меня например на Ubuntu 16.04 они сливаются в журнал systemd, и посмотреть их можно вот так:
journalctl -u myapp.service
или в файле /var/log/syslog
.
Можно сделать, чтобы systemd направлял вывод процесса в syslog, и написать правила обработки логов для него. Например сливать их на сервер-агрегатор логов или просто писать в файлы.
[Service]
Environment=MYAPP_PORT=3000
User=www-data
Group=www-data
ExecStart=/usr/local/bin/node /usr/local/www/myapp/index.js
Restart=always
# Отправка логов syslog
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=myapp
Опции StandardOutput и StandardError определяют, куда писать вывод приложения. Возможные значения этих опций вы
можете посмотреть в man systemd.exec
. SyslogIdentifier собственно задаёт имя сервиса для syslog.
Кстати, при отправке логов в syslog, в журнал systemd они тоже будут писаться, так что можно по-прежнему читать их с
помощью journalctl.
Ну так вот, в syslog можно настроить правила обработки логов от вашего приложения. У меня в качестве syslog-демона
rsyslog. Его настройки лежат в /etc/rsyslog.d/
. Создаю файл /etc/rsyslog.d/100-myapp.conf
с таким содержимым:
$RepeatedMsgReduction off
if $programname == 'myapp' then -/var/log/myapp/debug.log
$RepeatedMsgReduction off
нужно, чтобы syslog несколько одинаковых сообщений не схлопывал в одно.
Systemd не умеет слать stdout и stderr в syslog с разным уровнем severity, поэтому в такой конфигурации нельзя писать stdout в один файл, а stderr в другой.
Можно направить stdoud процесса в логгер (утилита для записи в syslog) пайпом, а stderr слать через systemd:
[Service]
Environment=MYAPP_PORT=3000
User=www-data
Group=www-data
# пайпим stdout в logger с тегом myapp
ExecStart=/bin/sh -c '/usr/local/bin/node /usr/local/www/myapp/index.js | logger --tag myapp'
# stder шлём в syslog средствами systemd
StandardError=syslog
SyslogIdentifier=myapp
# устанавливаем уровень логирования: err
SyslogLevel=err
Restart=always
И настроить в syslog правила для записи в разные файлы:
$RepeatedMsgReduction off
if $programname == 'myapp' and $syslogseverity < 5 then -/var/log/myapp/error.log
if $programname == 'myapp' and $syslogseverity >= 5 then -/var/log/myapp/debug.log
Кроме записи в файлы, можно конечно и на сервер-агрегатор слать.
Если вы пишете логи в файлы, не забудьте настроить logrotate. Пример файла конфигурации
/etc/logrotate.d/myapp
:
/var/log/myapp/debug.log
/var/log/myapp/error.log {
rotate 7
daily
compress
missingok
notifempt
postrotate
invoke-rc.d rsyslog rotate > /dev/null
endscript
}
Самое главное тут — скрипт postrotate, который заставляет rsyslog переоткрыть файл после ротации. Подробности
про настройку logrotate можно прочитать в man logrotate.conf
Вот так, с помощью нехитрых приспособлений приложение на nodejs (или на python) можно превратить в демон, который поднимется после падения, и логи которого нормально сохраняются и ротируются.
Источник - https://isqua.ru/blog/2020/05/25/nodejs-systemd/
![](http://lred.ru/templates/lred2023uslugi/img/head11logo.png)
![](http://lred.ru/templates/lred2023uslugi/img/head11logo.png)
Дайджест новых статей по интернет-маркетингу на ваш email
Новые статьи и публикации
- 2024-09-05 » OpenAI рассказал, как запретить ChatGPT использовать содержимое сайта для обучения
- 2024-08-28 » Чек-лист: как увеличить конверсию интернет-магазина на примере спортпита
- 2024-08-01 » WebSocket
- 2024-07-26 » Интеграция с Яндекс Еда
- 2024-07-26 » Интеграция с Эквайринг
- 2024-07-26 » Интеграция с СДЕК
- 2024-07-26 » Интеграция с Битрикс-24
- 2024-07-26 » Интеграция с Travelline
- 2024-07-26 » Интеграция с Iiko
- 2024-07-26 » Интеграция с Delivery Club
- 2024-07-26 » Интеграция с CRM
- 2024-07-26 » Интеграция с 1C-Бухгалтерия
- 2024-07-24 » Что такое сторителлинг: техники и примеры
- 2024-07-17 » Ошибка 404: что это такое и как ее использовать для бизнеса
- 2024-07-03 » Размещайте прайс-листы на FarPost.ru и продавайте товары быстро и выгодно
- 2024-07-01 » Профилирование кода в PHP
- 2024-06-28 » Изучаем ABC/XYZ-анализ: что это такое и какие решения с помощью него принимают
- 2024-06-17 » Зачем вам знать потребности клиента
- 2024-06-11 » Что нового в работе Яндекс Метрики: полный обзор обновления
- 2024-06-11 » Поведенческие факторы ранжирования в Яндексе
- 2024-06-11 » Скорость загрузки сайта: почему это важно и как влияет на ранжирование
- 2024-05-27 » Подборка сервисов для расшифровки аудио в текст
- 2024-05-27 » PostgreSQL 16. Изоляция транзакций. Часть 2
- 2024-05-06 » Как настраивать конверсионные стратегии: работа над ошибками
- 2024-04-22 » Комментирование кода и генерация документации в PHP
- 2024-04-22 » SEO в России и на Западе: в чем основные отличия
- 2024-04-22 » SEO для международного масштабирования
- 2024-04-22 » Как использовать XML-карты для продвижения сайта
- 2024-04-22 » Цифровой маркетинг: инструменты для продвижения и рекламы в 2024 году
- 2024-04-22 » Что такое CSS-модули и зачем они нам?
«Web делает доступным все, для всех, везде и всегда. Если знания - это сила, то сила сейчас находится практически везде». К. Нордстрем, Й. Ридерстрале |
Мы создаем сайты, которые работают! Профессионально обслуживаем и продвигаем их , а также по всей России и ближнему зарубежью с 2006 года!
Как мы работаем
Заявка
Позвоните или оставьте заявку на сайте.
Консультация
Обсуждаем что именно Вам нужно и помогаем определить как это лучше сделать!
Договор
Заключаем договор на оказание услуг, в котором прописаны условия и обязанности обеих сторон.
Выполнение работ
Непосредственно оказание требующихся услуг и работ по вашему заданию.
Поддержка
Сдача выполненых работ, последующие корректировки и поддержка при необходимости.