Кластеры и воспроизводимое окружение для расчётов

Планировщик Slurm, массивы задач, MPI из Python, образы Apptainer и конфигурация узлов кодом

Вячеслав Федоров

Лекция 21

Разработка и применение ПО

в физических исследованиях

sbatch

Содержание

От очереди задач к воспроизводимому окружению и настройке машин кодом

01

Кластер

02

Slurm

03

Массивы задач

04

MPI

05

Apptainer

06

Ansible

07

Kubernetes

Цель лекции

Перенести расчёт на кластер и сохранить возможность получить тот же результат снова

Планировщик выделяет ресурсы по запросу, массив задач распараллеливает независимые расчёты, MPI — связанные. Образ Apptainer фиксирует окружение, а Ansible описывает настройку машин кодом, который проверяется пробным прогоном и повторяется без изменений.

От рабочей машины

к кластеру

ЗадачаИнструментЧасть
очередь и запрос ресурсовSlurm: sbatch, squeue, scancel1, 2
сотни независимых расчётовмассив задач, зерно от номера3
один связанный расчётMPI: mpi4py, mpiexec, srun4
одно окружение вездеобраз Apptainer5
одинаковая настройка машинAnsible: плейбук, роль6
службы и облачные машиныKubernetes, Terraform7

Пояснение

Ресурсы кластера не берут на узле напрямую: их запрашивают у планировщика и получают на время задачи.

Для физика

Следующая ступень лестницы ускорения из главы «CUDA и вычисления на GPU»: после видеокарты — кластер.

Кластер

Зачем кластер и планировщик: узлы, общая файловая система, очередь, резервирование

01

Кластер

Slurm

Массивы задач

MPI

Apptainer

Ansible

Kubernetes

Устройство

кластера

рабочая машина

редактор, git

ssh

узел входа

sbatch, squeue

управляющий узел

slurmctld

узел 1

slurmd

узел 2

slurmd

узел 3

slurmd

общая файловая система

home и scratch видны на всех узлах

01

Узел входа

редактирование и постановка задач, не счёт

02

Вычислительные узлы

сотни и тысячи, часть — с видеокартами

03

Общая файловая система

home с копиями, scratch быстрый и без копий

04

Сеть и модули

InfiniBand для MPI; module load gcc openmpi

Для физика

HTC — тысячи независимых задач, например обработка событий детектора; HPC — один расчёт на многих узлах, например пучок с пространственным зарядом.

Планировщик:

ресурсы запрашивают

задача = скрипт + запрос ресурсов
 
#SBATCH --ntasks=64          # процессы
#SBATCH --cpus-per-task=4    # ядра на процесс
#SBATCH --mem=8G             # память на узел
#SBATCH --time=02:00:00      # лимит времени
#SBATCH --gres=gpu:1         # видеокарта

Пояснение

Slurm разрабатывается с 2002 года в LLNL, далее — компанией SchedMD; в декабре 2025 года SchedMD купила NVIDIA, объявив, что Slurm останется открытым.

01

Выделение целиком

ядра и память задачи не делятся с чужими

02

Приоритет

ожидание, размер задачи, справедливая доля

03

Лимит времени

обещание: задача сверх лимита снимается

04

Планировщики

Slurm, OpenPBS и PBS Pro, HTCondor, LSF

Важно

Kubernetes распределяет ресурсы между долгоживущими службами; пакетным расчётам нужен планировщик очереди.

Backfill:

заполнение промежутков

for job in list(queue[1:]):
    name, cores, run, limit = job
    fits_time = t + limit <= reserve
    if cores <= free and (fits_time or cores <= spare):
        queue.remove(job)
        running.append((t + run, t + limit, cores, name))
        start[name] = t
        free -= cores
        if not fits_time:
            spare -= cores

Важно

Планировщик знает только лимит задачи, а не её фактическую длительность.

01

FIFO

строгий порядок, ядра простаивают

02

Резерв

голове очереди — ближайший момент старта

03

Заполнение

меньшие задачи, успевающие до резерва

04

Точный --time

выгоден самому пользователю

Пояснение

Мюалем и Фейтельсон (IEEE TPDS, 2001): при оценках, завышенных в несколько раз, средние показатели очереди даже улучшаются. Проигрывает сама задача, запросившая лишнее.

Демонстрация:

очередь

01

Модель: 8 ядер, восемь задач A–H в момент 0

02

У задачи — ядра, длительность и лимит

03

FIFO, backfill и backfill, где H просит 120 мин

Что наблюдается

