Singularity и планировщик задач: запуск контейнеров в HPC-кластере

Задача, отправленная в SLURM командой sbatch, падает с ошибкой вида «command not found» или «container not found» — это самый частый сценарий, когда пользователь впервые совмещает Singularity (ныне Apptainer) с планировщиком задач на вычислительном кластере. Причина почти всегда одна: скрипт рассчитан на интерактивную сессию, а планировщик запускает его в другом окружении, где переменные, пути и даже смонтированные каталоги отличаются.

Ниже разберём, как устроено взаимодействие контейнеров Singularity/Apptainer с планировщиками задач, как правильно оформить batch-скрипт, какие директивы проверить в первую очередь и как диагностировать типовые сбои. Материал ориентирован на кластеры под управлением SLURM — самого распространённого планировщика в HPC-среде, — но общие принципы применимы и к PBS/Torque, и к LSF.

Почему Singularity и планировщик задач дополняют друг друга

На вычислительных кластерах планировщик (workload manager) решает две задачи: распределяет ресурсы между пользователями и ставит задания в очередь, когда свободных узлов нет. Контейнер же решает другую проблему — воспроизводимость окружения: все библиотеки, зависимости и версии ПО упакованы в один файл образа .sif.

Именно поэтому связка «планировщик + контейнер» стала стандартом в научных вычислениях. Singularity/Apptainer изначально проектировался для HPC: в отличие от Docker, он не требует root-демона, запускается от обычного пользователя и нативно интегрируется с планировщиками — переменные окружения задания, MPI-ранк и лимиты ресурсов прозрачно передаются внутрь контейнера.

Важно понимать разделение ролей: планировщик ничего не знает о содержимом контейнера. Он выделяет узлы, ядра и память, а уже внутри выделенного задания вы вызываете singularity exec или apptainer run. Отсюда следует главное правило: все ресурсные параметры задаются директивами планировщика, а не опциями контейнера.

Подготовка: что проверить перед отправкой задания

Прежде чем писать batch-скрипт, убедитесь, что базовые компоненты доступны на вычислительных узлах, а не только на login-узле. Это разные машины, и ПО на них может отличаться.

  • 🔍 Проверьте наличие команды: which singularity или which apptainer — на многих кластерах модуль загружается через module load singularity или module load apptainer.
  • 📁 Убедитесь, что файл образа .sif лежит на общей файловой системе, видимой с вычислительных узлов (обычно это $HOME или scratch-раздел, а не локальный диск login-узла).
  • 🧪 Выполните пробный запуск в интерактивной сессии на вычислительном узле, если кластер это позволяет: так вы отделите ошибки контейнера от ошибок планировщика.
  • 📜 Уточните в документации вашего кластера допустимые директивы, имена очередей (партиций) и лимиты — они различаются между установками.
⚠️ Внимание: сборку образа (singularity build с sudo или через fakeroot) на login-узлах кластеров часто запрещают политикой безопасности. Собирайте образ локально или через удалённый билдер, а на кластер переносите готовый .sif-файл.

Структура batch-скрипта для SLURM

Типовой скрипт задания состоит из двух частей: директив #SBATCH в начале и команд запуска ниже. Директивы читает планировщик, команды выполняются уже на выделенном узле.

#!/bin/bash

#SBATCH --job-name=sing_test

#SBATCH --nodes=1

#SBATCH --ntasks=4

#SBATCH --time=01:00:00

#SBATCH --output=job_%j.out

module load apptainer

singularity exec --bind /scratch/$USER:/data \

/home/$USER/images/myimage.sif \

python /data/script.py

Разберём ключевые моменты. Директива --ntasks определяет число процессов, которое выделит планировщик; внутри контейнера ваша программа должна использовать именно эти ресурсы, а не пытаться занять все ядра узла. Опция --bind монтирует каталог с данными внутрь контейнера — без неё программа в контейнере просто не увидит входные файлы.

Отправка задания выполняется стандартно:

sbatch myjob.sh

Статус смотрят через squeue -u $USER, а после завершения анализируют выходной файл job_<id>.out — туда попадают и сообщения об ошибках контейнера.

☑️ Проверка batch-скрипта перед отправкой

Выполнено: 0 / 5

Интерактивный и пакетный режимы: в чём разница

Многие сначала отлаживают задачу в интерактивной сессии — например, через srun --pty bash или аналогичную команду, принятую на конкретном кластере. Внутри такой сессии контейнер запускается точно так же, как и в batch-скрипте, но есть тонкость: интерактивная сессия наследует переменные вашего логина, а пакетное задание стартует с более «чистым» окружением.

Отсюда типичный симптом: «в интерактиве работает, через sbatch — нет». Возможные причины — незагруженный модуль, отсутствие переменной в .bashrc (который в пакетном режиме может не выполняться) или относительные пути вместо абсолютных. Лечение простое: всё необходимое окружение задавайте явно внутри скрипта задания, а пути пишите абсолютными.

