Контейнер Docker завершился с ошибкой и выдал: -bash: fork: повторить: Ресурс временно недоступен

Вопрос или проблема

Не уверен, стоит ли задавать этот вопрос здесь или на других форумах (например, 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. Ограниченные ресурсы: Учитывая, что ваш сервер имеет всего 1 ГБ оперативной памяти, это может выступать в качестве узкого места, особенно если ваше приложение потребляет больше ресурсов из-за утечек памяти или других факторов. Судя по графикам использования памяти, вы работаете вблизи предельных значений, даже если это не достигает установленного порога в 800 МБ.

  2. Проблемы с лимитами процессов: Вы упомянули, что команда ulimit -a показывает количество процессов, которые могут быть созданы пользователем, и это значение ограничено (3842). Если ваше приложение или PM2 пытается создать больше процессов, это вызовет ошибку. Когда система исчерпывает лимиты, использование команд оболочки становится невозможным, что приводит к ошибке fork.

  3. Сборка мусора: Вы правильно отметили необходимость использования --expose-gc. Это позволяет вручную инициировать сборку мусора, однако это не всегда гарантирует решение проблемы с утечкой памяти. В некоторых случаях, даже если вы вызываете сборку мусора, если ваше приложение продолжает удерживать ссылки на объекты, память не будет эффективно освобождена.

Рекомендации по решению проблемы

  1. Мониторинг и анализ:

    • Используйте инструменты профилирования, такие как clinic.js или memory leaks, чтобы определить возможные утечки памяти в вашем коде.
    • Рассмотрите возможность настройки более детального логирования в PM2, чтобы отслеживать использование ресурсов и поведение вашего приложения.
  2. Корректировка лимитов ресурсов:

    • Измените конфигурацию ulimit, чтобы увеличить количество процессов и открытых файлов. Например, чтобы изменить лимиты для контейнера, добавьте в ваш docker-compose.yml:
      ulimits:
      nproc: 8192
      nofile:
       soft: 8192
       hard: 16384
    • Изменять лимиты следует осторожно, чтобы не привести к неоптимальному потреблению ресурсов.
  3. Использование параметров --max_old_space_size:

    • Установите этот параметр, чтобы указать максимальный размер памяти для старого поколения в сборщике мусора V8. Для вашего случая с 1 ГБ оперативной памяти, вы можете попробовать установить значение в 512 МБ:
      node --max_old_space_size=512 your-app.js
  4. Оптимизация конфигурации PM2:

    • Рассмотрите возможность настройки PM2 для автоматического перезапуска приложений при достижении порога использования памяти в 50-60%, чтобы избежать ситуации, когда память заполняется полностью.
  5. Обновление инфраструктуры:

    • Если возможно, попробуйте увеличить объем используемой оперативной памяти до 2 ГБ или более, если это соответствует требованиям вашего приложения и бюджета.

Применение этих рекомендуемых действий должно помочь в устранении проблемы с ошибкой -bash: fork: retry: Resource temporarily unavailable и улучшить устойчивость вашего приложения в Docker. Не забывайте регулярно проводить мониторинг и настройку вашего кода и инфраструктуры для оптимизации производительности.

Оцените материал
Добавить комментарий

Капча загружается...