FIFO — ожидание 88,8 мин, конец 170 мин, загрузка 67 %; backfill — 45,0 мин, 130 мин, 88 %; с лимитом 120 мин задача H стартует в 130 мин вместо 20.

student@lab — запись со стенда

Результат:

три расписания

Три расписания восьми задач на 8 ядрах: FIFO, backfill и backfill, в котором задача H запросила 120 минут; B — синяя, H — оранжевая

Что наблюдается

Загрузка ядер растёт с 67 до 88 %, а большая задача B стартует в 60 мин во всех вариантах; лишние 100 минут в лимите задачи H обходятся ей в 110 минут ожидания.

Slurm

Службы, команды, пакетный скрипт, ресурсы и лимиты, учёт

02

Кластер

Slurm

Массивы задач

MPI

Apptainer

Ansible

Kubernetes

Устройство

Slurm

команды

sbatch, srun, squeue, scancel

slurmdbd

учёт в MySQL или MariaDB

slurmctld

очередь, расписание, узлы

узел

slurmd → slurmstepd

узел

slurmd → slurmstepd

узел

slurmd → slurmstepd

munge: один ключ на всех узлах

01

slurmctld

очередь, расписание, состояние узлов

02

slurmd

на каждом узле: процессы задачи через slurmstepd

03

munge

подлинность сообщений по общему ключу

04

slurmdbd

необязательный учёт в MySQL или MariaDB

Пояснение

Стенд: один узел lab — 2 ядра и 3000 МБ для задач, раздел main, Slurm 23.11.4 из Ubuntu 24.04.

Команды

пользователя

КомандаНазначение
sinfoсостояние разделов и узлов
squeueочередь: состояние и причина ожидания
sbatch скриптпостановка пакетной задачи
srun программашаг задачи; вне задачи — выделение и запуск
sallocвыделение для интерактивной работы
scancel номерснятие задачи
scontrol show job|nodeподробности о задаче или узле
sacctучёт завершённых задач

PD   ожидает: Resources, Priority,
     Dependency, PartitionConfig
R    выполняется
CG   завершается
CD   завершена успешно
F    завершилась с ошибкой
CA   снята
TO   превышен лимит времени
OOM  превышена память

Пояснение

job — выделение ресурсов, step — запуск srun внутри выделения, task — процесс шага.

Пакетный

скрипт

#!/bin/bash
#SBATCH --job-name=hello
#SBATCH --cpus-per-task=1
#SBATCH --mem=100M
#SBATCH --time=00:01:00
#SBATCH --output=hello-%j.out
# Первая задача: что планировщик выделил и что видит процесс
echo "задача $SLURM_JOB_ID, узел $SLURMD_NODENAME, раздел $SLURM_JOB_PARTITION"
echo "ядер: $SLURM_CPUS_ON_NODE, память: $SLURM_MEM_PER_NODE МБ"
python3 -c 'import os; print("доступны ядра:", sorted(os.sched_getaffinity(0)))'
sleep 5

Пояснение

Строки #SBATCH — комментарии для оболочки и параметры для sbatch; %j в имени файла вывода заменяется номером задачи.

Важно

Окружение оболочки наследуется (--export=ALL): python3 в задаче — тот же, что при постановке, например из venv.

Демонстрация:

первая задача

01

sinfo: раздел main и узел lab

02

scontrol show node: ядра, память, состояние

03

sbatch slurm/hello.sh: номер задачи

04

squeue с коротким набором полей

05

Через 6 с: файл вывода задачи

Что наблюдается

Узел lab: 2 ядра, 3000 МБ, idle; задача 60 сразу в состоянии R; в выводе — «ядер: 1, память: 100 МБ» и «доступны ядра: [0]», одно ядро из двух.

student@lab — запись со стенда

Ресурсы

и лимиты

01

--cpus-per-task

ядра на процесс: потоки OpenMP и BLAS

02

--ntasks

число процессов, например MPI

03

--mem и --mem-per-cpu

память на узел или на ядро

04

cgroup v2: только выделенное

ConstrainCores и ConstrainRAMSpace

export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
 
память сверх --mem      → OUT_OF_MEMORY
время сверх --time      → TIMEOUT
-c 4, в разделе 2 ядра  → PD (PartitionConfig)

Важно

Запрос с запасом выполним, но задача дольше ждёт, не помещается в промежутки очереди и занимает место соседей.

Учёт

выполненных задач

01

sacct

JobID, State, Start, Elapsed, ExitCode, MaxRSS

02

slurmdbd

полный учёт: шаги, пиковая память, seff

03

jobcomp/filetxt

строка на задачу, без шагов и памяти

