콘택트 시트 만들고 수집하기

AnsibleBeginner
지금 연습하기

소개

Field Notes 편집자는 서로 다른 사진 묶음을 처리할 두 워크스테이션을 가지고 있습니다. 콘택트 시트와 대응하는 파일 목록이 있어야 사진 선별에 활용할 수 있습니다. 백그라운드 작업이 시작되었거나 같은 이름의 기존 파일이 있다는 것만으로 납품이 끝난 것은 아닙니다. 작은 예제를 만든 다음 Ansible로 각 워크스테이션의 실제 현재 결과를 기다리고 수집합니다.

기본 터미널 탐색과 작은 Ansible 인벤토리, 변수, command/file 태스크 사용 경험이 필요합니다. SSH 접속, 사용 허가된 사진, 이미지 도구는 준비되어 있습니다. 이 실습에서는 인수 목록, 반복, 호스트별 작업 ID, 완료 확인, 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은 파일을 출력합니다. 준비된 인벤토리와 변수를 읽습니다.

pwd
cat inventory.yml
cat group_vars/all.yml
cat host_vars/camera-north.yml
cat host_vars/camera-south.yml

인벤토리의 cameras 그룹에는 실제 SSH 연결 설정을 가진 camera-north와 camera-south가 있습니다. 프로젝트의 ansible.cfg가 이 inventory.yml과 준비된 호스트 신뢰 설정을 선택합니다. 이미지에 원래 있던 inventory.ini는 이 코스의 인벤토리가 아닙니다. 설정, 인벤토리, 변수 파일을 그대로 보존하세요.

이 파일의 JSON 스타일 중괄호는 올바른 YAML입니다. group_vars/all.yml은 대상들이 공유하는 변수를 제공합니다. input_root는 /srv/photo-sheets/batches, output_root는 /srv/photo-sheets/rendered이며, collection_root는 컨트롤러에서 Playbook 옆에 있는 collected 디렉터리입니다. host_vars의 파일은 같은 이름의 호스트에 적용됩니다. 여기서 photo_batch는 북쪽에서 morning-walk, 남쪽에서 afternoon-walk를 선택합니다. 오늘의 두 묶음은 내용과 크기가 다릅니다.

먼저 북쪽 워크스테이션에서 준비된 두 장짜리 sample 입력을 사용합니다. Ansible의 command 모듈은 해당 SSH 대상에서 준비된 프로그램을 실행합니다. argv 목록은 항목마다 인수 하나를 전달하며, 셸이 명령 문자열을 해석하게 하지 않습니다. 도구의 --input은 원본 디렉터리, --output은 결과 디렉터리, --station은 결과에 표시할 워크스테이션을 지정합니다. 원본 사진은 편집하지 않습니다.

{{ variable }}은 Ansible 값을 삽입합니다. inventory_hostname은 현재 호스트의 인벤토리 이름이며, 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 표시를 확인하세요.

연습용 콘택트 시트와 대응하는 PNG 및 CSV 다운로드

명령이 “No such file or directory”라고 보고하면 ls로 현재 디렉터리를 나열해 철자를 확인합니다. cd /home/labex/project로 작업 공간에 돌아가 해당 명령을 반복하세요. 모든 원본 사진, 과거 참고 파일, 편집 메모, 준비된 연결 설정을 보존합니다. 더 큰 현재 사진 묶음은 아직 생성하지 않았습니다.

각 작업을 기다린 후 수집하기

이 단계에서는 두 워크스테이션의 현재 사진 묶음을 처리하고 각 호스트 자신의 성공 결과를 기다린 뒤, 충돌 없이 대응하는 파일을 수집합니다.

Controller의 /home/labex/project에 머무릅니다. cameras 그룹이 두 대상을 선택하며, 각 대상의 photo_batch는 자신의 준비된 입력을 선택합니다. 이 변수들을 사용하여 두 워크스테이션 이름이나 입력 경로를 고정하지 않고도 다른 구성원, 묶음 이름, 디렉터리에 같은 흐름을 적용하세요.

동기 태스크는 Ansible이 다음 태스크로 넘어가기 전에 기다립니다. async: 120은 원격 작업에 120초의 실행 시간을 허용합니다. poll: 0은 해당 태스크에서 완료를 기다리지 않고 시작합니다. 시작에 성공했다고 처리까지 성공한 것은 아닙니다. 원격 작업은 이후 오류로 끝날 수 있습니다.

register: photo_job은 시작 결과를 인벤토리 호스트별로 저장합니다. ansible_job_id는 그 호스트의 해당 작업을 식별합니다. 한 워크스테이션의 JID로 다른 워크스테이션의 결과를 조회할 수는 없습니다. debug 모듈은 저장된 관찰값을 출력하여 서로 다른 할당을 보여 줍니다.

async_status는 같은 호스트에 그 JID의 상태를 묻고 각 응답을 photo_result에 저장합니다. until: photo_result is finished는 네이티브 완료 플래그가 참이 될 때까지 조회를 반복합니다. retries: 60은 재시도 횟수를 제한하고 delay: 1은 사이에 1초씩 기다립니다. 이는 상태 조회의 반복이지 새 사진 작업 생성이 아닙니다. 실제 SSH와 처리 시간도 추가됩니다. 작은 실제 묶음은 첫 조회가 도착할 때 이미 끝날 수 있으며, 이 실습에는 인위적인 지연이 없습니다.

완료만으로 성공을 입증할 수는 없습니다. 원격 프로그램의 rc는 종료 상태이며, 0이 성공입니다. async_status는 기본적으로 0이 아닌 결과를 실패로 전파합니다. 추가 assert는 종료 상태가 0이어야 함을 명시할 뿐, 오류를 성공으로 바꾸지 않습니다. 다음 디버그 출력으로 가져오기 전에 실제 finished와 rc를 읽습니다. 동등한 흐름은 이 추가 검증 없이 모듈의 기본 실패 전파에 의존해도 됩니다.

두 호스트는 각자의 파일 시스템에 같은 이름의 contact-sheet.png와 files.csv를 만듭니다. 성공적으로 끝난 뒤에만 가져오고, 컨트롤러의 인벤토리 호스트별 디렉터리에 각 쌍을 보관하세요. Photo Desk는 명시적인 평면 배치와, 워크스테이션 수집 폴더 안의 기본 호스트/원본 경로 계층을 모두 읽습니다. 누락된 결과를 참고 파일로 채우지 않습니다.

앞 단계와 같은 따옴표로 감싼 here-document 방식으로 process-photos.yml을 작성합니다.

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

각 워크스테이션에 자신의 작업 ID가 표시되고, 완료 메시지에 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는 호출을 해당 인벤토리 호스트로 제한합니다. --extra-vars photo_batch=missing은 이번 호출에서만 묶음 변수를 덮어쓰며 준비된 변수 파일을 편집하지 않습니다. 작업이 시작된 뒤 처리가 끝났지만 실패했다고 보고할 것입니다. 기존의 올바른 파일을 새로운 성공 결과로 취급해서는 안 됩니다.

이 명령은 실패할 것으로 예상합니다.

ansible-playbook process-photos.yml --limit camera-north --extra-vars photo_batch=missing

즉시 이전 명령의 상태를 출력합니다. $?는 마지막 명령의 종료 상태를 담고 echo가 출력합니다. 다른 명령을 먼저 실행하면 값이 바뀝니다.

echo $?

Ansible CLI 상태는 0이 아닌 값, 보통 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 쌍을 수집하고, 종료된 실패와 성공을 구분했으며, 영향을 받은 호스트만 다시 실행해 입력 누락을 수정했습니다.