서비스는 모든 종류의 안정성 및 복원력을 포함하여 설계될 수 있지만, 실제로 안정성을 구현하기 위해 장애가 발생할 경우 예측 가능한 장애도 처리해야 합니다. 하드웨어는 결국 장애가 발생할 수밖에 없기 때문에 Amazon에서는 수평으로 확장 가능하고 중복되는 서비스를 구축합니다. 모든 하드 드라이브에는 예상 최대 수명이 있고, 소프트웨어의 일부분도 언젠가는 크래시가 발생할 수 있습니다. 서버 상태는 2진처럼 작동 상태이거나 전혀 작동하지 않는 상태 중 하나로 보일 수 있습니다. 그러나 안타깝게도 이렇게 단순하지는 않습니다. 그저 종료되는 것이 아니라, 장애가 발생한 서버로 인해 예상치 못한 상황이 발생하고 때로는 시스템에 큰 해를 입힐 수도 있다는 점을 확인했습니다. 상태 확인은 이러한 종류의 문제를 자동으로 감지하고 이에 대응합니다.

이 글에서는 상태 확인을 사용하여 단일 서버 장애를 감지하고 처리하는 방법, 상태 확인을 사용하지 않을 경우의 상황, 그리고 상태 확인 장애에 과잉 대응하는 시스템이 작은 문제를 전체 가동 중단으로까지 초래하는 과정을 설명합니다. 다양한 상태 확인 간에 장단점 조율을 처리하는 경험을 통해 Amazon에서 얻은 인사이트도 제공합니다.

작은 장애지만 커진 영향

Amazon에서 신규 소프트웨어 개발자로 일할 때 Amazon.com의 웹 사이트 렌더링 플릿에 대한 작업을 맡았습니다. 변경 사항을 작업하면서 몇 가지 계측을 추가하고 소프트웨어 실행 정도에 대한 가시적 정보를 제공하면서 의도와는 다르게 버그를 작성한 적도 있었습니다. 버그가 트리거되는 일은 드물었지만, 버그가 발생하면 이로 인해 해당 웹 서버가 모든 요청에서 빈 오류 페이지를 렌더링되었습니다. 웹 서버 프로세스를 다시 시작해야만 문제가 해결되었습니다. 그리고 저희는 버그를 감지하고 변경 사항을 빠르게 롤백하며 수많은 테스트를 추가하고 향후 이와 같은 조건을 포착하기 위해 프로세스를 개선시켰습니다. 하지만 버그가 프로덕션에 존재하면 대형 플릿의 소수의 서버만 중단된 상태로 종료되었습니다.
 
특히 버그를 찾는 데 방해가 되는 한 가지 요소는 서버에서 비정상 상태임을 깨닫지 못하는 상황입니다. 또한 서버는 상태를 모니터링 시스템으로 보고하는 기능을 유실하여, 자동으로 사용 중단 상태가 되지 못하고 일반 경보를 트리거하지 못합니다. 설상가상으로, 서버는 더 빨라지고 해당 피어 "정상 서버"가 올바른 웹 페이지를 렌더링할 때보다 더 빠르게 빈 오류 페이지를 생성하기 시작합니다. 당시에 저희가 사용하던 로드 밸런싱 기술은 느린 서버보다 빠른 서버를 선호했기 때문에, 비정상 상태의 서버로 필요 이상의 트래픽을 라우팅하면서 그 영향은 더욱 악화되었습니다.

모니터링에 시스템의 여러 지점에서 오류 비율 및 지연 시간을 측정하는 작업이 포함되기 때문에 다른 경보도 트리거되었습니다. 이러한 종류의 모니터링 시스템과 운영 프로세스는 문제를 억제하기 위한 안전장치 역할을 하지만, 올바른 상태 확인을 수행하면 장애를 빠르게 감지하고 대처하여 모든 종류의 오류 영향을 상당 부분 최소화할 수 있습니다.

상태 확인의 장단점

상태 확인은 특정 서버의 서비스에 작업을 성공적으로 수행할 수 있는지 여부를 물어보는 방식입니다. 로드 밸런서는 각 서버에 이 질문을 정기적으로 물어보며 트래픽을 라우팅하는 데 안전한 서버를 확인합니다. 대기열에서 메시지를 폴링하는 서비스는 대기열에서 더 많은 작업을 폴링하도록 결정하기 전에 정상 상태인지를 물어볼 수 있습니다. 외부 모니터링 플릿이나 각 서버에서 실행되는 모니터링 에이전트는 경보를 알리거나 장애가 발생한 서버를 자동으로 처리할 수 있도록 정상 상태인지 여부를 서버에 물어볼 수 있습니다.