04

Запрос по измерению

память × 1,2, время × 1,5 от измеренного

# стенд: Slurm 23.11.4 без slurmdbd
squeue -t all     # завершённые задачи
                  # в течение MinJobAge, 300 с
python3 timeline.py /var/log/slurm/jobcomp.log
sacct -c          # только root и slurm

Важно

В 23.11.4 sacct -c открывает журнал jobcomp на дозапись, и обычный пользователь получает отказ в доступе.

Демонстрация:

ресурсы и лимиты

01

-c 4 на узле с двумя ядрами

02

Две задачи по 2 ядра со sleep 10

03

squeue: причины ожидания

04

scancel -t PD: снятие ожидающих

05

--mem=200M, программа на 400 МиБ

06

Через 12 с: вывод задачи 64

07

squeue -t all: итог по задачам

Что наблюдается

61 принята и ждёт: PartitionConfig; 63 ждёт ядра: Resources; 64 — oom_kill после «занято 150 МиБ», OUT_OF_MEMORY; 62 — COMPLETED за 0:10.

student@lab — запись со стенда

Правила

общего кластера

01

Узел входа

редактирование и постановка задач, не счёт

02

Рабочая область

данные — в scratch, результаты — в архив

03

Мелкие задачи

массивом или объединением в порции

04

Квоты

на объём и на число файлов

05

Окружение

модули кластера, venv или образ

06

Лимит по измерению

с запасом, но без кратного завышения

Пояснение

Сессия tmux на узле входа служит для интерактивной работы; долгий расчёт отправляется в очередь командой sbatch.

Массивы задач

Скан параметров одним скриптом, сбор по зависимости, зёрна генератора

03

Кластер

Slurm

Массивы задач

MPI

Apptainer

Ansible

Kubernetes

Массив

задач

#!/bin/bash
#SBATCH --job-name=scan
#SBATCH --array=0-11
#SBATCH --cpus-per-task=1
#SBATCH --mem=300M
#SBATCH --time=00:05:00
#SBATCH --output=logs/scan-%A_%a.out
# Строка k двумерного скана токов дублета: I_F = 2k А, I_D = 0…24 А
python3 scan_point.py "$SLURM_ARRAY_TASK_ID" results/

01

SLURM_ARRAY_TASK_ID

номер задачи 0…11; параметр — в программе

02

%A_%a

номер массива и номер задачи в имени вывода

03

-d afterok:ID

сбор — после успешного завершения всех задач

Пояснение

Суффикс %2 (--array=0-11%2) ограничивает число одновременных задач массива; MaxArraySize по умолчанию 1001, номера 0–1000. Каталог logs для вывода Slurm с версии 23.02 создаёт сам.

Скан токов

дублета

пучок

электроны 5 МэВ

0,5 м

QF

ток I_F

0,3 м

QD

ток I_D

1,5 м

коллиматор

радиус 3 мм

Bρ = p/e ≈ 0.0183 Тл·м      # электроны 5 МэВ
1/f = G·L/(Bρ·(1 + δ)),  G = 0.02 Тл/(м·А)·I
ε = 1 мм·мрад,  β = 2 м,  α = -1,  δ: 2 %
 
задача k:  I_F = 2k А,  I_D = 0…24 А
точка:     4·10⁶ частиц, зерно [скан, k, j]

01

Задача массива

строка карты: 25 точек тока QD

02

Результат

файл results/row-KK.json

03

collect.py

пропуски и лучшая пара токов

Для физика

Сила линзы зависит от импульса частицы, поэтому долю прошедших частиц считают методом Монте-Карло, а не по огибающей.

Демонстрация:

скан токов

01

Массив scan.sh: номер — в переменной J

02

squeue: задачи массива в очереди

03

Сбор: зависимость afterok от массива

04

Через 30 с: таблица collect.py

05

timeline.py: расписание по журналу

Что наблюдается

Массив 79: 79_0 и 79_1 — R, 79_[2-11] — PD (Resources); 12 строк без пропусков, лучшая пара 18 и 19 А — 94,78 %; 35 с счёта за 22 с.

student@lab — запись со стенда

Результат:

карта и задачи массива

Слева карта прохождения пучка через коллиматор по токам QF и QD с максимумом 94,8 % при 18 и 19 А; справа задачи массива на двух ядрах по журналу jobcomp и отметка сбора

Что наблюдается

Максимум 94,8 % при I_F = 18 А и I_D = 19 А; задачи массива шли парами по 2–3 с, промежутки между парами — задержка планировщика; сбор — после всех 12 задач.

