소개
Paper & Pine는 대기 중인 행사 프로그램을 실제 PDF로 만듭니다. 작업을 일시 중단하기 전에 현재의 3페이지 유인물을 마치고, 다음 작업은 대기 상태로 남기며, 완성된 아침 PDF를 보존해야 합니다. 제공된 Python worker는 중지 요청 후 현재 작업을 마치는 방법을 이미 알고 있지만, 현재 진입 셸이 요청의 전달을 막고 있습니다.
터미널 경로, 기본 Docker, 컨테이너 로그를 알고 있어야 합니다. 여기서 사용하는 작은 Compose 파일과 셸 동작은 사용 전에 설명합니다. Python 프로그래밍과 실제 프린터는 필요하지 않습니다. 결과물은 다운로드하고 디코딩할 수 있는 PDF입니다.
Workstation과 Print Desk를 사용하세요. 독립적인 연습 및 전달 큐를 통해 전달 진행 상황을 잃지 않고 통제된 실패 하나를 살펴볼 수 있습니다. 교육 모드는 첫 페이지를 렌더링한 뒤 중지 요청이 올 때까지 멈추고, 나머지 페이지 사이에 공개된 0.5초 지연을 둡니다. 이 지연은 판단을 눈에 보이게 하며 실제 운영 속도 벤치마크가 아닙니다. 읽기와 일반적인 실수를 포함해 약 45분을 예상하세요. 이는 저자의 예상 시간입니다.
worker에 중지 요청 전달하기
이 단계에서는 실패한 연습 중지를 관찰하고 진입 셸을 수정한 뒤, 중지 요청을 받은 바로 그 worker에서 실제 전달 작업을 완료합니다.
실제 완성된 프로그램으로 시작하기
Print Desk에서 시작하세요. Delivery attempt 아래 Morning welcome handout의 Preview text를 열고 Download PDF를 선택합니다. 미리보기는 소스 텍스트를 재구성하지 않고 저장된 2페이지 PDF를 디코딩합니다. Back to Print Desk로 돌아오세요. 현재 Community open day handout은 대기 중이며, 이번 인계 후에도 Volunteer notes는 대기 상태여야 합니다.
데스크는 연습과 전달을 구분하며 처음에는 앞선 유인물만 사용할 수 있습니다.