웹 사이트 버그 예제에서 확인했듯이 비정상 상태의 서버가 서비스 중이면 전반적으로 서비스 가용성이 불균형적으로 떨어질 수 있습니다. 10개의 서버 플릿에서 서버 하나만 잘못되어도 플릿의 가용성은 90% 미만이 될 수 있음을 의미합니다. 설상가상으로 "최소 요청"과 같은 일부 로드 밸런싱 알고리즘 때문에 가장 빠른 서버에 더 많은 작업이 제공됩니다. 하나의 서버에서 장애가 발생하면 종종 빠르게 요청이 실패하기 시작하고, 정상 상태의 서버보다 더 많은 요청을 유도하여 서비스 플릿에 "블랙홀"이 생성될 수 있습니다. 일부 사례에서는 성공한 요청의 평균 지연 시간에 맞춰 실패한 요청 속도를 늦춤으로써 블랙홀을 방지하는 추가 보호 수단을 추가하기도 했습니다. 하지만 대기열 폴러와 같은 다른 시나리오도 존재하며, 이 경우 문제는 해결하기 더 어렵습니다. 예를 들어, 대기열 폴러가 수신 속도만큼 빠르게 메시지를 폴링하는 경우 장애가 발생한 서버도 블랙홀이 됩니다. 작업을 분산시키는 이러한 다양한 환경에서 부분적으로 장애가 발생하는 서버를 보호하는 방법은 시스템마다 다릅니다.

쓰기 작업이 불가능하고 요청이 즉시 실패하게 되는 디스크, 갑자기 왜곡되어 종속성에 대한 호출이 인증에 실패하게 하는 클럭, 업데이트된 암호 자료를 검색하는 데 실패하여 복호화 및 암호화를 실패하게 하는 서버, 자체 버그 및 메모리 누수 처리를 동결시키는 교착 상태로 인해 크래시가 발생하는 중요한 지원 프로세스를 포함하여 여러 가지 이유로 서버가 개별적으로 실패하는 상황을 보았습니다.

또한 서버는 플릿의 많은 서버 또는 모든 서버를 함께 실패하게 만드는 상관적인 이유로도 실패합니다. 이러한 상관적인 이유에는 공유되는 종속성의 가동 중단과 대규모 네트워크 문제가 포함됩니다. 바람직한 상태 확인은 중요하지 않은 지원 프로세스가 실행 중인지 검증하며 서버 및 애플리케이션 상태의 모든 측면을 테스트합니다. 하지만 문제는 중요하지 않은 이유로 상태 확인에 실패한 경우와 여러 서버에 걸쳐 실패가 상관되는 경우에 발생합니다. 여전히 유용한 작업을 수행할 수는 있어도 자동화 작업이 서비스에서 서버를 제거하면 자동화는 부작용이 더 큽니다.

상태 확인의 어려운 점은 철저한 상태 확인의 이점과 단일 서버 장애의 빠른 완화, 그리고 전체 플릿에서 거짓 긍정 실패로 인한 부작용 사이의 갈등에 기인합니다. 따라서 바람직한 상태 확인을 구축할 때 한 가지 과제는 거짓 긍정으로부터 신중하게 보호해야 한다는 점입니다. 일반적으로, 상태 확인과 관련된 자동화는 잘못된 한 서버로의 트래픽 전송을 중지해야 하지만, 전체 플릿에 문제가 있는 것 같으면 트래픽을 계속 허용함을 의미합니다.

상태를 측정하는 방법

서버에는 중단될 수 있는 많은 요소가 있고, 시스템의 여러 위치에서 서버 상태를 측정할 수 있습니다. 일부 상태 확인은 특정 서버가 개별적으로 중단되었음을 명확하게 보고하는 동시에, 다른 서버는 더 모호하고 상관되는 실패인 경우 거짓 긍정을 보고할 수 있습니다. 어떤 상태 확인은 구현하기 어렵습니다. Amazon Elastic Compute(Amazon EC2) 및 Elastic Load Balancing과 같은 서비스에서 설정할 때 구현되는 상태 확인도 있습니다. 각 유형의 상태 확인에는 고유한 강점이 있습니다.

실시간 검사

실시간 검사는 서비스에 대한 기본 연결성과 서버 프로세스의 존재를 테스트합니다. 종종 로드 밸런서나 외부 모니터링 에이전트가 실시간 검사를 수행하며, 애플리케이션의 작동 방식에 대한 세부 정보는 알지 못합니다. 실시간 검사는 서비스에 포함되기도 하고, 애플리케이션 작성자는 아무런 구현 작업을 수행하지 않아도 됩니다. 다음은 Amazon에서 사용하는 몇 가지 실시간 검사에 대한 예입니다.

• 서버가 예상 포트에서 대기 중이고 새 TCP 연결을 수락하는지 확인하는 테스트
• 기본 HTTP 요청을 수행하고 서버가 200 상태 코드로 응답하는지 확인하는 테스트
• 네트워크 접속성과 같은 시스템이 작동하는 데 필요한 기본적인 사항을 테스트하는 Amazon EC2의 상태 검사

로컬 상태 확인

로컬 상태 확인은 실시간 검사보다 더 나아가 애플리케이션이 작동할 수 있는지를 검증합니다. 이러한 상태 확인은 서버 피어와 공유하지 않는 리소스를 테스트합니다. 따라서 플릿의 많은 서버에서 동시에 실패할 가능성이 낮습니다. 이러한 상태 확인은 다음을 테스트합니다.