Зёрна

генератора

rng = np.random.default_rng(42)                # (а)
rng = np.random.default_rng(int(time.time()))  # (б)
rng = np.random.default_rng([20261005, k])     # (в)

01

(а) одно зерно

одинаковые результаты, разброс равен нулю

02

(б) время старта

задачи массива стартуют в одну секунду

03

(в) [общее зерно, номер]

SeedSequence: независимые воспроизводимые потоки

Пояснение

Политика совместимости NumPy: тот же поток гарантирован для одной сборки в одном окружении; между версиями и платформами возможны редкие отличия — версию фиксирует образ.

Для физика

В файл результата записываются зерно, версии Python и NumPy.

Демонстрация:

зёрна

01

Восемь задач по 200 000 квантов: счётчик NaI(Tl)

02

(а) зерно 42 во всех задачах

03

(б) зерно — время старта, по секундам

04

(в) зерно [общее зерно, номер задачи]

Что наблюдается

Квадратура 0,8808 %: (а) один результат на все 8 задач, разброс 0; (б) три различных, −2,5 σ; (в) восемь различных, −0,5 σ при ожидаемой погрешности 0,0074 %.

student@lab — запись со стенда

MPI из Python

Обмен сообщениями, коллективные операции, разбиение области, ускорение, запуск под Slurm

04

Кластер

Slurm

Массивы задач

MPI

Apptainer

Ansible

Kubernetes

Модель

MPI

from mpi4py import MPI
 
comm = MPI.COMM_WORLD
rank = comm.Get_rank()    # 0, 1, …, size - 1
size = comm.Get_size()
 
$ mpiexec -n 2 python3 pingpong.py

Пояснение

multiprocessing запускает процессы только на одной машине — глава «Многопоточность и GIL».

01

SPMD

одна программа в нескольких процессах, у каждого — ранг

02

Без общей памяти

данные передаются сообщениями, процессы — на разных узлах

03

Реализации

Open MPI и MPICH; MPI 5.0 (2025) — с единым ABI

04

Массив или MPI

независимые расчёты — массив, связанные — MPI

Обмен

точка-точка

# pingpong.py, процесс 0
if rank == 0:
    if pickled:
        comm.send(buf, dest=1)
        buf = comm.recv(source=1)
    else:
        comm.Send(buf, dest=1)
        comm.Recv(buf, source=1)

Важно

Два процесса, одновременно отправляющие друг другу большие сообщения через Send, могут ждать друг друга бесконечно.

01

send и recv

любой объект Python через pickle

02

Send и Recv

буфер NumPy без сериализации

03

Isend, Irecv и Wait

неблокирующий обмен

04

Sendrecv

встречный обмен одной операцией

Демонстрация:

пинг-понг

01

Два процесса на узле, Open MPI 4.1.6

02

Сообщения от 8 Б до 16 МиБ туда и обратно

03

Send/Recv — буфер NumPy, send/recv — pickle

Что наблюдается

8 Б: 0,4 мкс буфером против 4,3 мкс через pickle; 1 МиБ: 36 мкс, 29 ГБ/с, против 179 мкс; 16 МиБ: 1131 мкс против 3909 мкс.

student@lab — запись со стенда

Коллективные

операции

# reduce_mc.py
params = comm.bcast(params, root=0)
mine = range(rank, params["chunks"], size)
hits = sum(count_hits(
    np.random.default_rng([params["seed"], i]),
    params["per_chunk"]) for i in mine)
total = comm.reduce(hits, op=MPI.SUM, root=0)
info = comm.gather((len(mine), hits, elapsed),
                   root=0)

Важно

Коллективную операцию вызывают все процессы коммуникатора в одном порядке, иначе программа зависает.

01

bcast

объект от одного процесса всем

02

scatter и gather

раздать части и собрать результаты

03

reduce и allreduce

сумма или максимум: на одном или на всех

04

Дерево обменов

около log₂ p шагов вместо p

Демонстрация:

reduce

01

64 порции по 2·10⁶ квантов, зерно — номер порции

02

Один процесс: все 64 порции

03

Два процесса: по 32 порции, сумма — reduce

Что наблюдается

На 1 и 2 процессах зарегистрировано 1 128 023 из 128 000 000, 0,8813 % ± 0,0008 %; 0,49 с на одном процессе и по 0,26 с на двух.

student@lab — запись со стенда

Разбиение

области

процесс 0

строки 1…26 сетки 51×51

теневая строка ← процесс 1

теневая строка ← процесс 0

процесс 1

строки 27…51

обмен граничными строками: Sendrecv

