Введение
У редактора Field Notes есть две фотостанции с разными подборками для обработки. Контактный лист и соответствующий список файлов помогают отбирать фотографии, но запущенное фоновое задание или старый одноимённый файл ещё не означают готовую поставку. Создайте небольшой пример, затем используйте Ansible, чтобы дождаться и получить реальные текущие результаты каждой станции.
Нужны базовая навигация в терминале и практика с небольшим inventory Ansible, переменными и задачами command/file. Доступ SSH, лицензированные фотографии и инструмент обработки уже подготовлены. Лабораторная объясняет списки аргументов, циклы, идентификаторы заданий каждого хоста, проверки завершения и назначения fetch до их применения. Продолжительность — оценка автора с учётом чтения и обычных исправлений, а не измеренное время реальных новичков.
Создайте небольшой контактный лист
На этом шаге вы выполните небольшой фотопроцесс через Ansible, получите настоящее изображение и список файлов и используете их как образец для более крупного задания.
Откройте вкладку Photo Desk. В A previous walk нажмите Open contact sheet. Контактный лист размещает уменьшенные версии нескольких фотографий на одной странице, чтобы редактор сравнивал кадры, не открывая каждый оригинал. Этот исторический пример отмечен previous-walk: это справочный материал, а не сегодняшняя готовая работа. На узком экране прокручивайте содержимое рамки изображения, чтобы прочитать имена файлов. Нажмите Back to photo desk, чтобы вернуться на страницу в той же вкладке.
История и распределение станций вымышлены. Пейзажные фотографии — лицензированная учебная коллекция; Photo credits перечисляет настоящих фотографов и источники. Подготовленный инструмент читает реальные JPEG и создаёт контактный лист PNG и список CSV. Вы автоматизируете этот инструмент, а не пишете код обработки изображений.
Перейдите на вкладку Controller. Она открывается в /home/labex/project. Контроллер запускает Ansible. Две его цели SSH — контейнеры, разделяющие ядро этой учебной VM, а не две независимые VM. У каждой цели собственные файлы, даже если пути называются одинаково.
pwd показывает текущий каталог, а cat выводит файл. Прочитайте подготовленные inventory и переменные:
pwd
cat inventory.yml
cat group_vars/all.yml
cat host_vars/camera-north.yml
cat host_vars/camera-south.yml
Группа cameras в inventory содержит camera-north и camera-south с реальными параметрами SSH. Файл проекта ansible.cfg выбирает этот inventory.yml и подготовленные настройки доверия хостам. Исходный inventory.ini образа не является inventory курса. Сохраните настройки, inventory и файлы переменных без изменений.
Фигурные скобки в стиле JSON в этих файлах допустимы в YAML. group_vars/all.yml задаёт общие переменные: input_root — /srv/photo-sheets/batches, output_root — /srv/photo-sheets/rendered, а collection_root — каталог collected на контроллере рядом с playbook. Файл в host_vars действует для одноимённого хоста. Здесь photo_batch выбирает morning-walk на северной станции и afternoon-walk на южной. Сегодняшние подборки различаются содержанием и размером.
Сначала используйте подготовленную входную подборку sample из двух фотографий на северной станции. Модуль Ansible command запускает готовую программу на этой цели SSH. Список argv передаёт по одному аргументу на элемент, без интерпретации командной строки оболочкой. Параметр --input выбирает исходный каталог, --output — каталог результатов, а --station указывает станцию в результате. Оригинальные фотографии не редактируются.
{{ variable }} вставляет значение Ansible. inventory_hostname — имя текущего хоста в inventory; playbook_dir — каталог playbook на контроллере. gather_facts: false пропускает сбор сведений о машине, ненужных этому фотозаданию.
Модуль fetch передаёт файл с цели SSH на контроллер, в направлении, обратном copy. loop повторяет передачу для двух указанных имён; {{ item }} — текущее имя. По умолчанию fetch сохраняет дерево каталогов с хостом и исходным путём. Здесь flat: true убирает исходный путь, поэтому назначение явно включает inventory_hostname. Это разделяет одноимённые файлы разных станций. fetch создаёт необходимые каталоги назначения.
Создайте sample.yml. cat > filename записывает файл и заменяет существующий одноимённый файл. <<'YAML' начинает here-document: вставляйте текст до заключительного YAML на отдельной строке. Разделитель в кавычках сохраняет вставленный текст буквально; Ansible обрабатывает выражения при запуске playbook. Для отступов YAML используются пробелы:
cat > sample.yml <<'YAML'
- name: Build a small selection before scheduling both workstations
hosts: camera-north
gather_facts: false
tasks:
- name: Build the two-photo practice sheet
ansible.builtin.command:
argv:
- /usr/local/bin/photo-sheet
- --input
- "{{ input_root }}/sample"
- --output
- "{{ output_root }}/sample"
- --station
- "{{ inventory_hostname }}"
- name: Collect the practice image and file list
ansible.builtin.fetch:
src: "{{ output_root }}/sample/{{ item }}"
dest: "{{ playbook_dir }}/sample/{{ inventory_hostname }}/"
flat: true
loop:
- contact-sheet.png
- files.csv
YAML
Перед запуском проверьте синтаксис. --syntax-check проверяет структуру playbook, но не обрабатывает фотографии:
ansible-playbook sample.yml --syntax-check
При опечатке или ошибке отступа снова скопируйте весь playbook и повторите проверку. Теперь запустите его:
ansible-playbook sample.yml
Задача обработки изображения по умолчанию синхронная: Ansible ждёт завершения программы, прежде чем получать файлы. В итогах должны быть failed=0 и unreachable=0. Фотозадание выполняет обработку и может сообщать changed; повторная обработка изображений не гарантирует отсутствия изменений.
Прочитайте полученный список:
cat sample/camera-north/files.csv
Заголовок — station,batch,position,filename,sha256,width,height. Должны быть две строки данных для camera-north и sample в том же порядке слева направо, что и картинки. Файлы называются frame-01.jpg и frame-02.jpg. SHA256 — отпечаток содержимого каждого исходного файла; запоминать длинные значения не нужно. Размеры относятся к оригинальной фотографии, а не маленькому предпросмотру.
Перейдите в Photo Desk и обновите страницу. В Small practice sheet используйте Open contact sheet, чтобы осмотреть настоящий результат из двух фото. Вернитесь через Back to photo desk, затем нажмите Download contact sheet (PNG) и Download file list (CSV) в учебной карточке. Откройте загруженные изображение и CSV: обе фотографии, имена и порядок строк должны совпасть. При повторной загрузке браузер может добавить номер к имени файла.
Карточка ниже показывает настоящее изображение из двух фото и обе ссылки загрузки. Перед использованием файлов проверьте станцию и метку sample.