• 디스크에서 쓰기 또는 읽기 불가능 - 상태 비저장 서비스는 쓰기 가능한 디스크를 필요로 하지 않는다고 생각하는 경향이 있습니다. 하지만 Amazon에서 저희 서비스는 모니터링, 로깅 및 비동기식 측정 데이터 게시와 같은 작업을 위해 디스크를 사용합니다.
• 중요한 프로세스 크래시 또는 중단 - 일부 서비스는 서버 측 프록시(NGINX와 비슷함)를 사용하여 요청을 받고 다른 서버 프로세스에서 비즈니스 논리를 수행합니다. 실시간 검사는 프록시 프로세스가 실행 중인지만 테스트할 수 있습니다. 로컬 상태 확인 프로세스는 프록시에서 애플리케이션을 통과하며 둘 다 실행 중이고 요청에 올바르게 응답하는지 확인할 수 있습니다. 흥미로운 점은, 글의 초반에 언급한 웹 사이트 예시에서 기존 상태 확인은 렌더링 프로세스가 실행 중이며 응답하는지를 확인할 수 있을 만큼 자세하지만, 그렇다고 올바르게 응답하는지 확인할 수준은 아닙니다.
• 누락된 지원 프로세스 - 모니터링 데몬이 누락된 호스트의 경우 운영자는 "수치"에만 의존하고 서비스 상태를 알지 못할 수 있습니다. 다른 지원 프로세스는 측정 및 청구 사용량 레코드를 푸시하거나 자격 증명 업데이트를 수신합니다. 지원 프로세스가 중단된 서버는 감지하기 어려운 미묘한 방식으로 기능을 위험에 빠뜨립니다.

종속성 상태 확인

종속성 상태 확인은 인접한 시스템과 상호작용하는 애플리케이션의 기능을 조사하는 철저한 검사입니다. 이러한 검사는 이상적으로는 종속성과의 상호작용을 방해하는 만료된 자격 증명과 같이 서버에 로컬로 존재하는 문제를 포착합니다. 하지만 종속성 자체에 문제가 있어도 거짓 긍정을 보유할 수 있습니다. 이러한 거짓 긍정 때문에 종속성 상태 확인 실패에 대응하는 방법을 신중하게 고려해야 합니다. 종속성 상태 확인에서는 다음을 테스트할 수 있습니다.

• 잘못된 구성 또는 오래된 메타데이터 - 프로세스가 비동기식으로 구성 또는 메타데이터에 대한 업데이트를 검색하지만 서버에서 업데이트 메커니즘이 중단되면, 서버는 피어와 동기화되지 않으며 예측할 수 없고 테스트되지 않은 방식으로 잘못 작동할 수 있습니다. 그러나 서버가 한동안 업데이트를 확인하지 못하면 업데이트 메커니즘이 중단되었는지 여부와 중앙 업데이트 시스템이 모든 서버로 업데이트 게시를 중지했는지 여부를 알지 못합니다.
• 피어 서버 또는 종속성과 통신 불가능 - 이상한 네트워크 동작은 플릿에 있는 서버의 하위 집합에서 해당 서버로 트래픽을 전송하는 기능에는 영향을 주지 않고, 종속성과 통신하는 기능에 영향을 주는 것으로 알려져 있습니다. 연결 풀에서 버그 또는 교착 상태와 같은 소프트웨어 문제도 네트워크 통신을 방해할 수 있습니다.
• 프로세스 반송을 요구하는 기타 특이한 소프트웨어 버그 - 교착 상태, 메모리 누수 또는 상태 손상 버그는 서버 생성 오류로 이어질 수 있습니다. 

이상 탐지

이상 탐지는 플릿의 모든 서버를 관찰하고 피어와 비교했을 때 서버가 이상하게 동작하는지 확인합니다. 서버당 모니터링 데이터를 집계하여 지속적으로 오류 비율, 지연 시간 데이터 또는 기타 속성을 비교하여 이상 서버를 찾고 서비스에서 자동으로 제거할 수 있습니다. 이상 탐지는 다음과 같이 서버가 자체적으로 탐지할 수 없는 플릿의 일탈 조건을 찾아낼 수 있습니다.

• 클럭 스큐 - 특히 서버에 로드가 많은 경우 갑작스럽게 극적으로 클럭에 스큐가 발생하는 것으로 알려져 있습니다. AWS에 서명을 포함한 요청을 평가하는 데 사용되는 방법과 같은 보안 조치에서는 클라이언트 클럭의 시간이 실제 시간의 5분 이내여야 합니다. 그렇지 않으면 AWS 서비스에 대해 요청이 실패합니다.
• 오래된 코드 - 서버가 네트워크에서 연결에 끊어지거나 오랫동안 전원이 꺼진 상태였다가 다시 온라인으로 돌아온 경우 나머지 플릿과 호환되지 않는 오래된 코드를 실행하는 위험한 상황이 될 수 있습니다.
• 예상하지 못한 실패 모드 - 때때로 서버가 오류를 자체 오류가 아니라 클라이언트 오류로 식별하는 오류를 반환하므로 서버가 실패합니다(HTTP 500이 아니라 400). 서버는 실패하는 대신 속도가 느려지거나 피어보다 더 빠르게 응답할 수 있습니다. 이는 호출자에게 거짓 응답을 반환하는 상황을 나타냅니다. 이상 탐지는 예상하지 못한 실패 모드에 대해 여러 측면에서 뛰어난 성능을 보여줍니다.

이상 탐지를 실제로 사용하기 위해 몇 가지 지켜야 할 사항이 있습니다.