up = rank - 1 if rank > 0 else MPI.PROC_NULL
down = rank + 1 if rank < size - 1 else MPI.PROC_NULL
 
def exchange(a):
    comm.Sendrecv(a[1, :], dest=up,
                  recvbuf=a[-1, :], source=down)
    comm.Sendrecv(a[-2, :], dest=down,
                  recvbuf=a[0, :], source=up)

Пояснение

Счёт на итерацию растёт как N²/p, обмен — как N: с ростом сетки доля обмена убывает.

Для физика

Итерации Якоби поточечны: параллельный результат совпадает с последовательным до бита.

Демонстрация:

потенциал в камере

01

Сетка 51×51, 6000 итераций: до сходимости

02

Сетка 801×801, 400 итераций: замер времени

03

Ряд сеток от 101 до 1601: speedup.csv

Что наблюдается

51×51: 249,99 В при точных 250,00 В, расхождение 0, ускорение 0,96; 801×801: 1,87; ряд сеток — 1,56 при N = 101, 1,83–1,88 при 201–801, 1,76 при 1601.

student@lab — запись со стенда

Ускорение

и закон Амдала

S = T₁ / T_p,      E = S / p
 
Амдал:       S ≤ 1 / (f + (1 - f) / p)
             f = 5 %  →  S < 20 при любом p
Густафсон:   S = p - f·(p - 1)
Карп—Флэтт:  e = (1/S - 1/p) / (1 - 1/p)

Пояснение

Эффективность больше 1 возможна из-за кеша: часть задачи помещается в кеш, целая — нет. На стенде её нет: E ≤ 0,94.

01

Амдал

фиксированная задача: предел — последовательная доля

02

Густафсон

задача растёт с p: подробнее сетка, больше частиц

03

Карп—Флэтт

доля по измерению; её рост — накладные расходы обмена

Для физика

Тот же закон для части программы — в главе «Причины низкой скорости Python».

Результат:

ускорение на двух ядрах

Слева ускорение laplace.py на двух процессах стенда против размера сетки, справа закон Амдала для последовательной доли 1, 5 и 10 % до 64 процессов

Что наблюдается

На стенде ускорение 1,56 при N = 101, 1,83–1,88 при N = 201–801 и 1,76 при N = 1601; по закону Амдала при 64 процессах доля 5 % ограничивает ускорение 15,4, доля 1 % — 39,3.

Под Slurm:

srun или mpirun

#!/bin/bash
#SBATCH --job-name=laplace
#SBATCH --ntasks=2
#SBATCH --mem-per-cpu=300M
#SBATCH --time=00:05:00
#SBATCH --output=laplace-%j.out
# Один расчёт MPI двумя способами запуска внутри одного выделения
echo "== srun"
srun --mpi=pmix python3 laplace.py 801 400
echo "== mpirun"
mpirun python3 laplace.py 801 400

01

srun --mpi=pmix

Slurm сам запускает процессы и сообщает ранги через PMIx

02

mpirun

узнаёт выделение у Slurm и запускает процессы его средствами

03

ssh в обход

процессы вне учёта и ограничений

Пояснение

--ntasks — процессы, --cpus-per-task — потоки каждого; на стенде MpiDefault=none.

Демонстрация:

MPI в Slurm

01

srun --mpi=list: интерфейсы запуска MPI

02

sbatch --wait slurm/laplace.sh

03

Вывод: тот же расчёт через srun и mpirun

Что наблюдается

Плагины none, pmix, pmi2, cray_shasta, версия pmix_v5; задача 92: srun — ускорение 1,93, mpirun — 1,87, расхождение в обоих случаях 0.

student@lab — запись со стенда

Apptainer

Воспроизводимое окружение: источники расхождений, файл определения, образ SIF, запуск без root

05

Кластер

Slurm

Массивы задач

MPI

Apptainer

Ansible

Kubernetes

Источники

невоспроизводимости

01

Версии

Python, NumPy, BLAS, glibc, ОС узла

02

Переменные окружения

OMP_NUM_THREADS и подобные

03

Порядок суммирования

реализация BLAS и число потоков

04

Зёрна

из номера задачи, а не из времени

Уровень фиксацииЧто фиксирует
pip freeze, lock-файл, хешипакеты Python
окружение condaи библиотеки не на Python
модули кластерасборки администраторов
образ контейнеравсё, кроме ядра и драйверов

Для физика

Образ и файл определения хранятся рядом с результатом и публикуются вместе со статьёй.

Apptainer

и Docker на кластере

