얼쑤 기술 블로그의 편집 API를 점검하던 중 Gunicorn 기동 로그에서 같은 오류가 반복되는 것을 발견했습니다. 편집 설정과 조회수 API는 계속 HTTP 200을 돌려줬는데, 컨테이너를 시작할 때마다 /.gunicorn을 만들 수 없다는 메시지가 한 번씩 남았습니다. Gunicorn이 시작하면서 어떤 작업에 실패했는지부터 살펴봤습니다.

요청은 처리됐는데 어떤 소켓이 실패했을까

Gunicorn은 HTTP 요청을 받는 리스너 외에 프로세스 관리를 위한 Unix 소켓을 엽니다. gunicornc는 이 제어 소켓으로 워커 상태를 조회하거나 재시작을 요청할 수 있습니다. 소켓 경로를 지정하지 않으면 Gunicorn은 XDG_RUNTIME_DIR을 우선 사용하고, 그 값이 없으면 홈 아래의 .gunicorn 디렉터리를 사용합니다. Gunicorn 제어 소켓 설정

편집 API 컨테이너에서 Gunicorn 26.2.0의 설정을 확인하니, 실행 사용자에게 홈으로 해석된 경로는 /였고 제어 소켓 경로는 /.gunicorn/gunicorn.ctl이었습니다. 루트 파일시스템은 읽기 전용이어서 Gunicorn이 소켓의 부모 디렉터리를 만들 때 Errno 30이 발생했습니다. HTTP 리스너는 계속 요청을 처리하고 있었기 때문에 API 응답만 확인했을 때는 이 오류가 드러나지 않았습니다.

읽기 전용 설정을 유지하고 옵션 하나를 바꿨습니다

소켓을 쓰고 있었다면 쓰기 가능한 런타임 디렉터리에 소켓을 옮기는 방법이 있었습니다. 이 서비스의 프록시는 HTTP로 연결했고, 점검한 실행·운영 설정에서 제어 소켓을 사용하는 곳은 발견되지 않았습니다. 그래서 Gunicorn 실행 인자에 --no-control-socket을 추가했습니다. 이 옵션을 쓰면 해당 인스턴스에 gunicornc로 접속할 수 없으므로, 관리 명령이 이 소켓에 의존하는지는 적용 전에 확인해야 합니다. Gunicorn 비활성화 옵션

같은 이미지와 읽기 전용 루트 설정으로 임시 컨테이너 두 개를 띄웠습니다. 두 실행의 차이는 제어 소켓 옵션 하나였습니다.

실행 조건헬스 체크기동 시 제어 소켓 오류
기본 옵션통과1건
--no-control-socket 추가통과0건

운영에서는 기동 로그와 편집 흐름을 따로 확인했습니다

운영 배포 뒤 읽기 전용 루트 설정과 실행 인자를 다시 확인했습니다. 점검 시점까지 새 컨테이너의 기동 로그에서 같은 오류는 0건이었고 컨테이너도 재시작하지 않았습니다. 편집 설정과 조회수 API는 각각 HTTP 200으로 응답했습니다. 브라우저의 삭제 사전 요청에서도 DELETE가 허용되는 것을 확인했습니다.

마지막으로 작성자 계정으로 글 수정 화면을 열었습니다. 삭제 확인창에 대상 글 제목이 표시됐고, 취소하자 수정 화면으로 돌아왔습니다. 실제 삭제는 검증 범위에서 제외했습니다. 헬스 체크는 수정 전후 모두 통과했기 때문에, 이 사례에서는 기동 로그의 오류 건수와 사용자가 밟는 화면 흐름을 각각 확인해야 조치 결과를 판단할 수 있었습니다.