AWS 기술 블로그
AWSLogs 컨테이너 로그 드라이버의 non-blocking 모드로 로그 손실 방지
본 게시물은 AWS Container Blog에 게시된 “Preventing log loss with non-blocking mode in the AWSLogs container log driver” by Wesley Pettit을 한국어로 번역한 글입니다.
소개
향상된 관찰 가능성과 문제 해결을 위해 컨테이너 로그를 컴퓨팅 플랫폼에서 중앙 로깅 서버에서 실행되는 컨테이너로 전달하는 것이 좋습니다. 실제로는 로깅 서버에 연결할 수 없거나 때때로 로그를 전송할 수 없는 경우가 있습니다. 로그 서버 장애에 대비하여 설계할 때 아키텍처에 상충 관계가 있습니다. 서비스 소유자는 다음 고려 사항 중에서 선택해야 합니다.
- 애플리케이션이 트래픽에 대한 응답(또는 작업 수행)을 중지하고 중앙 집중식 로깅 서버가 복원될 때까지 기다려야 합니까? (즉, 정확한 감사 로그가 서비스 가용성보다 우선 순위가 높습니까?)
- 버퍼가 가득 차기 전에 로깅 서버가 회복되길 바라며 로그를 버퍼링하는 동안 애플리케이션이 트래픽을 계속 제공해야 합니까? 드문 경우지만 로그 대상을 사용할 수 없는 경우 로그 손실에 대한 위험을 감수해야 합니까?
컨테이너 로깅 드라이버에서 이 트레이드 오프는 첫 번째 고려 사항으로 구성 파라미터인 blocking과 두 번째 고려 사항인 non-blocking으로 구현됩니다. AWS 블로그인 Choosing container logging options to avoid backpressure에서 Rob Charlton은 이 장단점을 살펴보고 AWSLogs 컨테이너 로그 드라이버에 대한 blocking 모드에서 애플리케이션이 작동하는 방식을 테스트하는 방법을 설명하며 테스트 결과를 보여줍니다.
솔루션 개요
AWS Logs driver modes
Amazon Elastic Container Service(Amazon ECS)에서 AWSLogs 로깅 드라이버는 컨테이너의 stdout 및 stderr에서 로그를 캡처한 다음 PutLogEvents API를 통해 Amazon CloudWatch Logs에 업로드합니다. 로그 드라이버는 다음과 같이 구성할 수 있는 모드 설정을 지원합니다:
- blocking(기본값): 로그를 Amazon CloudWatch로 즉시 전송할 수 없는 경우, 컨테이너 코드에서 stdout 또는 stderr에 쓰기를 호출하면 코드 실행이 차단되고 중단됩니다. 애플리케이션의 로깅 스레드가 차단되어 애플리케이션이 작동하지 않고 상태 검사 실패 및 작업 종료로 이어질 수 있습니다. 필요한 로그 그룹 또는 로그 스트림을 만들 수 없는 경우 컨테이너 시작이 실패합니다.
- non-blocking: 로그를 Amazon CloudWatch로 즉시 전송할 수 없는 경우, max-buffer-size 설정으로 구성된 in-memory 버퍼에 저장됩니다. 버퍼가 가득 차면 로그가 손실됩니다. 컨테이너 코드에서 stdout 또는 stderr에 쓰는 호출은 차단되지 않고 즉시 반환됩니다. Amazon ECS에서 Amazon Elastic Compute Cloud(Amazon EC2)를 사용하면 필요한 로그 그룹이나 로그 스트림을 생성할 수 없는 경우에도 컨테이너 시작이 실패하지 않습니다. AWS Fargate에서 Amazon ECS를 사용하면 구성된 모드에 관계없이 로그 그룹 또는 로그 스트림을 생성할 수 없는 경우 컨테이너 시작이 항상 실패합니다.
기본이 아닌 non-blocking 모드로 전환해야 합니까?
기본 blocking 모드의 애플리케이션 가용성 위험으로 인해 서비스 소유자는 non-blocking 모드로 전환하는 것을 고려할 수 있습니다. 이로 인해 다음과 같은 질문이 제기됩니다:
- max-buffer-size는 어떻게 선택해야 하나요? 기본 1MB 크기로 로그 손실을 방지할 수 있나요?
- non-blocking 모드를 사용하면 빠른 속도로 로깅하는 애플리케이션의 로그 손실이 발생하나요?
이러한 질문에 답하기 위해 AWS 팀은 non-blocking 모드에서 AWSLogs 드라이버에서 대규모 로그 수집 테스트를 실행했습니다.
권장되는 max-buffer-size 값은 무엇입니까?
non-blocking 모드를 선택하는 경우 이 테스트에서 권장되는 Amazon ECS 작업 정의 설정은 다음과 같습니다.
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"mode": "non-blocking",
"max-buffer-size": "25m",
}
}
버퍼 크기를 결정하는 변수는 무엇입니까?
필요한 max-buffer-size에 영향을 미치는 주요 변수는 애플리케이션이 데이터를 출력하는 빈도와 로그 처리량입니다.
CloudWatch Metric의 IncomingBytes 메트릭을 사용하여 로그 그룹에 대한 수집 속도를 추적하세요. 모든 컨테이너가 거의 동일한 속도로 전송한다고 가정하면, 로그 그룹 수집 속도를 컨테이너 수로 나눌 수 있습니다. 그러면 각 개별 컨테이너에 대한 속도를 알 수 있습니다.
특히 인시던트 발생 시 로그 출력이 때때로 급증할 수 있으므로 각 컨테이너의 로그 처리량을 과도하게 추정하지 않는 것이 좋습니다. 가능하면 부하 테스트 또는 최근 인시던트 중 처리량을 계산합니다. 1분 이하의 시간 간격에 대한 최대 로그 출력 속도를 사용하여 처리량 폭증을 고려하세요.
테스트 결과 무엇을 발견했나요?
이 게시물에서 논의된 결과는 성능을 보장하지 않으며 단순히 테스트를 실행한 결과를 공유한다는 점에 유의해야 합니다.
다음은 중앙 로깅 서버가 사용 가능하고 정상일 때의 주요 결과입니다.
- max-buffer-size >= 4MB : 컨테이너의 로그 출력 속도가 2MB/s 미만인 경우 로그 손실을 표시하지 않습니다.
- max-buffer-size >= 25MB는 컨테이너의 로그 출력 속도가 5MB/s 미만인 경우 로그 손실을 표시하지 않습니다.
- 6MB/s를 초과하면 AWSLogs 드라이버의 성능이 예측하기 어렵고 일관적이지 않습니다. 예를 들어, 100MB 버퍼와 7MB/s의 이상치 테스트 실패가 있었으며 간헐적인 로그 손실을 방지하는 것이 불가능할 수 있습니다.
- 결과적으로 Amazon ECS의 AWS Fargate 시작 유형과 Amazon EC2 시작 유형이 유사합니다.
이 문서는 테스트 결과에 대한 간단한 요약을 제공합니다. 시작 유형 및 로그 크기별로 분류된 전체 벤치마크 결과, 분석 및 데이터는 Github에서 찾을 수 있습니다.
테스트는 어떻게 실행되었나요?
벤치마킹에 사용된 코드는 GitHub에서 확인할 수 있습니다. Amazon EC2 테스트는 Docker 버전 v20.10.25에서 수행되었습니다. AWS Fargate 테스트는 플랫폼 버전 1.4에서 수행되었습니다.
각 로그 손실 테스트 Amazon ECS Task에서 실행되며 AWSLogs 드라이버를 사용하여 1GB의 로그 데이터를 Amazon CloudWatch Logs로 전송합니다. 그런 다음 Task는 Amazon CloudWatch Logs를 쿼리하여 모든 로그 이벤트를 다시 가져오고 수신된 로그 이벤트 수를 확인합니다. 각 로그 메시지에는 예측 가능한 시퀀스 번호인 고유 ID가 있습니다. 테스트는 1KB와 250KB 크기의 단일 로그 메시지로 실행되었습니다.
로그 손실에 대한 의미 있는 통계 분석을 위한 충분한 데이터를 얻기 위해 수천 번의 테스트 실행이 실행되었습니다.
버퍼가 가득 차서 로그가 손실되었는지 어떻게 알 수 있나요?
안타깝게도 AWSLogs 로깅 드라이버를 사용하면 non-blocking 모드 버퍼로 인해 손실된 로그를 확인할 수 없습니다. 손실이 발생할 때 Docker Daemon에서 내보내는 로그 문이나 메트릭이 없습니다. 로그 손실 메트릭에 대한 제안은 GitHub에서 의견을 남겨 주세요.
버퍼 크기는 내 애플리케이션에 사용 가능한 메모리에 어떤 영향을 줍니까?
max-buffer-szie 설정은 Go 슬라이스에서 메시지의 바이트 크기를 제어합니다. Go는 GC(garbage collection)가 동작하는 언어이기 때문에 메모리 사용량을 직접적으로 제한하지는 않습니다. 한 테스트 모음에 따르면 큐의 실제 크기는 평균적으로 상당히 작으며 일반적으로 500KB 미만입니다. 대기 시간이나 로그 처리량이 증가하는 동안 버퍼 크기가 한도까지 올라가는 경우가 있습니다. 이는 버퍼에 사용되는 메모리가 시시각각 크게 달라지며 Go의 GC로 인해 실제 메모리 사용량이 구성된 크기를 초과할 수 있음을 의미합니다.
컴퓨팅 플랫폼이 버퍼 크기에 영향을 줍니까?
테스트 결과, Amazon EC2와 AWS Fargate 모두에서 시작된 Amazon ECS 작업과 결과가 유사한 것으로 나타났습니다.
리전 간 로그를 보낼 때 non-blocking 모드가 안전한가요?
AWSLogs 드라이버는 CloudWatch에 대한 연결 지연 시간이 짧기 때문에 테스트 작업과 동일한 리전의 Amazon CloudWatch API에 로그를 보낼 때 훨씬 더 높은 속도로 일관되게 업로드할 수 있습니다. 리전 간 로그 업로드의 안정성이 떨어지며 이는 리전 격리의 모범 사례를 위반합니다. 또한 지역 간 로그 푸시로 인해 네트워크 비용도 높아집니다.
테스트 결과
본 게시물에서 논의된 결과는 성능을 보장하지 않는다는 점에 유의하시기 바랍니다. 우리는 단지 우리가 실행한 테스트 결과를 공유하고 있을 뿐입니다.컴퓨팅 플랫폼(AWS Fargate 대 Amazon EC2) 및 로그 메시지 크기와 같은 차원(dimension)으로 분류된 전체 데이터 테이블은 GitHub를 참조하십시오.
리전 내 테스트 실행 요약
다음은 약 17,000건의 리전 내 테스트 실행에 대한 히트맵 요약입니다. 음영 처리된 상자 안의 백분율은 최악의 테스트 실행에서의 로그 손실 백분율입니다. 붉은 색이 진할수록 로그 손실이 더 많이 관찰되었습니다. 로그 출력 속도가 2MB/s 미만인 모든 테스트 실행에서 로그 손실이 없었습니다.
리전 간 테스트 요약
작업은 us-west-2에서 실행되었으며 us-east-1의 Amazon CloudWatch에 업로드합니다. 결과에 따르면 리전 간 로그 업로드는 안정성이 낮으며 로그 손실을 피하가 위해 훨씬 더 큰 버퍼 크기가 필요합니다.
결론
이 게시물에서 다음 내용을 배웠습니다.
- 컨테이너 로그 드라이버의 blocking 및 non-blocking을 통한 애플리케이션 가용성과 로그 손실 간의 트레이드 오프.
- max-buffer-size 값에 따른 non-blocking AWSLogs 드라이버 동작 방식.
- 리전 간 로그 업로드는 권장되지 않으며 non-blocking 모드를 사용하면 로그 손실 위험이 훨씬 더 높음.
- 컨테이너당 로그 출력 속도를 찾는 방법.
- non-blocking 모드에서는 AWSLogs 드라이버를 사용하여 로그 손실을 모니터링 불가능.
애플리케이션 가용성과 로그 손실 간의 트레이드 오프를 고려할 때, 사용 사례에 blocking 모드가 필요한지 non-blocking 모드가 필요한지 결정해야 합니다. 애플리케이션 가용성 측면을 선택하는 경우, non-blocking 모드에서 AWSLogs 드라이버를 선택해야 할까요, 아니면 다른 로그 수집 솔루션을 선택해야 할까요? FireLens가 포함된 FluentBit와 같은 대부분의 다른 로그 수집 솔루션은 유한 버퍼에 로그를 저장하는 것만 지원할 뿐 애플리케이션 측면에서 block하지 않습니다. 그러나 다른 솔루션은 로그 손실을 방지하기 위해 조정하고 모니터링하기가 더 쉬울 수 있습니다. non-blocking 모드에서 AWSLogs 드라이버를 선택하는 경우, 컨테이너당 로그 출력 속도를 고려할 때 위험 허용 범위에 맞는 최대 버퍼 크기는 어느 값인가요? GitHub에서 전체 테스트 결과를 주의 깊게 검토하는 것이 좋습니다. 결과를 고려할 때, 최대 버퍼 크기는 25m를 권장하며, 리전 간 로그 전송은 매우 불안정하므로 모든 로그 업로드가 리전 내에서 이루어지도록 하는 것이 좋습니다.