Вопрос или проблема
Не уверен, стоит ли задавать этот вопрос здесь или на других форумах (например, SuperUser или Unix&Linux), но пока оставлю его здесь.
У меня есть контейнеризированное приложение на NodeJS, использующее Selenium и управляемое средой выполнения PM2. Приложение работает на сервере DigitalOcean с 1 ГБ памяти и 25 ГБ дискового пространства. Приложение предназначено для периодического сбора данных с веб-сайта с интервалом в 2 минуты с использованием Selenium WebDriver. Я столкнулся с этой проблемой некоторое время назад, когда при подключении по SSH к серверу любая команда возвращала:
-bash: fork: retry: Ресурс временно недоступен
Настроил новый сервер DigitalOcean с мониторингом для измерения использования памяти. Мое использование медленно увеличивалось, поэтому я подумал, что где-то есть утечка памяти. Пытался найти, но не смог (все еще ищу). Я увидел, что PM2 имеет опцию перезапуска приложения, если использование памяти достигает определенной точки. В качестве меры предосторожности я установил это значение на 800M
(80%). Мой ecosystem.config.js
:
module.exports = {
apps: [
{
name: 'scraper',
script: './index.js',
watch: process.env.NODE !== 'production',
ignore_watch: ['node_modules', 'logs', 'test', 'scripts', '.vscode', '.git'],
out_file: './logs/app.log',
max_memory_restart: '800M',
node_args: '--expose-gc',
env_development: {
NODE_ENV: 'development'
},
env_production: {
NODE_ENV: 'production'
}
}
]
}
После более тщательного изучения я увидел, что PM2 может вызвать утечку памяти, не проводя сборку мусора должным образом, поэтому я добавил аргументы для Node --expose-gc
, чтобы принудительно вызывать сборку мусора с интервалом в минуту (на основе этого примера):
exports.generateHeapDumpAndStats = function() {
try {
if (global.gc) {
global.gc()
} else {
logger.info('Сборка мусора недоступна. Используйте "--expose-gc", чтобы запустить принудительную сборку мусора')
}
const heapUsed = process.memoryUsage().heapUsed
const heapUsedMb = (heapUsed / 1024 * 1024).toFixed(2)
logger.info(`Программа использует ${heapUsedMb} MB кучи`)
} catch (err) {
logger.error(`Ошибка: ${err.message}`)
process.exit(1)
}
}
Я думал, что это решит проблему, поскольку даже если сборка мусора не удается, PM2 перезапустит приложение при использовании 80%. Я запустил контейнер примерно в 10:45 вечера (GMT +1), и судя по графику использования, контейнер отключился в 2:00 ночи. Вот мои графики использования за последние 24 часа:
Вы можете увидеть, что использование памяти даже не приближается к 80%, так стоит ли мне временно понизить порог перезапуска?
Я пытался проверить использование памяти на самом сервере, но любая команда, которую я ввожу, возвращает указанную ошибку.
Стоит ли мне попробовать установить --max_old_space_size
? Я заметил, что процесс NodeJS пытается выделить себе 1.5 ГБ памяти, чего у меня нет на этом сервере.
Я очень сбит с толку, почему это происходит. К сожалению, журналы контейнера записываются только в локальный файл на сервере, поэтому я не могу получить к ним доступ сейчас.
Я попытался проверить запущенные контейнеры, и мне один раз вернули что-то полезное:
Моя команда npm start
:
sudo -E pm2-runtime –raw ecosystem.config.js –env production –only scraper
И полный Dockerfile
:
FROM selenium/standalone-chrome
WORKDIR /usr/src/app
RUN curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
RUN sudo apt-get install -y nodejs build-essential firefox
# копируем package.json и package-lock.json и устанавливаем пакеты
# мы делаем это отдельно от кода приложения, чтобы лучше использовать кэширование Docker
# `npm install` будет кэшироваться на будущих сборках, если изменился только код приложения
COPY package*.json ./
RUN sudo npm install pm2 -g
RUN sudo npm install
# Переменные окружения динамически устанавливаются здесь CI
# копируем приложение
COPY . .
# открываем порт для express и запускаем
EXPOSE 3000
CMD [ "npm", "start"]
Я предоставлю код по запросу, если это необходимо, просто изначально не думал, что это необходимо и не хотел делать вопрос слишком большим 🙂
ПРИМЕЧАНИЕ: Я изначально опубликовал этот вопрос на SO, но меня попросили перенести его сюда
ИСПРАВЛЕНИЕ
Согласно комментарию @dirkt, похоже, что я могу достигать предела ресурсов. Команда ulimit -a
возвращает:
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 3842
max locked memory (kbytes, -l) 16384
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 3842
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
Таким образом, у пользователя есть ограничения по процессам, что, как я думаю, и вызывает проблемы. Однако я не совсем уверен, где изменить это значение и нужно ли мне устанавливать его на unlimited
или просто поднять его до произвольного значения? Ограничения не установлены в /etc/security/limits.conf
. Я видел несколько способов изменить мягкие/жесткие (ulimit
CLI, limits.conf
, user.conf
– я полагаю, что последний не имеет значения для меня, так как я работаю под root
) лимиты для пользователя. Также стоит отметить, что я запускаю этот Docker-контейнер под root
(в будущем изменю это)
У меня была та же проблема,
Когда я пытаюсь запустить команды в Docker-контейнере, это обычно связано с исчерпанием ресурсов системы. Это может произойти, если запущено слишком много процессов или если контейнер сталкивается с ограничениями ресурсов, такими как процессор или память.
Сначала проверьте свои лимиты, войдите в Docker:
docker exec -it crawler-puppeteer-service-1 /bin/bash
Затем проверьте текущие лимиты:
ulimit -a
Лучшее решение — лучше управлять потребностями ресурсов в вашем приложении, но если необходимо, вы можете установить новые лимиты в файле docker-compose, добавив:
ulimits:
nproc: 8192
nofile:
soft: 8192
hard: 16384
Чтобы установить все на неограниченные, вы можете сделать следующее:
ulimits:
nproc: -1
nofile:
soft: -1
hard: -1
memlock:
soft: -1
hard: -1
stack:
soft: -1
hard: -1
Обратите внимание: установка всех значений на неограниченные не рекомендуется и должна использоваться только для тестирования!
Ответ или решение
Проблема с сообщением ошибки -bash: fork: retry: Resource temporarily unavailable
может возникнуть в контексте работы с Docker-контейнерами, особенно в случае, когда идет недоступность системных ресурсов, таких как память или количество процессов. Ваша ситуация описывает примеры использования Node.js с Selenium и PM2 на сервере с ограниченными ресурсами.
Анализ проблемы
-
Ограниченные ресурсы: Учитывая, что ваш сервер имеет всего 1 ГБ оперативной памяти, это может выступать в качестве узкого места, особенно если ваше приложение потребляет больше ресурсов из-за утечек памяти или других факторов. Судя по графикам использования памяти, вы работаете вблизи предельных значений, даже если это не достигает установленного порога в 800 МБ.
-
Проблемы с лимитами процессов: Вы упомянули, что команда
ulimit -a
показывает количество процессов, которые могут быть созданы пользователем, и это значение ограничено (3842). Если ваше приложение или PM2 пытается создать больше процессов, это вызовет ошибку. Когда система исчерпывает лимиты, использование команд оболочки становится невозможным, что приводит к ошибкеfork
. -
Сборка мусора: Вы правильно отметили необходимость использования
--expose-gc
. Это позволяет вручную инициировать сборку мусора, однако это не всегда гарантирует решение проблемы с утечкой памяти. В некоторых случаях, даже если вы вызываете сборку мусора, если ваше приложение продолжает удерживать ссылки на объекты, память не будет эффективно освобождена.
Рекомендации по решению проблемы
-
Мониторинг и анализ:
- Используйте инструменты профилирования, такие как
clinic.js
илиmemory leaks
, чтобы определить возможные утечки памяти в вашем коде. - Рассмотрите возможность настройки более детального логирования в PM2, чтобы отслеживать использование ресурсов и поведение вашего приложения.
- Используйте инструменты профилирования, такие как
-
Корректировка лимитов ресурсов:
- Измените конфигурацию
ulimit
, чтобы увеличить количество процессов и открытых файлов. Например, чтобы изменить лимиты для контейнера, добавьте в вашdocker-compose.yml
:ulimits: nproc: 8192 nofile: soft: 8192 hard: 16384
- Изменять лимиты следует осторожно, чтобы не привести к неоптимальному потреблению ресурсов.
- Измените конфигурацию
-
Использование параметров
--max_old_space_size
:- Установите этот параметр, чтобы указать максимальный размер памяти для старого поколения в сборщике мусора V8. Для вашего случая с 1 ГБ оперативной памяти, вы можете попробовать установить значение в 512 МБ:
node --max_old_space_size=512 your-app.js
- Установите этот параметр, чтобы указать максимальный размер памяти для старого поколения в сборщике мусора V8. Для вашего случая с 1 ГБ оперативной памяти, вы можете попробовать установить значение в 512 МБ:
-
Оптимизация конфигурации PM2:
- Рассмотрите возможность настройки PM2 для автоматического перезапуска приложений при достижении порога использования памяти в 50-60%, чтобы избежать ситуации, когда память заполняется полностью.
-
Обновление инфраструктуры:
- Если возможно, попробуйте увеличить объем используемой оперативной памяти до 2 ГБ или более, если это соответствует требованиям вашего приложения и бюджета.
Применение этих рекомендуемых действий должно помочь в устранении проблемы с ошибкой -bash: fork: retry: Resource temporarily unavailable
и улучшить устойчивость вашего приложения в Docker. Не забывайте регулярно проводить мониторинг и настройку вашего кода и инфраструктуры для оптимизации производительности.