• 서버는 대략적은 같은 작업을 수행해야 합니다. 명시적으로 다른 종류의 서버에 다른 유형의 트래픽을 라우팅하는 경우 서버는 비슷하게 동작하지 않아서 이상값을 탐지하지 못할 수 있습니다. 하지만 로드 밸런서를 사용하여 서버로 트래픽을 라우팅하는 경우 비슷한 방식으로 응답할 수 있습니다.
• 플릿은 어느 정도 같은 종류여야 합니다. 서로 다른 인스턴스 유형을 포함하는 플릿에서 일부 인스턴스가 다른 인스턴스보다 느릴 수 있고, 이로 인해 잘못된 패시브 서버 탐지가 잘못 트리거될 수 있습니다. 이 시나리오의 문제를 해결하기 위해 인스턴스 유형별로 지표를 수집합니다.
• 동작에서의 차이나 오류를 보고해야 합니다. 오류를 보고하기 위해 서버 자체에 의존하기 때문에 모니터링 시스템도 중단되면 어떻게 될까요? 다행히도 서비스의 클라이언트는 계측 기능을 추가할 수 있는 훌륭한 위치입니다. Application Load Balancer와 같은 로드 밸런서는 모든 요청에서 접속하는 백엔드 서버, 응답 시간 및 요청의 성공 또는 요청 실패를 표시하는 액세스 로그를 게시합니다. 

상태 확인 실패에 대한 안전한 대응

서버가 비정상 상태라고 판단하면 두 종류의 작업을 수행할 수 있습니다. 가장 극단적인 상황에서는 어떤 작업도 제공하면 안 되고 로드 밸런서 상태 확인에 실패하거나 대기열 폴링을 중지하여 서비스를 중단하기로 로컬로 결정할 수 있습니다. 서버가 대응할 수 있는 또 다른 방법은, 중앙 기관에 문제가 발생했음을 알리고 중앙 시스템에서 문제를 처리할 방법을 결정하게 맡기는 것입니다. 중앙 시스템은 자동화로 전체 플릿을 중단시키지 않고 문제를 안전하게 해결할 수 있습니다.

상태 확인을 구현하고 이에 대응하는 여러 가지 방법이 있습니다. 이 섹션에서는 Amazon에서 사용하는 몇 가지 패턴에 대해 설명합니다.

페일 오픈

일부 로드 밸런서는 스마트 중앙 기관 역할을 할 수 있습니다. 개별 서버가 상태 확인이 실패하면 로드 밸런서는 트래픽 전송을 중지합니다. 하지만 모든 서버가 동시에 상태 확인에 실패하면 로드 밸런서는 페일 오픈 상태가 되고, 모든 서버로 트래픽을 허용합니다. 로드 밸런서를 사용하면 해당 데이터베이스를 쿼리하는 검사 및 중요하지 않은 지원 프로세스가 실행 중인지 확인하는 검사를 포함하여 종속성 상태 확인의 안전한 구현을 지원할 수 있습니다.

예를 들어, 정상 상태로 보고되는 서버가 없으면 AWS Network Load Balancer는 페일 오픈 상태가 됩니다. 또한 가용 영역의 모든 서버가 비정상 상태로 보고되면 비정상 가용 영역에도 트래픽 전송이 중단됩니다. (상태 확인을 위해 Network Load Balancer 사용에 대한 자세한 내용은 Elastic Load Balancing 설명서를 참조하십시오.) Application Load Balancer에서도 Amazon Route 53와 마찬가지로 페일 오픈을 지원합니다. (Route 53에서 상태 확인 구성에 대한 자세한 내용은 Route 53 설명서를 참조하십시오.)

페일 오픈 동작에 의존하는 경우 종속성 상태 확인의 실패 모드를 테스트해야 합니다. 예를 들어, 서버가 공유 데이터 저장소에 연결되는 서비스를 고려합니다. 데이터 저장소가 느리거나 낮은 오류 비율로 응답하는 경우 서버는 때때로 종속성 상태 확인에 실패할 수 있습니다. 이러한 조건에서는 서버 가동 상태가 바뀔 수 있지만, 페일 오픈 임계값을 트리거하지는 않습니다. 실패로 인해 심층적인 상태 확인이 더 악화되는 상황을 피하려면 이러한 상태 확인에서 종속성의 부분 실패를 추론하고 테스트하는 작업이 중요합니다.

페일 오픈이 유용한 동작이긴 하지만, Amazon에서는 모든 상황을 완전히 추론하거나 테스트할 수 없다는 점에서 회의적인 생각이 듭니다. 시스템이나 해당 시스템의 종속성에서 모든 유형의 오버로드, 부분 실패 또는 그레이 페일(gray failure)이 예상될 때 페일 오픈이 트리거되는 일반적인 증거까지는 아직 찾아내지 못했습니다. 이러한 제한 사항 때문에 Amazon 팀은 빠르게 작동하는 로드 밸런서 상태 확인을 로컬 상태 확인으로 제한하고 중앙 시스템에 의존하여 보다 심층적인 종속성 상태 확인에 신중하게 대응합니다. 그렇다고 해서, 페일 오픈 동작을 사용하지 않는다거나 특정 상황에서 페일 오픈이 효과적이지 않다는 것은 아닙니다. 하지만 많은 서버에서 논리를 빠르게 사용하려면 해당 논리에 매우 신중해야 합니다.

회로 차단기가 없는 상태 확인