СвойствоDockerApptainer
службадемон от имени rootнет: процесс пользователя
доступгруппа docker — права rootобычный пользователь
пользователь внутриroot по умолчаниютот же UID, что снаружи
каталогитолько подключённые явно$HOME, текущий, /tmp — сразу
образслои в хранилище службыодин файл SIF (squashfs)
GPU и MPI--gpus--nv; MPI хоста и образа

Пояснение

Singularity: LBNL, 2015 год; с ноября 2021 года — Apptainer в Linux Foundation, а Sylabs развивает ответвление SingularityCE.

Важно

Членство в группе docker равносильно правам root на узле, поэтому на общих кластерах Docker не устанавливают.

Файл

определения

Bootstrap: docker-daemon
From: python:3.12-slim
%files
    numpy-2.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl /opt/wheels/
    detector.py /opt/mc/
    mc_detector.py /opt/mc/
%post
    python3 -m pip install --no-index --no-cache-dir /opt/wheels/numpy-*.whl
    rm -rf /opt/wheels
%environment
    export PYTHONDONTWRITEBYTECODE=1
%runscript
    exec python3 /opt/mc/mc_detector.py "$@"
%test
    python3 -c "import numpy; print('numpy', numpy.__version__)"

Пояснение

apptainer/mc.def без разделов %labels и %help; определение хранится в образе: apptainer inspect --deffile mc.sif.

Демонстрация:

сборка образа

01

Заголовок и разделы mc.def

02

apptainer build: последние строки журнала

03

Размер образа и apptainer test

04

inspect --deffile: определение в образе

Что наблюдается

Заголовок docker-daemon и семь разделов; при сборке %test печатает «numpy 2.5.3», затем «Build complete: mc.sif»; образ — 63 МиБ, определение хранится внутри.

root@lab — запись со стенда

Запуск:

exec, run, привязка

#!/bin/bash
#SBATCH --job-name=mc-sif
#SBATCH --cpus-per-task=1
#SBATCH --mem=300M
#SBATCH --time=00:02:00
#SBATCH --output=mc-sif-%j.out
# Расчёт из образа: окружение — внутри mc.sif, данные — в привязанном каталоге
srun apptainer run --bind "$PWD/data:/data" mc.sif 2000000 /data/mc.json

01

exec и run

любая команда или программа %runscript

02

-B хост:контейнер

каталог данных или рабочей области

03

--containall

без $HOME, /tmp и окружения хоста

Пояснение

Под Slurm образ запускается шагом задачи — srun apptainer, — и ограничения cgroups действуют на процесс контейнера, как на любой другой.

Демонстрация:

запуск образа

01

apptainer run mc.sif: расчёт в образе

02

Тот же расчёт вне образа, /opt/l9venv

03

ОС образа и пользователь внутри

04

Привязка -B $PWD/data:/data и владелец файла

05

docker run от student и права сокета

Что наблюдается

В образе и вне его — 17 529 из 2 000 000; внутри — Debian 13 и пользователь student, mc.json принадлежит student; docker — permission denied.

student@lab — запись со стенда

Ansible

Управление конфигурацией: конфигурация как код, инвентарь, плейбук, идемпотентность, роли

06

Кластер

Slurm

Массивы задач

MPI

Apptainer

Ansible

Kubernetes

Конфигурация

как код

01

Дрейф конфигурации

машины, настроенные вручную, расходятся

02

Код в git

ревью, история, откат

03

Пробный прогон

что изменится — до применения

04

Идемпотентность

повторный прогон ничего не меняет

Для физика

Стенды, машины сбора данных, кластер лаборатории и учебный класс пересоздаются за минуты и остаются одинаковыми.

репозиторий git

плейбук, роли, инвентарь

ревью и --check

что изменится

применение

ansible-playbook

узел 1

ok

узел 2

changed

узел 3

ok

Ansible, Salt,

Chef, Puppet

СистемаОписаниеАгент на узлеМодельНа 2026 год
AnsibleYAML и Jinja2нет, нужен Pythonpush по SSH; ansible-pullRed Hat; ansible-core 2.21
SaltYAML и Jinja2миньоншина ZeroMQ; salt-sshBroadcom через VMware
ChefRuby DSLChef Infra Clientpull: клиент применяетProgress Software
Puppetязык Puppet, данные Hieraагентpullсборки Perforce по EULA; OpenVox

Пояснение

push: управляющая машина подключается к узлам; pull: агент сам по расписанию запрашивает описание у сервера и применяет его.

Важно

Владельцы и лицензии меняются: с 2025 года новые сборки Puppet — по лицензионному соглашению, в январе 2025 года вышло открытое ответвление OpenVox.

