Elastic APM으로 Go Application 모니터링
진행하고 있는 프로젝트의 개발팀에서 Golang 기반 Backend API를 개발하는게 있어서 연초에 VUE, GO Application 샘플을 개발해놓았다. (매우 간단하게...)
이후 Application Performance Monitoring을 어떻게 할 것인가?
에 대한 질문들로 Elastic APM을 사용해보기로 했다.
지금까지 java APM만을 셋업했었는데 golang은 어떻게 다른지 파악해보자.
Java와의 차이
java application은 javaagent 로 올리면 바이트코드를 읽어 자동으로 수집하지만 go application은 아니다.
그래도 http route, gorm 을 통해 어느정도 요청에 대한 그래프를 볼 수 있다.
필자는 라우터 라이브러리로 고릴라, 데이터베이스는 gorm으로 프로젝트를 구성하였다.
- gorilla/mux
- gorm
적용방법
elastic에서 지원하는 라이브러리를 사용하여 아주 간편하게 적용할 수 있는데, 라우터에는 미들웨어를 추가하고, gorm은 전용 driver를 활용하면 되며, 모두 Elastic 공식 가이드 문서를 참고하여 진행하였다.
모듈 다운로드
go get -u go.elastic.co/apm/v2
goilla/mux 적용
route.go 파일에서 Route 생성 부분에 추가한다.(맨 처음으로 미들웨어 등록 필수)
참고로 golang 에서 지원하는 라우터 종류가 많은데 고릴라를 활용하면 elastic apm 에서 추적하는 것이 많다고 한다.
func NewHttpHandler() http.Hander {
...
// Elastic APM 추가 ( gorilla mux 는 자동으로 트랜잭션을 추적하도록 도와줌)
mux.Use(apmgorilla.Middleware())
...
}
gorm 적용
dbConnection.go 파일에서 gorm.io/driver/mysql 라이브러리를 go.elastic.co/apm/module/apmgormv2/v2/driver/mysql 로 교체한 뒤 mysql.Open 을 변경한다..
import (
...
apmmysql "go.elastic.co/apm/module/apmgormv2/v2/driver/mysql"
//"gorm.io/driver/mysql"
...
)
func InitDB() {
...
db, err := gorm.Open(apmmysql.Open(dsn), &gorm.Config{})
...
}
route.go 파일에서 미들웨어를 추가한다.
func NewHttpHandler() http.Hander {
...
// DB Context 미들웨어 추가
mux.Use(DBContextMiddleware)
...
}
// APM 미들웨어 함수
func DBContextMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
DB = DB.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
환경 변수 설정(Local)
먼저 개발자 PC에서 검증을 거치기 위해 local 테스트를 진행한다.
ELASTIC_APM_SECRET_TOKEN=QnVsV0laRUJ4OWo3QVBpdVZQbTU6MEVkcjJkRklUN2E0NDFCNUY1T3Yydw==
ELASTIC_APM_SERVER_URL=http://{ip}:{port}/
ELASTIC_APM_SERVER_TIMEOUT=60s
ELASTIC_APM_SERVICE_NAME=local-test
ELASTIC_APM_ENVIRONMENT=local
ELASTIC_APM_LOG_FILE=stderr
ELASTIC_APM_LOG_LEVEL=debug
설정 후 application 을 구동 하면 아래와 같이 나온다.(주기적으로 트랜잭션을 서버에 전달함)
{"level":"debug","time":"2024-08-07T14:40:39+09:00","message":"sent request with 2 transactions, 1 span, 0 errors, 0 metricsets"}
{"level":"debug","time":"2024-08-07T14:40:53+09:00","message":"gathering metrics"}
{"level":"debug","time":"2024-08-07T14:41:03+09:00","message":"sent request with 0 transactions, 0 spans, 0 errors, 4 metricsets"}
환경 변수 설정(Server)
서버에 배포했을 때 연결될 네트워크 정보를 설정한다.
필자의 환경은 애플리케이션을 kubernetes 에 배포하여 서비스명.네임스페이스.svc 경로로 서버를 지정하였고, APM 에서 수집될 명, 환경에 대한 구분을 변경해주었다.
ELASTIC_APM_SECRET_TOKEN=QnVsV0laRUJ4OWo3QVBpdVZQbTU6MEVkcjJkRklUN2E0NDFCNUY1T3Yydw==
ELASTIC_APM_SERVER_URL=http://apm-server-apm-server.logging.svc:8200
ELASTIC_APM_SERVER_TIMEOUT=60s
ELASTIC_APM_SERVICE_NAME=my-goapp-service
ELASTIC_APM_ENVIRONMENT=dev
#ELASTIC_APM_LOG_FILE=stderr
#ELASTIC_APM_LOG_LEVEL=debug
Kibana에서 확인
Postman API Test
먼저 간단하게 API를 호출하는 스크립트를 작성하였다.
랜덤값을 발생시켜 PUT API(데이터를 insert 하는 로직)에서 일부로 Duplicate Key 에러를 발생시키고자 한다.
pm.environment.set("randomNumber", Math.floor(Math.random() * 500000));
--- body raw
{
"UserId":"auto{{randomNumber}}",
"FirstName":"first{{randomNumber}}",
"LastName":"lim",
"Email":"auto@{{randomNumber}}"
}
설정을 마친 뒤 Runner 를 통해 API를 자동으로 호출하자.
Kibana View
APM 탭에서 이제 아래와 같은 데이터를 확인할 수 있다. 트랜잭션 처리 속도는 분단위(TPM)로 볼 수 있는게 약간 아쉽다.
Error 가 발생한 Case를 확인할 수 있다.
APM의 핵심인 구간별 레이턴시도 확인할 수 있다.
무료로 이런 기능을 제공한다는 점에서 매우 괜찮게 느꼈다. 그동안 Logging 적재/분석용으로 ElasticSearch를 구축/운영 했었는데, 더 많은 기능들이 있었고, 실제로 사용하는 사례도 적잖게 있다고하니 도입을 검토 해볼만하다고 느낀다.