서버가 서버 자체의 문제에 대응하는 것이 가장 빠르고 단순한 복구 방법처럼 보일 수 있습니다. 그러나 서버가 서버 상태를 잘못 알고 있거나 플릿의 전반적인 상태를 파악하지 못하면 가장 위험한 방법이기도 합니다. 플릿의 모든 서버가 동시에 동일한 잘못된 결정을 내리면 인접한 전체 서비스에서 계단식 실패가 발생할 수 있습니다. 이러한 위험에는 이를 보완해주는 점도 있습니다. 상태 확인과 모니터링 사이에 시간상 공백이 있으면 서버는 문제를 탐지할 때까지 서비스 가용성을 줄일 수 있습니다. 하지만 이 시나리오에서는 전체 플릿에서 예상치 못한 상태 확인 동작으로 인한 전체 서비스 가동 중단을 방지합니다.

다음은 기본 회로 차단기가 없을 때 상태 확인을 구현하기 위해 따르는 모범 사례입니다.

• 실시간 및 로컬 상태 확인을 수행하도록 작업 생산자(로드 밸런서, 대기열 폴링 스레드)를 구성합니다. 잘못된 디스크와 같이 해당 서버에 대해 명확히 로컬로 존재하는 문제가 발생한 경우에만 서버는 로드 밸런서에 의해 자동으로 가동 중단 상태가 됩니다.
• 종속성 상태 확인 및 이상 탐지를 수행하도록 기타 외부 모니터링 시스템을 구성합니다. 이러한 시스템은 자동으로 인스턴스를 종료하거나 경보를 보내거나 운영자의 개입을 유도하려고 시도할 수 있습니다.

종속성 상태 확인 실패에 자동으로 대응하기 위해 시스템을 구축하는 경우 자동화된 시스템이 예상치 못한 극단적인 작업을 수행하지 않도록 올바른 임계값을 구축해야 합니다. Amazon DynamoDB, Amazon S3 및 Amazon Relational Database Service(Amazon RDS)와 같은 상태 저장 서버를 운영하는 Amazon 팀에는 서버 대체와 관련된 중요한 내구성 요구 사항이 있습니다. 또한 이들 팀은 임계값을 초과하면 자동화를 중지하고 사람이 개입할 수 있도록 신중한 비율 제한과 제어 피드백 루프도 구축합니다. 이러한 자동화를 구축할 때 서버에서 종속성 상태 확인에 실패하면 확실히 알림이 표시되도록 해야 합니다. 일부 지표의 경우 서버에 의존하여 중앙 모니터링 시스템에 개별 상태를 자체 보고합니다. 서버가 중단되어 서버 상태를 보고할 수 없는 경우에 대비하기 위해 저희도 적극적으로 서버에 접속하여 서버 상태를 확인합니다. 

상태 우선순위 지정

특히 오버로드 상황에서는 서버에서 일상적인 작업보다 상태 확인을 우선적으로 처리하는 것이 중요합니다. 이러한 상황에서는 상태 확인에 대한 느린 대응이나 실패로 인해 잘못된 부분 정전 상황이 더 악화될 수 있습니다. 

서버에서 로드 밸런서 상태 확인에 실패하면 로드 밸런서에서 즉시 사용량이 낮은 시간에 해당 서버를 가동 중단할 것인지 묻습니다. 단일 서버에서 실패한 경우 문제가 되지 않지만, 서비스에 대한 트래픽이 급증할 때 최악의 상황은 서비스 크기를 줄이는 것입니다. 오버로드 중에 서버를 가동 중단하면 가용성이 급격히 떨어질 수 있습니다. 남은 서버로 트래픽을 강제로 넘겨도 해당 서버에서 오버로드가 발생할 수 있고, 역시 상태 확인에 실패하여 플릿이 더욱 축소됩니다.

문제는 오버로드 상태의 서버가 오류를 반환한다는 점이 아닙니다. 오히려 서버가 제시간에 로드 밸런서의 Ping 요청에 응답하지 않는 것이 문제입니다. 어쨌든 다른 원격 서비스 호출과 마찬가지로, 로드 밸런서 상태 확인에도 제한 시간이 구성됩니다. 부분 정전된 서버는 높은 CPU 경합, 가비지 수집기의 긴 주기 또는 단순한 작업자 스레드 부족 등 여러 가지 이유로 응답 속도가 느립니다. 너무 많은 추가 요청을 받는 대신, 제때 상태 확인에 대응하기 위해 리소스를 따로 확보하도록 서비스를 구성해야 합니다.

다행히도 이러한 종류의 가용성 축소를 방지하기 위해 따르는 간단한 몇 가지 모범 사례가 있습니다. iptables와 같은 도구와 일부 로드 밸런서는 "최대 연결"이라는 개념을 지원합니다. 이 경우 OS나 로드 밸런서는 서버에 대한 연결 수를 제한하여 서버 프로세스가 속도를 늦출 수 있는 동시 요청으로 과중되지 않도록 합니다.

최대 연결을 지원하는 프록시나 로드 밸런서가 서비스 전면에 배치되는 경우 HTTP 서버의 작업자 스레드 수가 프록시의 최대 연결 수와 일치해야 논리적으로 보입니다. 하지만 이 구성은 부분 정전 동안 서비스의 가용성을 떨어뜨릴 수 있습니다. 프록시 상태 확인에도 연결이 필요하므로, 추가 상태 확인 요청을 수용할 만큼 충분한 서버의 작업자 풀을 확보하는 것이 중요합니다. 유휴 상태의 작업자는 비용이 저렴하므로, 추가 작업자를 구성하려는 편입니다. 그러면 몇 개의 추가 작업자를 통해 어디서나 구성된 프록시 최대 연결을 2배로 늘릴 수 있습니다.

