systemd - как работать с NodeJS сервисом, у которого утекает память?

В статье рассмотрен один из вариантов борьбы с утечками памяти в приложении на NodeJS, которые начинаются после нескольких часов работы

Анатолий Остроумов

systemdlinuxjavascript

366 Words

2021-02-18 18:26 +0300


systemd_memleaky_node.png

Всем хороша среда выполнения Javascript NodeJS, но у неё есть три недостатка.

  • Первый недостаток - много низкокачественных NPM модулей, включая те, в которых происходят утечки памяти.
  • Второй недостаток - обычная программа требует множество модулей, и обязательно среди них будет несколько, в которых утекает память.
  • Третий недостаток - иногда утечки памяти начинаются после часов, а то и суток работы программы.

Что с этим делать? Можно потратить много времени, разобраться, где утекает память, написать исправление к модулю и послать Pull-Request разработчикам модуля, который они скорее всего проигнорируют, так как у них проблема не воспроизводится, или же они, как обычно, не видят в этом проблему. А можно просто перезапустить программу через несколько часов работы, когда начинают появляться утечки памяти.

Для примера, рассмотрим почтовый сервер на основе https://haraka.github.io/, который, после запуска потребляет 56.8 мегабайт памяти, а через несколько суток - уже 180 мегабайт.

Данный сервер запускается с помощью systemd, используя этот unit файл:

[Unit]
Description=Haraka inbound STMP server
After=network.target

[Service]
Type=simple

User=haraka
Group=haraka

WorkingDirectory=/opt/haraka/haraka-inbound
ExecStart=/usr/bin/npm start
Restart=always
RestartSec=1s
Environment=NODE_ENV=production

# Hardening
ProtectHome=yes
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

Как его можно перезапустить, допустим, раз в час, используя systemd?

Для этого достаточно почитать документацию к systemd, и найти этот параметр конфигурации:

https://www.freedesktop.org/software/systemd/man/systemd.service.html#RuntimeMaxSec

RuntimeMaxSec=

Configures a maximum time for the service to run. If this is used 
and the service has been active for longer than the specified time it is terminated 
and put into a failure state. Note that this setting does not have any effect 
on Type=oneshot services, as they terminate immediately after activation 
completed. Pass "infinity" (the default) to configure no runtime limit.

То есть, задав этот параметр равным 3600, мы добьёмся того, что systemd будет автоматически останавливать, и снова запускать почтовый сервер каждый час. Так как Haraka написан неожиданно хорошо для nodejs приложений, он не только быстро перезагружается, он ещё знает, как закончить все транзакции перед перезапуском, и поэтому ни одно письмо не пропадёт.

Такой unit файл был в итоге создан для почтового сервера:


[Unit]
Description=Haraka inbound STMP server
After=network.target

[Service]
Type=simple

User=haraka
Group=haraka

WorkingDirectory=/opt/haraka/haraka-inbound
ExecStart=/usr/bin/npm start
Restart=always
RestartSec=1s
Environment=NODE_ENV=production

# Gracefully restart service every hour to prevent memleaks
RuntimeMaxSec=3600

# Hardening
ProtectHome=yes
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

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