Media 호스트의 헬스 요청은 200인데, 사이드카는 재시작할 때마다 실패했습니다. 비정상 종료 시험에서 컨테이너는 살아남고 그 컨테이너에 제공하던 설정 파일만 사라지는 경로가 드러났습니다. MediaMTX는 옛 비밀을 붙든 채 계속 실행되고 있었습니다.

PokeClip에서 저는 미디어·인프라의 기동·복구 범위를 정하고 검증 결과를 검토했습니다. 구현과 장애 주입 시험은 AI와 협업했습니다. PR #311의 핵심은 재시작 횟수를 늘리는 대신 프로세스·컨테이너·비밀 파일이 같은 기동 상태를 가리키도록 만드는 것이었습니다.

어떤 실패가 방송 전체를 다시 띄워야 하는가

한 호스트에서 MediaMTX는 수신·녹화·재생을, nginx는 HTTP 요청 검증과 전달을 맡습니다. 사이드카는 완성된 녹화 파일을 색인하고 업로드합니다. 이들을 모두 같은 정책으로 재시작하면 DB 문제로 사이드카가 죽었을 때 정상인 송출까지 끊을 수 있습니다.

저는 최초 기동은 systemd 유닛 하나가 맡고, 실행 중 복구 범위는 다음과 같이 나누는 방식을 선택했습니다.

서비스 실행 중 종료되었을 때
MediaMTX·nginx Compose 묶음을 내리고 systemd가 전체를 다시 기동
사이드카·인증 응답기·정리 작업 Docker의 on-failure로 해당 컨테이너만 재기동
운영자의 명시적 stop 자동으로 다시 띄우지 않음

Compose 템플릿에서 핵심 둘의 엔진 재시작은 끄고, 시작 스크립트가 두 서비스의 출력에 연결됩니다. 보조 서비스의 실패를 같은 방식으로 묶지 않는 구성이며, 단일 호스트의 모든 장애를 격리한다는 의미는 아닙니다. systemd 재시도 간격 20초 역시 복구 완료 시간의 보장은 아닙니다.

비밀을 매번 만들면서 수명 문제가 생겼습니다

시작 스크립트는 CDN 비밀을 가져오고 관리 API 비밀번호를 기동마다 생성합니다. 관리 비밀번호는 영구 저장하지 않고 /run의 tmpfs에 제한된 파일 권한으로 둡니다. tmpfs는 메모리를 기반으로 하며 재부팅 뒤 내용이 남지 않는 파일 시스템입니다. 컨테이너에는 이 파일을 읽기 전용 bind mount로 전달합니다.

이 선택은 장기 자격증명을 줄이는 대신, 파일을 지우는 시점과 그 파일을 읽는 컨테이너를 내리는 시점을 맞춰야 한다는 책임을 만들었습니다. 호스트 경로를 삭제했다고 실행 중 컨테이너가 이미 참조하는 파일까지 즉시 새 내용으로 바뀌는 것은 아닙니다.

AL2023·systemd 252 환경에서 유닛의 프로세스 그룹 전체나 Compose 플러그인에 SIGKILL을 보내는 시험을 했습니다. 당시 비정상 종료 경로에서는 기대한 컨테이너 정리가 이루어지지 않았고, 종료 후 정리에서 렌더 파일만 삭제됐습니다. 그 뒤 사이드카가 다시 시작하면서 없어진 bind 원본 자리에 디렉터리가 생겼습니다. 다음 기동은 같은 자리에 파일을 써야 하므로 실패가 이어졌습니다.

남은 상태 다음 동작에 미친 영향
옛 MediaMTX 컨테이너 옛 CDN 비밀을 계속 사용
삭제된 렌더 파일 경로 다음 렌더가 필요한 상태
파일 자리에 생긴 디렉터리 새 설정 파일 생성 방해
nginx 자체 헬스 200 MediaMTX·사이드카 전체 정상 여부를 설명하지 못함

여기서 ExecStop의 일반 규칙과 관측을 구분해야 합니다. systemd 252 공식 문서는 성공적으로 시작한 서비스가 종료되거나 죽어도 stop 작업을 수행한다고 설명합니다. 시작 실패에서는 ExecStop을 건너뛰고 ExecStopPost를 사용합니다. 따라서 이번 시험 기록을 “주 프로세스가 실패하면 항상 ExecStop이 생략된다”는 규칙으로 일반화하지 않았습니다. 확실한 문제는 비정상 종료 경로에서 컨테이너 정리가 누락돼 파일 수명과 어긋났다는 관측입니다.

정리·마운트·다음 기동을 함께 고쳤습니다

정상 stop 경로만 보완해서는 부족했습니다. 종료 후 정리와 다음 시작도 같은 조건을 회복해야 했습니다.

변경 막으려는 실패
ExecStopPost에서도 Compose 정리 정리되지 않은 컨테이너를 둔 채 비밀 파일만 삭제
create_host_path: false 없는 파일 원본 자리에 디렉터리 자동 생성
--force-recreate 옛 설정 파일을 참조하는 컨테이너 재사용
--remove-orphans 새 설정에서 빠진 서비스의 옛 컨테이너 잔존

Docker 공식 문서도 bind 원본이 없을 때 디렉터리가 만들어질 수 있으며, 긴 문법의 create_host_path: false로 이를 막을 수 있다고 설명합니다. 이 옵션만으로 복구가 완성되지는 않습니다. 원본 파일이 없으면 해당 컨테이너 시작이 실패하도록 하고, 전체 재기동이 새 파일과 새 컨테이너를 준비하게 하는 조합입니다.

정지 스크립트는 Compose 정리가 실패하면 프로젝트 라벨로 남은 컨테이너를 찾아 정리하는 경로도 둡니다. 녹화·훅 스풀·DVR 볼륨은 정지 때 삭제하지 않습니다. 실행 상태를 초기화하는 것과 복구에 필요한 데이터를 지우는 것은 다른 작업입니다.

다시 켜졌다는 것보다 무엇이 남았는지를 봤습니다

수정 후에는 같은 강제 종료 갈래와 운영자 복구를 반복했습니다. 10월 22일 일괄 검증에서는 실제 systemd 환경의 재시작 시험 19개 사례가 3회 반복 통과했습니다. 후속 보완에서는 사례가 21개로 늘었으며, 서로 다른 시점의 시험 수를 더해 하나의 성과처럼 세지 않았습니다. 최종 템플릿으로 송출·재생·인증 거부도 다시 확인했습니다.

헬스 200은 nginx 자체 응답입니다. 내부 서비스가 모두 복구됐다는 뜻으로 쓰지 않고, 컨테이너·설정·실제 기능을 별도로 확인했습니다. 비밀 조회에 의존하는 시작 과정과 단일 호스트라는 제약도 남습니다. 이 시험은 다중 호스트 고가용성이나 무중단 복구를 입증하지 않습니다.

PR #311은 2026년 10월 23일 한국 시각에 병합됐고, 10월 27일 저장 기록에서는 운영 Media 배포가 후속 단계입니다. 이번에 고친 것은 운영 장애 통계가 아니라, 배포 전에 재현한 수명 불일치입니다. 기동 정책을 평가할 때는 어떤 프로세스가 다시 떴는지에 더해 이전 실행의 컨테이너와 파일이 어떤 상태로 남는지까지 확인해야 했습니다.

nginx의 재생 요청 검증 · 전체 기술 기록