Practice attempt 열은 두 번째 머신이 아닌 교육 큐의 별도 사본입니다. 각 열에는 자체 출력과 worker 상태가 있습니다. 현재 PDF는 문서 전체가 최종 완성된 뒤에만 다운로드할 수 있습니다. 내부에서 1페이지를 렌더링했다고 아직 완전한 PDF 파일이 만들어지는 것은 아닙니다.
연습 중지가 실패하는 이유 관찰하기
Workstation으로 전환하세요. 시작 위치는 /home/labex/project/print-signal입니다. cd는 그곳으로 돌아가고 cat은 파일을 읽습니다.
cd /home/labex/project/print-signal
cat compose.trial.yaml
Compose는 services.worker 아래 설정을 묶습니다. 준비된 이미지에는 worker와 PDF 라이브러리가 있습니다. user는 이미 실제 숫자 계정 ID와 일치하여 출력을 편집할 수 있습니다. 그 값을 대체하지 마세요. 읽기 전용 /input에는 원본 작업, /work에는 이 시도의 쓰기 가능한 상태/출력, /entry.sh에는 읽기 전용 진입 스크립트가 있습니다. :ro는 읽기 전용을 뜻합니다. entrypoint: ["/bin/sh", "/entry.sh"]는 해당 셸을 PID 1이라는 컨테이너 프로세스 1로 직접 시작합니다. 컨테이너는 VM 커널을 공유하지만 자체 프로세스 보기를 가집니다.
stop_grace_period: 10s는 애플리케이션에 유한한 10초 종료 시간을 줍니다. Docker는 먼저 일반적인 종료 요청인 SIGTERM을 보냅니다. 유예 시간이 지나면 프로세스를 강제로 종료하는 SIGKILL을 보낼 수 있습니다. 이 신호는 작업을 마치도록 처리할 수 없습니다. PID 1에 신호를 보낸다고 모든 자식에게 자동으로 전달되지는 않습니다.
기존 연습 진입 스크립트는 &로 Python을 백그라운드 자식으로 시작하고 wait로 기다립니다. 셸은 PID 1로 남습니다. 제공된 스크립트를 읽으세요.
cat trial-entry.sh
셸에는 전달 핸들러가 없습니다. 컨테이너의 PID 1은 특별한 기본 신호 동작을 가지므로 셸은 살아 있는 동안 Python이 TERM 요청을 전혀 받지 못할 수 있습니다.
연습 프로젝트만 시작하세요. -f는 파일 선택, -p는 리소스 그룹 지정, up -d는 백그라운드 시작이며 마지막 옵션들은 pull이나 build 없이 준비된 이미지를 사용합니다.
docker compose -f compose.trial.yaml -p print-trial up -d --pull never --no-build
Print Desk로 전환하여 새로고침하고 Practice attempt가 Training pause after page 1; waiting for stop request를 표시할 때까지 기다리세요. worker는 실제로 1페이지를 렌더링하고 의도적으로 기다리고 있습니다. 짧은 타이밍을 맞출 필요는 없습니다.
Workstation으로 전환하세요. docker exec는 기존 컨테이너에서 명령을 실행합니다. Linux는 /proc 아래에 프로세스 정보를 노출합니다. 이 컨테이너의 /proc/1/comm은 PID 1 명령의 이름을 나타냅니다. 다음 읽기 전용 관찰은 sh를 보여 주어야 합니다.
docker exec print-trial cat /proc/1/comm
stop은 설정된 유예 시간으로 이 서비스에 중지를 요청합니다.
docker compose -f compose.trial.yaml -p print-trial stop worker
worker가 강제 종료되어도 stop 명령 자체는 0으로 종료할 수 있습니다. Docker가 중지 작업을 처리했다는 뜻이지 프로그램 문서가 완성되었다는 뜻이 아닙니다. docker inspect는 보존된 컨테이너를 읽습니다. 작은따옴표 템플릿은 worker 종료 코드를 선택합니다.
docker inspect print-trial --format '{{.State.ExitCode}}'
docker logs print-trial
종료 코드 137과 worker의 stop-received 또는 completed 이벤트가 없음을 예상하세요. 이 통제된 연습에서는 유예 시간이 지나 Docker가 강제로 중지했습니다. 137만으로 모든 컨테이너 실패를 일반적으로 진단할 수는 없습니다. 로그는 줄당 JSON 이벤트 하나입니다. event는 관찰 이름, job은 프로그램 문서 식별값, page는 실제 렌더링 진행 상황입니다.
Print Desk로 전환하여 새로고침하세요. 연습에는 최종 완성된 현재 PDF가 없지만 앞선 유인물과 손대지 않은 전달 큐는 계속 이용할 수 있습니다.
셸을 worker로 교체하기
Workstation으로 전환하세요. 전달용 entry.sh만 수정하고 trial-entry.sh, 프로그램 입력, 기존 PDF, 다른 설정은 온전히 유지합니다. 셸 exec는 현재 셸 프로세스를 지정한 프로그램으로 교체합니다. 따라서 Python이 PID 1이 되어 제공된 TERM 핸들러가 Docker 요청을 받습니다.
이 리터럴 히어 도큐먼트는 짧은 스크립트 전체를 씁니다. >는 entry.sh를 교체하며 따옴표로 감싼 SH 표시는 닫는 줄까지 셸 확장을 막습니다. 설정된 /bin/sh가 파일을 읽으므로 실행 비트는 필요하지 않습니다.
cat > entry.sh <<'SH'
#!/bin/sh
exec python3 -B /app/worker.py
SH
cat entry.sh
독립적인 전달 프로젝트를 시작하세요. 이미 설정된 10초 유예는 이 3페이지 유인물에 충분합니다.
docker compose -p print-delivery up -d --pull never --no-build
Print Desk로 전환하여 새로고침하고 Delivery attempt의 교육 일시 정지 메시지를 기다립니다. Workstation으로 돌아가 PID 1을 다시 읽으세요.
docker exec print-delivery cat /proc/1/comm
기다리는 셸 대신 python3가 예상됩니다. 정상 중지를 요청하세요.
docker compose -p print-delivery stop worker
docker inspect print-delivery --format '{{.State.ExitCode}}'
docker logs print-delivery
종료 코드 0이 예상됩니다. 같은 worker가 stop-received, 남은 page-completed 이벤트, 현재 프로그램 문서의 completed, worker-stopped를 기록합니다. 나중에 새 프로세스가 누락된 PDF를 채운다면 이 worker가 자기 작업을 마쳤다는 증거가 되지 않습니다.
전달 파일과 보존된 큐 확인하기
Print Desk로 전환하여 새로고침하고 Delivery attempt 아래 현재 유인물의 Preview text를 여세요. 디코딩된 세 페이지 전체, 각 절 제목, 프로그램 ID, 페이지 번호가 있어야 합니다. 실제 PDF를 다운로드하여 여세요. 데스크로 돌아가면 Morning welcome handout은 원래 두 페이지를 유지하고 Volunteer notes는 생성된 PDF 없이 대기 상태로 남아 있어야 합니다. Docker에 기록된 상태를 직접 확인할 수 있도록 정상 중지된 print-delivery 컨테이너를 보존하세요.
이름을 잘못 입력했다면 오류를 읽고 명시된 컨테이너 이름을 사용하세요. 예를 들어 docker logs print-workr는 이 전달물을 찾지 못하지만 표시된 docker logs print-delivery는 읽을 수 있습니다. PDF를 직접 만들어 대체하지 마세요. 검사는 실제 중지 사실과 모든 페이지를 읽고 디코딩한 다음, 실제 진행을 변경하지 않고 격리된 프로그램 fixture에서 저장된 진입 스크립트를 재실행합니다.
실패한 전달 복구하기
전달 시도가 실패하면 로그와 종료 상태를 읽을 때까지 중지된 컨테이너를 보존하세요. 진입 또는 유예 설정만 수정합니다. 제공된 복구 작업은 실행 중이거나 성공적으로 완료된 컨테이너를 거부합니다. 실패하여 중지된 과정 worker에는 현재 작업만 다시 대기열에 넣고 실패한 산출물을 delivery/attempt-history/에 보관합니다. 앞선 PDF, 원래 입력, 나중 대기 작업을 보존합니다.
python3 /opt/labex/dk05/source/retry.py
명시적으로 복구한 다음 docker compose -p print-delivery down으로 실패한 전달 프로젝트의 컨테이너만 제거하고 표시된 전달 시작, 일시 정지 대기, 중지 순서를 반복하세요. 원본 PDF는 재생성되지 않으며 탭이나 점수 검사에서 이 복구를 실행하는 일은 없습니다. 실패한 시도를 숨기려고 VM을 초기화하거나 큐 JSON을 변경하지 마세요.
요약
대기하는 PID 1 셸이 자식에게 중지 요청을 전달하지 않을 수 있음을 확인하고, exec를 사용해 제공된 worker가 TERM을 직접 받게 했습니다. 원래 전달 worker는 3페이지 PDF를 완성하고 정상 종료했으며 아침 문서를 보존하고 다음 작업을 대기 상태로 남겼습니다.