Инвентарь

и модули

# Узлы стенда; файлы демонстраций пишутся в stand_root, а не в корень
[cluster]
lab ansible_connection=local
 
[cluster:vars]
ansible_python_interpreter="{{ ansible_playbook_python }}"
stand_root=/tmp/l21rec/stand
slurm_partition=main

01

Инвентарь

группы узлов и их переменные; подключение ssh или local

02

Модуль

ping, setup, command, file, template, apt, systemd_service, pip, user

03

Разовая команда

ansible группа -m модуль -a аргументы

Пояснение

На узле нужен Python 3.9–3.14 (ansible-core 2.21); без него работают только модули raw и script.

Демонстрация:

инвентарь

01

Инвентарь: группа cluster, узел lab

02

ansible cluster -m ping

03

Факт ansible_processor_vcpus модулем setup

04

ansible cluster -a nproc: модуль command

Что наблюдается

ping — SUCCESS и pong; факт ansible_processor_vcpus равен 2; nproc — CHANGED, rc=0, вывод 2: модуль command всегда сообщает об изменении.

root@lab — запись со стенда

Плейбук

и идемпотентность

  tasks:
    - name: Каталог конфигурации
      ansible.builtin.file:
        path: "{{ stand_root }}/etc/slurm"
        state: directory
    - name: Файлы конфигурации из шаблонов
      ansible.builtin.template:
        src: "{{ item.src }}"
        dest: "{{ stand_root }}/etc/slurm/{{ item.dest }}"
      loop:
        - {src: slurm.j2, dest: slurm.conf}
        - {src: cgroup.j2, dest: cgroup.conf}
      loop_control:
        label: "{{ item.dest }}"
      notify: Перезапуск Slurm
  handlers:
    - name: Перезапуск Slurm
      ansible.builtin.debug:
        msg: перезапуск slurmctld и slurmd

01

Сценарий (play)

группа узлов и список задач

02

Задача

модуль с описанием состояния: ok или changed

03

--check и --diff

пробный прогон и изменения файлов

04

Обработчик

один раз в конце и только при изменениях

Важно

command и shell неидемпотентны: их ограничивают параметрами creates и removes или условием changed_when.

Демонстрация:

плейбук

01

Пробный прогон --check: что изменится

02

Применение плейбука stand.yml

03

Повторное применение: последние три строки

Что наблюдается

--check и первое применение — changed для каталога и двух файлов, обработчик, ok=3 changed=2; повторное применение — ok=2 changed=0, обработчик не вызван.

root@lab — запись со стенда

Роли

и шаблоны

roles/slurm_node/
  defaults/main.yml     # значения по умолчанию
  handlers/main.yml     # перезапуск служб
  tasks/main.yml        # каталог, шаблоны, службы
  templates/cgroup.j2
  templates/slurm.j2    # шаблон slurm.conf
 
# site.yml
- name: Узел Slurm из роли
  hosts: cluster
  gather_facts: false
  roles:
    - slurm_node

01

Роль

tasks, handlers, templates, defaults, vars, files, meta

02

Шаблон Jinja2

slurm.j2 получает переменные роли

03

Старшинство

22 уровня: defaults — второй снизу, -e — высший

04

lab-stand.yml

пакеты, munge, роль, mpi4py, пользователь

Пояснение

Плейбук стенда курса собран из той же роли: пробный прогон на готовом стенде не меняет ничего.

Демонстрация:

роль

01

Файлы роли slurm_node

02

site.yml -e slurm_mem_mb=2800 -CD

03

lab-stand.yml --check: стенд целиком

Что наблюдается

Роль — пять файлов; с -e slurm_mem_mb=2800 дифф одной строки RealMemory=3000 → 2800, ok=3 changed=1 skipped=2; стенд целиком — ok=8 changed=0.

root@lab — запись со стенда

Kubernetes и Terraform

Обзор: службы в контейнерах, облачные ресурсы кодом, инфраструктура как код

07

Кластер

Slurm

Массивы задач

MPI

Apptainer

Ansible

Kubernetes

Kubernetes

на одном слайде

управляющий уровень (control plane)

kube-apiserver

все запросы

etcd

состояние кластера

kube-scheduler

узел для пода

controller-manager

желаемое → фактическое

рабочий узел

kubelet: поды, пробы
kube-proxy: правила Service
под: контейнеры

рабочий узел

kubelet: поды, пробы
kube-proxy: правила Service
под: контейнеры

01

Под

один или несколько контейнеров; sidecar — стабильны с 1.33