상태 확인 우선순위를 지정하는 데 사용하는 또 다른 전략은, 서버가 자체 최대 동시 요청 수를 강제적으로 구현하는 것입니다. 이 경우 로드 밸런서 상태 확인은 항상 허용되지만, 서버가 이미 일부 임계값에 도달한 상태에서 작동하는 경우 정상적인 요청은 거부됩니다. Amazon에서의 구현 방식은 Java의 단순한 세마포에서 CPU 사용률에 대한 보다 복잡한 추세 분석에 이르기까지 매우 다양합니다.

서비스가 상태 확인 Ping 요청에 제때 응답하도록 보장하는 또 다른 유용한 방법은, 백그라운드 스레드에서 종속성 상태 확인 논리를 수행하고 Ping 논리가 검사하는 isHealthy 플래그를 업데이트하는 것입니다. 이 경우 서버는 상태 확인에 즉시 응답하고 종속성 상태 확인은 상호작용하는 외부 시스템에 예측 가능한 로드를 생성합니다. 팀에서 이 작업을 수행할 때 상태 확인 스레드의 실패를 탐지하는 기능에 특별히 신경을 더 기울였습니다. 백그라운드 스레드가 종료되면 서버는 향후 서버 실패나 복구를 탐지하지 않습니다!

영향 범위와 관련하여 종속성 상태 확인 조절

종속성 상태 확인은 서버 상태에 대한 전체적인 테스트 역할을 수행한다는 점에 매우 매력적입니다. 하지만 종속성으로 인해 전체 시스템에서 계단식 실패가 발생할 수도 있다는 점에서 위험하기도 합니다.

Amazon의 서비스 중심 아키텍처를 살펴보면 상태 확인 종속성을 처리하는 방법에 대한 인사이트를 얻을 수 있습니다. Amazon의 각 서비스는 몇 가지 작은 작업을 수행하도록 설계되었으며, 모든 것을 처리하는 모놀리스 체계가 아닙니다. 이러한 방식으로 서비스를 구축하려는 데에는 여러 가지 이유가 있습니다. 작은 팀에서 빠른 혁신을 유도하고 서비스 하나에서 문제가 발생한 경우 영향 범위를 줄이기 위해서입니다. 이러한 아키텍처 설계는 상태 확인에도 적용할 수 있습니다.

하나의 서비스가 다른 서비스를 호출하면 해당 서비스에 대한 종속성이 생깁니다. 서비스가 가끔씩만 종속성을 호출하는 경우 종속성이라고 말할 수 없긴 해도 서비스가 일부 유형의 작업을 계속 수행하기 때문에 이러한 종속성을 "소프트 종속성"이라고 간주할 수 있습니다. 페일 오픈 보호 기능 없이, 종속성을 테스트하는 상태 확인을 구현하면 이러한 종속성은 "하드 종속성"이 됩니다. 종속성이 중단되면 서비스도 중단되고, 영향 범위가 커지며 계단식으로 실패할 수 있습니다.

다른 서비스로 기능을 구분하긴 해도 각 서비스는 여러 API와 같이 작동합니다. 때로는 서비스의 API가 자체적으로 종속성을 보유하기도 합니다. 하나의 API가 영향을 받으면 서비스가 다른 API를 계속 지원해주기를 원합니다. 예를 들어, 서비스는 컨트롤 플레인(예를 들어, 수명이 긴 리소스에서 CRUD API라고도 함)이자, 데이터 플레인(높은 처리량의 매우 중요한 비즈니스 API)일 수 있습니다. 우리는 컨트롤 플레인 API가 종속성과 통신하는 데 문제가 발생해도 데이터 플레인 API는 계속 작동하기를 원합니다.

마찬가지로, 데이터의 상태나 입력에 따라 심지어 단일 API도 다르게 동작할 수 있습니다. 공통된 패턴으로, 데이터베이스를 쿼리하지만 일정 시간 동안 로컬로 응답을 캐시하는 읽기 API가 있습니다. 데이터베이스가 중단되면 데이터베이스가 다시 온라인 상태가 될 때까지 서비스는 계속 캐시된 읽기를 지원할 수 있습니다. 유일한 하나의 코드 경로가 비정상 상태인 경우 상태 확인에 실패하면 종속성과의 통신 문제에 영향을 받는 범위가 커집니다.

상태를 확인하는 종속성에 대한 이야기를 해보면 마이크로서비스와 어느 정도 모놀리식 서비스 사이의 장단점에 대한 흥미로운 질문이 하나 생깁니다. 서비스를 분류할 배포 가능한 단위 또는 엔드포인트 수에 대한 명확한 규칙은 거의 없지만, "상태를 확인할 종속성"과 "영향 범위를 늘리는 실패"에 대한 질문은 마이크로서비스를 만들 것인지 매크로서비스를 만들것인지 결정할 때 흥미로운 고려 사항이 됩니다. 

상태 확인이 잘못될 경우 실제 상황