Если команда сообщает «No such file or directory», ls покажет текущий каталог для проверки написания. Вернитесь через cd /home/labex/project и повторите нужную команду. Сохраните все исходные фотографии, исторические образцы, заметки редактора и подготовленные подключения. Большие текущие подборки ещё не созданы.
Дождитесь каждого задания перед получением файлов
На этом шаге вы запустите текущие подборки обеих станций, дождётесь собственного успешного результата каждого хоста и получите соответствующие файлы без конфликтов.
Оставайтесь в Controller, в /home/labex/project. Группа cameras выбирает обе цели; переменная photo_batch каждой цели по-прежнему выбирает её подготовленную подборку. Используйте эти переменные, чтобы тот же процесс работал с другими участниками, именами подборок и каталогами без жёстко заданных названий двух станций и их путей.
Синхронная задача ждёт завершения, прежде чем Ansible перейдёт к следующей. С async: 120 Ansible даёт удалённой операции 120 секунд на выполнение. poll: 0 запускает её, не ожидая завершения в этой задаче. Успешный старт не равен успешной обработке: удалённое задание ещё может закончиться ошибкой.
register: photo_job отдельно сохраняет результат старта для каждого хоста inventory. Его ansible_job_id идентифицирует задание на этом хосте. JID одной станции нельзя использовать для запроса результата на другой. Модуль debug выводит сохранённые наблюдения, позволяя увидеть разные назначения.
async_status спрашивает тот же хост о его JID и сохраняет каждый ответ в photo_result. until: photo_result is finished повторяет запрос до истинного нативного флага завершения; retries: 60 ограничивает повторы, а delay: 1 ждёт секунду между ними. Повторяются запросы состояния, а не создаются новые фотозадания. SSH и обработка добавляют реальное время. Небольшая подборка может уже закончиться к первому запросу; искусственных задержек в упражнении нет.
Одного завершения недостаточно для успеха. rc удалённой программы — её код выхода: ноль означает успех. По умолчанию async_status передаёт ненулевой результат как ошибку. Дополнительный assert явно требует нулевого кода, но не превращает ошибку в успех. Следующий отладочный вывод показывает реальные finished и rc до получения файлов. Эквивалентный процесс может полагаться на нативную передачу ошибки без дополнительного утверждения.
Оба хоста создают одинаковые имена, contact-sheet.png и files.csv, в собственных файловых системах. Получайте их только после успеха и сохраняйте каждую пару в каталоге её хоста inventory на контроллере. Photo Desk читает как явно заданное плоское размещение, так и стандартную иерархию хоста и исходного пути внутри папки станции. Он никогда не дополняет отсутствующие результаты историческими файлами.
Запишите process-photos.yml тем же here-document с разделителем в кавычках, что и на предыдущем шаге:
cat > process-photos.yml <<'YAML'
- name: Build and collect each workstation's current photo selection
hosts: cameras
gather_facts: false
tasks:
- name: Start this workstation's contact sheet
ansible.builtin.command:
argv:
- /usr/local/bin/photo-sheet
- --input
- "{{ input_root }}/{{ photo_batch }}"
- --output
- "{{ output_root }}"
- --station
- "{{ inventory_hostname }}"
async: 120
poll: 0
register: photo_job
- name: Show the job assigned to this workstation
ansible.builtin.debug:
msg:
workstation: "{{ inventory_hostname }}"
job: "{{ photo_job.ansible_job_id }}"
- name: Wait for this workstation's own job
ansible.builtin.async_status:
jid: "{{ photo_job.ansible_job_id }}"
register: photo_result
until: photo_result is finished
retries: 60
delay: 1
- name: Require successful processing before collecting
ansible.builtin.assert:
that: photo_result.rc == 0
- name: Read the actual completion result
ansible.builtin.debug:
msg:
workstation: "{{ inventory_hostname }}"
finished: "{{ photo_result.finished }}"
rc: "{{ photo_result.rc }}"
- name: Collect the image and its file list by workstation
ansible.builtin.fetch:
src: "{{ output_root }}/{{ item }}"
dest: "{{ collection_root }}/{{ inventory_hostname }}/"
flat: true
loop:
- contact-sheet.png
- files.csv
YAML
Проверьте синтаксис:
ansible-playbook process-photos.yml --syntax-check
Запустите настоящую обработку:
ansible-playbook process-photos.yml
Каждая станция должна показать свой идентификатор, затем finished: true и rc: 0 в сообщении завершения. Итоговая сводка должна содержать failed=0 и unreachable=0 для обоих хостов. В северной подборке три фото, в южной шесть. Реальные задания могут закончиться в разное время. Запуск демонстрирует настоящую асинхронную обработку, а не гарантирует производительность больших нагрузок.
Прочитайте оба полученных списка:
cat collected/camera-north/files.csv
cat collected/camera-south/files.csv
Поля station и batch должны соответствовать назначению хоста. Одинаковые имена не означают одинаковые исходные фотографии. Позиция, имя, отпечаток и размеры каждой строки описывают одну настоящую входную фотографию; лист использует тот же порядок.
Перейдите в Photo Desk и обновите страницу. В Current collections откройте Open contact sheet каждой станции. Осмотрите листы из трёх и шести фотографий в полном размере, возвращаясь через Back to photo desk. Загрузите PNG и CSV каждой станции, откройте оба файла и проверьте соответствие изображений и полных строк. Образец и небольшая учебная коллекция должны оставаться доступны.
Сравните подписи хоста и подборки двух карточек. Северный лист имеет один ряд из трёх изображений; южный — два ряда по три с другой исходной подборкой.

