컨테이너가 계속 재시작되면서 로그가 초당 수백 줄씩 쌓이고 있었다. 확인하니 Docker 의 --restart always 와 systemd 유닛의 Restart=always 양쪽에 재시작 설정이 켜져 있었다.
Table of contents
Open Table of contents
원인 — 살리는 설정보다 먼저 볼 것
살리는 설정이 겹친 것도 문제이지만 더 앞의 문제는 왜 죽느냐였다. 로그를 보니 Class not found 로 시작하자마자 죽고 있었고 배포에서 파일 하나가 빠진 것이었다.
살리는 설정 때문에 계속 다시 뜨고 계속 죽는 상태였다. 살리는 쪽을 먼저 손대면 증상만 멈추고 원인은 그대로 남는다.
조치 — 상한과 알림
재시작에 on-failure:5 로 상한을 둬서 몇 번 실패하면 그만두게 했다. 계속 도는 것보다 멈춰 있는 편이 낫기 때문이다.
멈춰 있으면 알림이 가게 했는데 여기서 하나를 놓칠 뻔했다. 계속 재시작하는 동안에는 상태가 exited 가 아니라 restarting 이라 --filter status=exited 에 안 잡히므로 재시작 상태도 함께 봐야 했다.
검증 — 겹친 관리를 하나로
살리는 계층을 하나로 정했다. Docker 공식 문서도 재시작 정책을 호스트의 프로세스 관리자와 함께 쓰지 말라고 경고한다. 두 가지 방법이 있었는데 systemd 가 관리하고 Docker 쪽을 no 로 끄는 것과 Docker 에 맡기고 서비스 유닛을 지우는 것이다. 뒤쪽을 골랐다. 이유는 다른 컨테이너들이 이미 Docker 정책으로만 관리되고 있어서다. 대신 호스트 부팅 순서를 systemd 로 제어하지 못하는 것을 감수했다.
둘이 겹치면 서로 싸우는데 한쪽이 멈추라고 하는 동안 다른 쪽이 살린다.
주의 — 로그와 디스크
재시작 간격이 점점 늘어나는 방식이라도 처음 몇 번은 빠르므로 그 사이에 로그가 수천 줄 쌓인다. 그래서 --log-opt max-size=10m 과 max-file=3 으로 상한을 뒀다.
상한을 안 두면 디스크가 차는데 실제로 재시작 반복으로 디스크가 찬 적이 있었다. 시작하다 죽는 것과 돌다가 죽는 것도 갈라서 HEALTHCHECK 에 --start-period 를 두고 그 안에 건강해지지 않으면 문제로 보게 했다.
배경 — 종료 코드와 횟수
무엇 때문에 죽었는지는 종료 코드로 갈렸다. 0 과 1 과 메모리 부족에 의한 137 과 SIGTERM 의 143 이 각각 다른 코드로 나온다.
메모리 부족은 애플리케이션 로그를 봐도 안 나오므로 docker inspect 의 OOMKilled 로 확인해야 한다. RestartCount 도 세어 지표로 보냈는데 계속 살아나는 동안에는 겉으로 정상으로 보이므로 그 숫자를 봐야만 알 수 있다.
정리
- 죽으면 살리는 설정이 두 곳에 있으면 서로 싸운다
- 살리는 설정이 아니라 죽는 것이 문제이므로 원인을 먼저 본다
- 재시작에 상한을 두고 멈춰 있게 한다
- 계속 재시작하면 종료 상태로 안 잡히므로 재시작 상태도 본다
- 로그에 크기 상한을 두지 않으면 디스크가 찬다
- 시작하다 죽는 것과 돌다가 죽는 것을 나눈다
- 종료 코드를 보면 메모리 부족을 가릴 수 있다
- 재시작 횟수를 세지 않으면 계속 살아나는 동안 정상으로 보인다