왜 필요한가?
서비스를 개발하고 배포,운영하다 보면 다양한 비기능 요구사항에 마주치게 되는데요.
이러한 요구사항을 만족하기 위해서는 서버가 어떻게 돌아가고 있는지에 대한 여러 정보가 필요하게 됩니다.
특히나 단순히 로그를 남기는 정도로는 해소하기 어려운 케이스들이 발생하게 됩니다.
예를 들어, 다음과 같은 케이스에서는 로그만으로 해결하기에는 어려움이 있습니다.
1. 분산 시스템 환경
MSA, 클라우드 네이티브 환경에서는 기존의 운영 방법에 한계가 있습니다. 봐야 하는 정보가 여러 마이크로 서비스에 분산되어 있고, 서비스간 상호작용을 한 눈에 보기 어렵기 때문입니다.
2. 실시간 문제 분석
로그는 사용자가 설정한 이벤트만 기록하고, 이는 과거의 기록이므로 실시간으로 발생하는 성능 저하나 장애를 분석하는데는 적절하지 않습니다.
3. 대용량 로그 분석
로그를 꼼꼼히 남기더라도, 로그의 양이 너무나 많아지면 사람이 보고 분석하는 데 한계가 있습니다. 이를 종합적으로 관리하고 통계 등을 제공하는 도구가 필요합니다.
이러한 문제는 새로운 것이 아니며, 이를 해결하기 위한 솔루션들도 많이 존재합니다.
그러나 기존의 솔루션들은 벤더 종속적이거나, 특정 언어/프레임워크만 호환이 된다거나 하는 문제들이 있습니다.
오픈소스가 아니기 때문에, 기업에서 활용하기에 제약이 생기기도 합니다.
이를 해결하기 위한 OpenTelemetry는 벤더 종속적이지 않은 오픈소스 Observablility 프레임워크입니다.
"서버가 어떻게 돌아가고 있는지"에 대한 정보를 어떻게 처리할 것인가에 대한 방법론과 개념을 포함하여 OpenTelemetry는 실행가능한 프레임워크나 라이브러리를 제공하고 있습니다.
개발자 식으로 말하자면, 서버 운영 정보에 대해서 인터페이스와 구현체를 모두 제공한다고 생각해도 될 것 같습니다.
OpenTelemetry의 구상대로라면, 우리는 인터페이스만 학습하면 실제 서버의 구현체와 무관하게 이 서버를 적절히 관리할 수 있게 되는거죠.
Observablility
서버가 어떻게 돌아가고 있는지에 대한 정보, 또는 이 정보에 접근할 수 있는지를 Observability라고 표현합니다. (제가 이해하기로는)
프로세스가 RAM을 얼마나 차지하고 있는지, CPU는 얼마나 사용하고 있는지부터 DB Query에 걸리는 시간까지 운영상 도움이 될 수 있는 모든 정보를 포함합니다.
OpenTelemetry에서는 이러한 정보들을 3개로 분류합니다.
Logs
일반적으로 로그를 찍는다/남긴다고 할 때 그 로그입니다.
사용자가 애플리케이션 코드로 직접 입력하는 경우가 많고, 자동으로 남기기도 합니다.
Metric
애플리케이션이 실행 중인 VM/베어메탈 서버, 언어의 런타임 등에 대한 정보를 제공합니다.
RAM, CPU 사용량이나 Java의 JVM 런타임 정보 등이 여기에 포함됩니다.
Trace
여러 서비스를 거쳐 응답되는 경우, 어떤 요청이 어느 서비스로 가서 어떻게 처리되는지에 대한 그림을 그리기 어려운 경우가 많습니다.
API Gateway 등에서 Trace Id(일반적으로 UUID)를 발급하고 이를 포함해서 다른 서비스를 요청하게 되면 하나의 Id로 일련의 요청들을 모두 파악할 수 있습니다.
이러한 하나의 비즈니스 요청을 처리하는 일련의 요청 묶음을 추적하게 해주는 것을 Trace라고 합니다.
결론적으로 이러한 3개의 정보를 서비스에서 내보내고, 이 것들을 받아서 처리하고, 사람이 보기 편하도록 GUI로 나타내면 그것이 모니터링 시스템이 되고, Observability가 구축된 것입니다. 여기까지는 단순히 OpenTelemtry에서 정의한 개념들을 살펴본 것이지만, 이 개념들은 OpenTelemetry에 한정된 개념이 아니며 다른 솔루션에서는 다르게 부르더라도 근본적인 컨셉은 비슷합니다.
이렇게 개념으로 보면 간단하기 때문에 구축이 어렵지 않을 것 같지만, 실제로 구축해보면 많은 어려움이 뒤따릅니다.
Observability가 확보하기 어려운 이유 (OpenTelemetry가 유용한 이유)
실제로 모니터링 시스템을 구축하며 느낀 난점은 다음과 같습니다.
1. 서비스별로 사용하는 언어/라이브러리/프레임워크 등이 다릅니다.
2. Logs, Metric, Trace 규격을 어떻게 할지에 대한 고민이 필요합니다.
3. 어떤 정보를 Logs, Metric, Trace에 담아야 할지에 대한 고민이 필요합니다.
4. 수집한 정보를 어떤 솔루션을 통해서 어떻게 처리할지에 대한 고민이 필요합니다.
5. Observability의 기능과 가용성이 가능한 실제 비즈니스 로직에 영향이 없어야 합니다.
이러한 이유들에 대해서 하나씩 살펴보겠습니다.
1. 서비스별로 사용하는 언어/라이브러리/프레임워크가 다릅니다.
모니터링 시스템을 구축하기 전에는 Logs, Metric, Trace 정보는 개념적으로 전혀 벤더 종속적이지 않기 때문에, 서비스별로 사용하는 언어/라이브러리/프레임워크에 영향이 없을 거라고 생각했습니다.
개념적으로는 물론 그렇습니다. Logs, Metric, Trace 모두 결국 그냥 문자열 정보인데 어떤 방식으로 출력하든 형식만 맞춰 주면 상관 없는 것 아닌가? 라는 생각을... 저도 해버렸던 것이지요.
그러나 막상 구축해보면 이게 굉장히 복잡하게 얽힌 문제라는 걸 깨닫게 됩니다.
저희 팀의 케이스를 기반으로 설명해보자면, 로그 관련한 문제는 다음과 같았습니다.
저희 팀은 Java/Spring Boot, Go/Gin 두 가지 스택을 사용하고 있는데요.
Java/Spring Boot에서는 일반적으로 로그를 남기기 위해서 Logback 또는 Log4j를 이용합니다. Spring Boot와 Logback 라이브러리를 사용할 경우 날짜별로 파일을 남기고 로그를 롤링합니다.
Go/Gin에서는 아직 업계 표준이라고 부를만큼 압도적인 채택률을 가지는 라이브러리는 없지만, Zap을 많이 사용하고 저희 팀도 사용하고 있습니다. Go는 클라우드 네이티브에 최적화 되었다고 말해도 과언이 아닌 언어이고, 일반적으로 클라우드 네이티브, k8s를 사용하는 환경에서는 stdout으로 로그를 출력하는 경우가 많습니다. 이렇게 하는 이유는, 파드의 콘솔에 출력된 로그를 따로 파일로 저장하는 것이 편리하기 때문입니다.
당연하게도, Logback, zap의 로그 규격은 다릅니다. 로그를 출력하는 방식, 기록된 로그를 처리하는 방식도 조금씩 다릅니다.
물론 원칙적으로는 둘이 규격이 맞도록 로직을 조금 수정하면 되는 것이지만, 기존의 방식에 익숙해져 있는 개발자들에게 Observability를 위해서 바꿔달라고 하는 건, 귀찮은 일이죠.
각 스택에서는 국룰처럼 여겨지고 있는 규격을 바꾸는 건, 바꾸는 사람이나 받아들이는 사람 입장에서나 귀찮은 일입니다.
(서버 모니터링을 위해서 팀 전체가 다 움직여야 하는 귀찮은 일이 발생한다는 뜻입니다.)
거기다가 기본으로 제공되는 설정을 적극 활용하지 않고 커스텀 로직을 붙이기 시작하면, 어느 순간에는 이게 매우 무거워질겁니다.
라이브러리에 붙인 커스텀 로직도 유지보수의 대상이 될 거구요. (만약에 라이브러리가 버전업 되면 그걸 캐치업해서 deprecated 된 사양을 대체한다든지)
그렇게 사용자의 수고를 많이 들일거라면 라이브러리를 굳이 사용하지도 않았을지도 모르죠.
로그는 본질적으로 사용자가 남기고 싶은 문자열을 남기는 것일 뿐인데, 이렇게 수고가 많이 드는 건 아무도 원하지 않습니다.
"팀장님, 모니터링 시스템을 구축하려고 보니 로깅 라이브러리부터 만들어야 할 것 같습니다." 같은 상황이 될지도 모르는거죠.
2. Logs, Metric, Trace 규격을 어떻게 할지에 대한 고민이 필요합니다.
이미 많은 솔루션들이 Logs, Metric, Trace에 대한 독자 규격이 있습니다.
이 중에 어떤 규격을 고를지도, 생각보다 고민이 필요한 부분입니다.
왜냐면 어떤 서비스는 이미 A 규격을 쓰고 있고, 다른 서비스는 B 규격을 쓰고 있다든지 하는 일이 발생하기 때문입니다.
이렇게 이미 규격이 다른 경우에는 어떻게 할 것인지에 대한 고민이 필요합니다.
3. 어떤 정보를 Logs, Metric, Trace에 담아야 할지에 대한 고민이 필요합니다.
결국 다 연계된 문제이긴 합니다만, 언어/라이브러리/프레임워크 별로 중요한 정보가 달라집니다. 서비스의 종류에 따라서도 중요한 정보가 달라지고요.
예를 들어서, JVM 런타임과 Go 런타임에서 유용한 정보는 전혀 다릅니다. JVM 런타임에서는 힙 메모리에 대한 정보가 유용한 대신 Go 런타임에서는 고루틴 개수가 유용할 수 있습니다.
4. 수집한 정보를 어떤 솔루션을 통해서 어떻게 처리할지에 대한 고민이 필요합니다.
Logs, Metric, Trace를 처리하는 솔루션을 고르는 것도 생각보다 어려운 문제입니다.
일반적으로 세 개를 모두 지원하는 솔루션은 드물거나 없고, Log에 특화된 솔루션, Metric에 특화된 솔루션, Trace에 특화된 솔루션이 있다거나 하기 때문입니다.
이런 식이면 하나의 Observability를 위해서 하나의 서비스에 솔루션을 3~4개씩 붙여야 하는데, 바람직한 모습은 아니죠.
특히 4번 문제는 2번 문제와도 연관이 있기 때문에, 잘 고민해야 합니다.
솔루션 별로 독자 규격이 있기 때문이죠.
5. Observability의 기능과 가용성이 가능한 실제 비즈니스 로직에 영향이 없어야 합니다.
Observability는 가능한 실제 비즈니스 로직이 도는 데 영향을 주지 않는 걸 목표로 해야 합니다.
어떤 종류의 로그는 컴플라이언스와 관련이 있기도 하지만, 대부분의 경우 서버의 Observability를 확보하는데 리소스를 써서 비즈니스 로직에 방해가 되면 주객전도기 때문이죠.
그렇지만 제대로 된 Observabiltiy를 위해서는 Logs, Metric, Trace 정보를 가능한 많이 수집해야 하는데, 이는 필연적으로 무거운 작업이 될 수 밖에 없습니다. (특히 가벼운 API의 경우에는 거의 무조건 이러한 정보를 처리하는 로직이 실제 로직보다 더 무겁습니다.)
위와 같은 이유로 Observability는 생각보다 구축하기 까다로운 목표이며, OpenTelemetry를 이용하면 여러 고민을 해결할 수 있기 때문에 이를 사용하는 게 좋습니다.
위의 5가지 문제점을 OpenTelemetry를 이용해서 대부분 해결할 수 있는데요.
(이어서)