이론적으로는 모두 다 타당해보이지만, 실제로 상태 확인이 잘못되면 시스템에서는 어떤 일이 벌어질까요? 전체적인 상황을 고려하기 위해 AWS 고객과 Amazon 주변에서 들은 이야기에서 패턴을 살펴보았습니다. 그리고 상태 확인의 취약점이 광범위한 문제를 일으키지 않도록 팀에서 구현하는 "벨트와 세스펜더"와 같은 보완 요소도 검토했습니다.

배포

상태 확인 문제의 한 가지 패턴은 배포와 관련이 있습니다. AWS CodeDeploy와 같은 배포 시스템은 한 번에 플릿의 한 하위 집합에 새 코드를 푸시하고, 다음으로 진행하기 전에 하나의 배포 단계가 완료되기를 기다립니다. 이 프로세스는 배포 시스템이 새 코드를 사용해 가동 및 실행되면 다시 해당 시스템으로 보고하는 서버에 의존합니다. 서버가 다시 보고하지 않으면 배포 시스템은 새 코드에 문제가 있다고 판단하고 배포를 롤백합니다.

가장 기본적인 서비스 시작 배포 스크립트는 서버 프로세스를 나누고 배포 시스템에 "배포 완료"를 즉시 응답하는 것입니다. 하지만 이 방법은 새 코드에서 문제가 많이 발생할 수 있으므로 위험합니다. 새 코드는 시작 직후 크래시를 발생시키거나 시스템을 중단시키고 서버 소켓에서 수신 대기를 시작하지 못하게 하거나 요청을 처리하는 데 필요한 구성을 로드하지 못하게 하거나 버그를 일으킬 수 있습니다. 종속성 상태 확인에 대해 테스트하도록 배포 시스템이 구성되지 않은 경우 잘못된 배포를 푸시하는지 확인하지 못합니다. 그리고 계속 서버를 하나씩 중단시킵니다.

다행히 실제로 Amazon 팀은 여러 완화 시스템을 구현하여 이러한 시나리오에서 전체 플릿이 중단되는 상황을 방지합니다. 이러한 한 가지 완화 방법은 전체 플릿 크기가 너무 작아지거나 너무 많은 로드를 실행할 때마다 또는 지연 시간이나 오류 비율이 높은 경우 트리거할 경보를 구성하는 것입니다. 이러한 경보가 트리거되면 배포 시스템은 배포를 중지하고 롤백합니다.

또 다른 완화 방법은 단계별 배포를 사용하는 것입니다. 전체 플릿을 단일 배포로 배포하는 대신 가용 영역에 대한 전체 통합 테스트를 일시 중지하고 실행하기 전에 해당 가용 영역과 같은 하위 집합을 배포하도록 서비스를 구성할 수 있습니다. 이러한 가용 영역당 배포 조정 방식은 단일 가용 영역에 문제가 있어도 서비스를 계속 운영할 수 있도록 설계하였기 때문에 매우 편리합니다.

물론, 프로덕션에 배포하기 전에 Amazon 팀은 테스트 환경으로 이러한 변경 사항을 푸시하고 이러한 유형의 실패를 포착하는 자동화된 통합 테스트를 실행합니다. 하지만 프로덕션 환경과 테스트 환경 사이에는 어쩔 수 없는 미묘한 차이가 존재하기 때문에, 프로덕션에서 영향을 미치기 전에 모든 종류의 문제를 포착하도록 여러 계층의 배포 안전 기능을 결합하는 것이 중요합니다. 잘못된 배포로부터 서비스를 보호한다는 점에서 상태 확인이 중요하지만 이것으로 그치지 않습니다. 이러한 문제와 다른 실수로부터 플릿을 보호하기 위해 안전장치 역할을 하는 "벨트와 서스펜더" 접근 방식을 고려합니다.

비동기식 프로세서

또 다른 실패 패턴은 SQS 대기열 또는 Amazon Kinesis Stream을 폴링하여 작업하는 서비스와 같은 비동기식 메시지 처리와 관련이 있습니다. 로드 밸런서에서 요청을 가져오는 시스템과 달리, 이번에는 서비스에서 서버를 제거하기 위해 자동으로 상태 확인을 수행하는 요소가 없습니다.

서비스가 충분한 상태 확인을 수행하지 않으면, 개별 대기열 작업자 서버에서 파일 설명자 부족 또는 가득 찬 디스크와 같은 장애가 발생할 수 있습니다. 이러한 문제가 발생해도 서버는 계속 대기열에서 작업을 가져오지만 서버는 더 이상 메시지를 성공적으로 처리하지 못합니다. 이 문제는 메시지 처리의 지연 때문에 발생합니다. 잘못된 서버가 대기열에서 빠르게 작업을 가져오고 이를 처리하지 못하기 때문입니다.

이러한 종류의 상황에서는 종종 그 영향을 억제하기 위해 여러 가지 보완 요소가 존재합니다. 예를 들어, 서버가 SQS에서 가져오는 메시지를 처리하지 못하는 경우 구성된 메시지 가시성 제한 시간이 경과하면 SQS는 해당 메시지를 다른 서버로 재전송합니다. 전체적인 지연 시간은 길어지지만 메시지는 삭제되지 않습니다. 또 다른 보완 요소는 메시지 처리 중 오류가 너무 많이 발생하면 생성되는 경보입니다. 이를 통해 운영자에게 조사가 필요함을 알립니다.

