서버를 멈추게 한 누락된 스왑 파일
9월 24일, 프로덕션 서버 하나가 응답을 멈췄습니다. 로드 밸런서는 그 서버를 비정상으로 표시했고, 원격 명령 도구는 서버에 닿지 못했습니다. 하지만 아무것도 충돌하지 않았고, 메모리를 너무 많이 쓴다고 강제 종료된 프로세스도 없었습니다. 서버는 스래싱 상태였습니다. 스왑 공간이 없고 컨테이너가 머신의 메모리보다 더 많은 메모리를 쓸 수 있었기 때문에, 리눅스는 실행에 필요한 파일을 쫓아내고 다시 읽는 데 시간을 모두 썼고 거의 진전이 없었습니다. 모든 서버에 2GB 스왑 파일을 추가하자 문제가 해결되었습니다. 더 흥미로운 교훈은 헬스 체크에 관한 것이었습니다.
“멈춤”은 어떻게 보였나
- 로드 밸런서의 헬스 체크가 그 서버에서 시간 초과되었습니다.
- AWS Systems Manager를 통해 보낸 명령은 “Pending” 상태에 머물고 실행되지 않았습니다.
- 로그에 메모리 부족 종료는 없었습니다. 죽은 프로세스도 없었습니다.
일주일 전 우리는 프로덕션을 더 작은 ARM 서버로 옮겼습니다. 각 서버는 메모리가 2GB였습니다. 그보다 더 작은 우리 스테이징 서버도 배포 중 컨테이너 이미지를 풀다가 똑같이 멈춘 적이 있었습니다.
왜 메모리 오류가 없었나요?
리눅스는 회수할 수 있는 메모리가 정말로 바닥났을 때에만 프로세스를 죽입니다. 그 전까지는 페이지 캐시, 즉 RAM에 보관된 파일 복사본에서 메모리를 회수합니다. 여기에는 실행 중인 모든 프로그램의 코드도 포함됩니다.
우리 서버에서 컨테이너들의 메모리 한도를 합치면 약 3.3GB였는데, 사용 가능한 메모리는 약 1.8GB뿐인 머신이었습니다. 보통은 모두가 한도까지 쓰지는 않습니다. 하지만 모두가 한도까지 쓰면, 커널은 공간을 만들기 위해 캐시에서 프로그램 코드를 계속 쫓아냈고, 그 코드가 다시 실행되는 순간 디스크에서 다시 읽어야 했습니다. 이 순환 — 스래싱 — 은 머신을 거의 완전히 응답하지 않게 만들 수 있으면서도, 기술적으로는 메모리가 바닥나지 않게 합니다. 그래서 아무것도 죽지 않고, 아무것도 회복되지 않습니다.
스왑이 없으면 커널은 할당되었지만 거의 쓰지 않는 메모리를 둘 곳이 없으므로, 대신 캐시에서 빼앗아야 합니다.
수정
서버 부트스트랩 스크립트는 이제 서버가 부팅되거나 복구될 때마다 2GB 스왑 파일을 낮은 swappiness(10) 설정으로 만듭니다. 낮은 swappiness는 커널이 여전히 작업 메모리를 RAM에 두는 것을 선호하지만, 거의 쓰지 않는 페이지는 디스크로 옮길 수 있게 해서 프로그램 코드가 캐시에서 쫓겨나지 않도록 합니다. 프로덕션 서버 두 대와 스테이징에 실시간으로 적용했고, 배포 파이프라인은 프로덕션 배포마다 부트스트랩 스크립트를 다시 게시하므로 교체된 서버도 이를 받습니다.
더 걱정스러웠던 부분: 응답 없는 서버가 그대로 남아 있었습니다
서버 그룹은 EC2 헬스 체크, 즉 “머신이 실행 중인가?”를 사용했고, 로드 밸런서 헬스 체크, 즉 “요청에 응답하는가?”는 사용하지 않았습니다. 그래서 로드 밸런서가 포기한 서버도 그룹의 입장에서는 정상이었습니다. 그 서버는 결코 교체되지 않았을 것입니다. 누군가 알아채고 다른 한 대를 재부팅할 때까지, 프로덕션은 조용히 한 서버에서만 돌아갔을 것입니다.
그룹을 로드 밸런서 헬스 체크로 바꾸는 것이 명백한 해결책처럼 보이지만, 우리에게는 간단하지 않습니다. 배포 중에 서버는 로드 밸런서가 트래픽을 빼도록 헬스 엔드포인트에서 일부러 오류를 반환합니다(배포를 502 0개로 만든 방법 참고). 그 신호에 따라 서버를 교체하는 그룹은 모든 배포 도중에 서버를 교체해 버릴 것입니다.
그래서 지금은 배포 체크리스트에 단계를 하나 추가했습니다. 배포할 때마다 배포 작업이 통과했는지만 보지 말고, 로드 밸런서에서 각 대상의 헬스를 확인합니다. 서버로 가는 명령이 “Pending”에 머물러 있다면 그 서버는 스래싱 중입니다. 재부팅하세요. 이미 로테이션에서 빠져 있는 상태입니다.
우리가 얻은 교훈
- “충돌하지 않음”이 “정상”은 아닙니다. 스래싱은 오류를 만들지 않고, 머신이 응답을 멈출 만큼 극심한 느려짐만 만듭니다.
- 작은 서버에는 스왑이 필요합니다. 낮은 swappiness와 함께 조금이라도 있으면, 커널이 오래된 메모리를 둘 곳이 생깁니다.
- 머신보다 합계가 큰 메모리 한도는 도박입니다. 가끔은 집니다. 언제 도박을 했는지 알아 두세요.
- 오토스케일링이 신뢰하는 헬스 체크가 무엇인지 아세요. “VM이 켜져 있나?”만 묻는 헬스 체크는 작동을 멈춘 서버를 교체하지 않습니다.