Завершившееся задание тоже может быть неуспешным
Вернитесь в Controller. Теперь намеренно выберите отсутствующий вход только на северной станции. --limit camera-north ограничивает вызов этим хостом inventory. --extra-vars photo_batch=missing переопределяет переменную подборки лишь для этого вызова, не редактируя подготовленный файл. Задание запустится, затем сообщит о завершившейся ошибке обработки. Существующие правильные файлы нельзя считать новым успешным результатом.
Ожидается ошибка этой команды:
ansible-playbook process-photos.yml --limit camera-north --extra-vars photo_batch=missing
Немедленно выведите статус предыдущей команды. $? хранит код выхода последней команды, а echo его выводит. Другая выполненная перед этим команда заменит значение:
echo $?
Ожидайте ненулевой статус CLI Ansible, обычно 2. В сообщении ошибки удалённый результат завершён, программа имеет rc, равный 2, а ошибка называет /srv/photo-sheets/batches/missing. Статус CLI и код удалённой программы — разные значения; здесь оба случайно равны 2. Завершённая ошибка не является успешным получением файлов. Задача fetch не должна выполняться для этой неудачной попытки. Южная станция не входит в данный вызов.
Исправьте выбор, вернув подготовленное значение, и повторите только северную станцию:
ansible-playbook process-photos.yml --limit camera-north
Запуск должен завершиться успешно и получить реальный результат morning-walk. Южная коллекция остаётся доступна после её успешного задания. Вернитесь в Photo Desk, обновите страницу и снова загрузите северные PNG и CSV для проверки правильной подборки из трёх фото. Оставьте process-photos.yml пригодным для повторного использования, сохраните оригиналы и исторические результаты и держите заметки редактора приватными.
При обычной ошибке YAML или имени используйте изученные ls, возврат в каталог и исправление полного блока, затем повторите проверку синтаксиса. Не сбрасывайте VM и не меняйте исходную фотографию, чтобы скрыть ошибку входа. Повтор успешного фотозадания заново выполняет обработку; неизменная контрольная сумма fetch не означает, что обработки не было.
После poll: 0 Ansible оставляет кэш состояния задания. Проверив завершённое задание, можно удалить его запись с помощью async_status, mode: cleanup и jid этого хоста. Здесь это необязательное обслуживание. Очистка кэша не отменяет выполняющееся задание и не удаляет контактный лист.
Резюме
Вы создали настоящий небольшой контактный лист, затем с помощью Ansible запустили и дождались двух асинхронных заданий, каждое на своём хосте. Вы получили полные пары изображения/CSV по станциям, отличили завершившуюся ошибку от успеха и исправили отсутствующую входную подборку повтором только затронутого хоста.