가득 찬 디스크

우리가 볼 수 있는 또 다른 실패의 유형은 서버의 디스크가 가득 찬 경우입니다. 그러면 처리와 로깅이 모두 실패합니다. 이 실패로 인해 모니터링 가시성에 차이가 발생합니다. 서버가 모니터링 시스템으로 실패를 보고할 수 없기 때문입니다.

역시 여러 완화 제어 조치를 통해 서비스가 "수치"에만 의존하지 않도록 하고 영향을 빠르게 완화합니다. Application Load Balancer나 API Gateway와 같은 프록시가 전면에 배치된 시스템에는 해당 프록시에서 생성되는 오류 비율과 지연 시간 지표가 있습니다. 이 경우 서버가 이를 보고하지 않고도 경보가 생성됩니다. 대기열 기반 시스템의 경우 Amazon Simple Queue Service(Amazon SQS)와 같은 서비스에서 일부 메시지에 대한 처리가 지연됨을 알리는 지표를 보고합니다.

이러한 솔루션의 공통점은 여러 계층의 모니터링을 제공한다는 것입니다. 서버 자체에서도 오류를 보고하지만 외부 시스템에서도 보고합니다. 상태 확인에서도 이 동일한 원칙이 중요합니다. 외부 시스템은 특정 시스템의 상태를 시스템에서 자체적으로 테스트할 때보다 더 정확하게 테스트할 수 있습니다. 그래서 팀은 AWS Auto Scaling을 통해 외부 Ping 상태 확인을 수행하도록 로드 밸런서를 구성합니다.

또한 정기적으로 각 서버에 상태가 정상인지 묻고 서버가 비정상 상태이면 이를 AWS Auto Scaling에 보고하는 고유한 사용자 지정 상태 확인 시스템을 작성합니다. 이러한 시스템의 한 가지 공통된 구현으로는 1분마다 실행되며 모든 서버의 상태를 테스트하는 Lambda 함수가 있습니다. 이 상태 확인은 한 번에 너무 많은 서버를 비정상 상태로 잘못 표시하지 않도록 DynamoDB와 같은 서비스에서 실행할 때마다 해당 상태를 저장할 수도 있습니다.

좀비

문제의 또 다른 패턴은 좀비 서버를 포함합니다. 이러한 서버는 일정 시간 네트워크와 연결이 끊어질 수 있지만 여전히 실행 상태이거나 오랫동안 전원이 꺼졌다가 나중에 재부팅될 수 있습니다.

좀비 서버가 다시 활성 상태가 되면 플릿 나머지와 동기화 격차가 커질 수 있고, 이로 인해 심각한 문제가 발생할 수 있습니다. 예를 들어, 좀비 서버가 훨씬 이전의 호환되지 않는 소프트웨어 버전을 실행하는 경우 다른 스키마의 데이터베이스와 상호작용하려고 할 때 실패하거나 잘못된 구성을 사용할 수 있습니다.

좀비 문제를 해결하기 위해 시스템은 현재 실행 중인 소프트웨어 버전에 대한 상태 확인에 자주 회신합니다. 그리고 중앙 모니터링 에이전트가 플릿에서 응답을 비교하여 예상치 못한 오래된 버전을 실행하는 서버를 찾고 이러한 서버가 다시 가동 상태가 되지 않도록 합니다.

결론

서버와 여기에서 실행되는 소프트웨어는 각종 이상한 이유로 실패합니다. 실제로 하드웨어는 물리적으로 중단되기도 합니다. 소프트웨어 개발자들은 위에서 설명한 것처럼 소프트웨어를 중단시키는 버그를 실제로 작성하기도 합니다. 예상치 못한 모든 유형의 실패 모드를 포착하려면 간단한 실시간 검사에서 서버당 지표의 패시브 모니터링에 이르기까지 여러 계층의 확인이 필요합니다.

실패가 발생하면 이를 탐지하고 영향을 받는 서버를 빠르게 가동 중단하는 것이 중요합니다. 그래도 플릿 자동화와 마찬가지로 극단적인 상황이나 불확실한 상황에서 자동화를 끄고 사람이 개입하는 비율을 제한하고, 임계값 설정 및 회로 차단기를 추가합니다. 중앙 기관의 구축과 페일 오픈 구성은 비율을 제한하는 자동화의 안전 장치와 함께 심층적인 상태 확인의 이점을 활용하는 전략입니다.

Hands-on Lab

여기에서 배운 원칙을 Hands-on Lab을 통해 실습해 보십시오.


저자에 대하여

David Yanacek는 AWS Lambda 서비스의 선임 수석 엔지니어입니다. David는 2006년부터 Amazon에서 소프트웨어 개발자로 근무하고 있으며, 전에는 Amazon DynamoDB와 AWS IoT는 물론, 내부 웹 서비스 프레임워크와 플릿 운영 자동화 시스템도 다루었습니다. David가 회사에서 가장 즐겨하는 활동 중 하나는, 로그를 분석하고 운영 지표를 면밀히 조사하며 시간에 따라 시스템을 보다 더 원활하게 실행시키는 방법을 찾는 것입니다.

시간 제한, 재시도 및 지터를 사용한 백오프