02

Deployment

число экземпляров и выкатка новой версии

03

Service

постоянное имя и адрес для подов

04

Job и CronJob

пакетные задачи до завершения

Пояснение

Желаемое состояние описывается декларативно, а контроллеры постоянно приводят к нему фактическое.

Kubernetes

или Slurm

ЗадачаKubernetesSlurm
долгоживущие службыосновное назначениене предназначен
пакетные задачиJob, CronJob; очереди — расширенияосновное назначение
связанный расчёт MPIчерез расширенияsrun, PMIx, быстрая сеть
ядра, память, GPUrequests и limits податочное распределение по задачам
учёт и справедливая доляквоты, расширенияfair-share, sacct

Для физика

Kubernetes уместен для JupyterHub, веб-доступа к данным установки, архива и мониторинга, конвейеров обработки.

Важно

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

Terraform

и OpenTofu

terraform init      # провайдеры: программы на Go
terraform plan      # описание против состояния
terraform apply     # создать и изменить ресурсы
terraform destroy   # удалить ресурсы
 
terraform.tfstate   # связь кода с ресурсами,
                    # общее хранилище с блокировкой

Для физика

Облачные машины на время расчёта или учебного класса; на собственном оборудовании лаборатории Terraform не нужен.

01

HCL

описание облачных машин, дисков и сетей

02

Состояние

tfstate: что создано этим кодом

03

Модули

переиспользуемые блоки описаний

04

Лицензии

Terraform — BSL 1.1, OpenTofu — MPL 2.0

Инфраструктура как код:

итог

01

Описание в git

ревью, история, откат

02

Пробный прогон

terraform plan, ansible-playbook --check

03

Изменяемая

Ansible приводит работающие машины к описанию

04

Неизменяемая

новый образ вместо правки: SIF, контейнер, образ машины

Важно

Риски: смена лицензий и владельцев (Terraform, Puppet), прекращение доступа к реестрам пакетов и образов.

Для физика

Копии пакетов, колёс и образов расчёта хранятся у себя: стенд курса установлен без сети, из файлов, проверенных по контрольным суммам.

Итоги

лекции

01

Ресурсы запрашивают по измерению: точный лимит сокращает ожидание.

02

Независимые расчёты — массивом задач, зерно — от номера задачи.

03

Связанные — MPI: ускорение ограничено обменом и последовательной долей.

04

Окружение — неизменяемым образом, который запускается без root.

05

Настройка машин — кодом: пробный прогон и повтор без изменений.

06

Копии пакетов, колёс и образов — у себя.

Упражнения

и задание

01

Массив со своим параметром

скан градиента или энергии, сбор зависимостью afterok

02

Обмен в laplace.py

Isend и Irecv или разбиение блоками, замер ускорения

03

Свой образ

расчёт в образе Apptainer, запуск через srun

04

Роль Ansible

пакет и конфигурация с обработчиком перезапуска

Пояснение

Задание: плейбук, поднимающий на чистой машине одноузловой Slurm и запускающий проверочную задачу; повторный прогон — без изменений.

Для физика

Массив для своей задачи — скан параметра установки или серия Монте-Карло детектора.

Источники

1

Книга курса: главы «Кластеры и воспроизводимое окружение для расчётов», «CUDA и вычисления на GPU», «Docker», «Внутреннее устройство Linux», «Многопоточность и GIL»

phys-dev.github.io/soft-dev-book

2

Документация Slurm 23.11: slurm.conf, sbatch, srun, sacct, массивы задач, MPI Users Guide, cgroup.conf; Yoo A. B., Jette M. A., Grondona M. SLURM // JSSPP 2003

slurm.schedmd.com

3

MPI 5.0 (MPI Forum, 2025); Dalcin L., Fang Y.-L. L. mpi4py: Status Update After 12 Years of Development // Computing in Science & Engineering, 2021

mpi4py.readthedocs.io

4

Документация Apptainer 1.5; Kurtzer G. M., Sochat V., Bauer M. W. Singularity: Scientific containers for mobility of compute // PLoS ONE, 2017

apptainer.org/docs

5

Документация Ansible, Kubernetes, OpenTofu; Morris K. Infrastructure as Code. 3rd ed. – O'Reilly, 2025

docs.ansible.com

6

Amdahl G. M. (1967); Gustafson J. L. (1988); Karp A. H., Flatt H. P. (1990); Mu'alem A. W., Feitelson D. G. // IEEE TPDS, 2001

Спасибо за внимание

Вопросы

phys-dev.github.io/soft-dev-book

21