📊 Как вы чаще запускаете контейнеры на кластере?
Пакетные задания через sbatch
Интерактивные сессии srun
MPI-задачи на несколько узлов
Только начинаю осваивать

MPI и многозадачные задания внутри контейнера

Для параллельных вычислений применяется модель «гибридного MPI»: библиотека MPI установлена и в контейнере, и на хост-системе, а планировщик запускает процессы через srun или mpirun снаружи контейнера. Схематично это выглядит так:

srun singularity exec mpi_app.sif /opt/app/mpi_program

Здесь критична совместимость версий MPI внутри и снаружи контейнера: при существенном расхождении задание может зависнуть или упасть с ошибками транспорта. Точные требования зависят от используемой реализации (Open MPI, MPICH, Intel MPI) и настроек кластера — сверяйтесь с документацией вашей системы и администраторами.

Для заданий на GPU добавляется флаг --nv (для NVIDIA), который пробрасывает драйвер и устройства внутрь контейнера. Выделение GPU при этом запрашивается директивой планировщика, например #SBATCH --gres=gpu:1 — синтаксис может отличаться в зависимости от конфигурации кластера.

Типичные ошибки и их диагностика

Большинство сбоев при связке Singularity с планировщиком относятся к нескольким повторяющимся классам. Таблица ниже помогает быстро сузить поиск.

СимптомВероятная причинаЧто проверить
singularity: command not foundМодуль не загружен в скрипте заданияСтрока module load до вызова контейнера
container not found / no such fileОбраз недоступен с вычислительного узлаРасположение .sif на общей ФС, абсолютный путь
Программа не видит входные файлыКаталог не примонтированОпция --bind и пути внутри контейнера
Задание убито по лимитуПревышено время или памятьДирективы --time, --mem, логи планировщика
MPI-задание зависаетНесовместимость MPI внутри и снаружиВерсии MPI, рекомендации администраторов
⚠️ Внимание: не пытайтесь обойти лимиты планировщика, запуская внутри контейнера больше процессов, чем запрошено директивами. На многих кластерах такие задания принудительно завершаются, а аккаунт может получить ограничения.

Если ошибка неочевидна, запустите контейнер с повышенной детализацией: singularity --debug exec .... Вывод покажет, какие каталоги монтируются, какие переменные передаются и на каком этапе происходит сбой.

Переменные окружения внутри контейнера

По умолчанию Singularity передаёт в контейнер большинство переменных окружения хоста, добавляя префикс SINGULARITYENV_ для явного проброса. Например, SINGULARITYENV_FOO=bar сделает переменную FOO доступной внутри контейнера. В Apptainer аналогичный префикс — APPTAINERENV_. Планировщик SLURM со своей стороны экспортирует переменные вида SLURM_JOB_ID, SLURM_NTASKS — их можно читать внутри контейнера, чтобы программа узнала параметры задания.

Переносимость между кластерами и версиями

Один из главных плюсов контейнерного подхода — образ .sif можно переносить между кластерами без пересборки. Но batch-скрипт при этом почти наверняка придётся адаптировать: имена партиций, лимиты, система модулей и пути к файловым системам у каждой установки свои.

Также учитывайте переименование проекта: новые установки используют команду apptainer, старые — singularity. Скрипт, жёстко привязанный к одному имени, на другом кластере не запустится. Надёжный приём — проверять доступность команды в начале скрипта или использовать переменную для имени исполняемого файла.

FAQ: частые вопросы

Можно ли отправить контейнер в очередь одной командой без скрипта?

Да, sbatch принимает команду через --wrap, например: sbatch --wrap="singularity exec image.sif python script.py". Для одноразовых запусков это удобно, но для воспроизводимости лучше хранить скрипт задания в файле.

Нужен ли root для запуска контейнера на кластере?

Нет. Singularity/Apptainer спроектирован для запуска от обычного пользователя — это одно из ключевых отличий от Docker и причина его распространения в HPC. Root может понадобиться только для сборки образа, что обычно делается вне кластера.

Почему задание работает на login-узле, но падает через sbatch?

Наиболее вероятная причина — различия в окружении: незагруженный модуль, переменные из .bashrc, относительные пути. Пропишите в скрипте задания явную загрузку модулей и абсолютные пути, затем сравните вывод команды env в обоих режимах.

Как передать переменную окружения внутрь контейнера?

Используйте префикс SINGULARITYENV_ (или APPTAINERENV_ для Apptainer) перед именем переменной, либо флаг --env в новых версиях. Обычные переменные хоста часто пробрасываются автоматически, но явный способ надёжнее и переносимее.

Чем Apptainer отличается от Singularity при работе с планировщиком?

С точки зрения пользователя планировщика — практически ничем: это развитие одного проекта, команды и флаги в основном совпадают, меняется имя исполняемого файла и префиксы переменных. Скрипты, как правило, переносятся с минимальными правками.