AWS 기술 블로그

AWS 오픈 소스 관찰 가능성(Observability) 도구로 커스텀 메트릭 모니터링

현대의 기업과 조직은 IT 인프라와 애플리케이션의 성능을 실시간으로 정확하게 파악하고, 문제가 발생할 때 즉각적으로 대응할 수 있는 능력이 중요합니다. 이 능력을 갖추기 위해 많은 조직들이 다양한 모니터링 도구를 활용하고 있습니다. 그 중에서도 특히 Prometheus (프로메테우스)는 널리 사용되는 오픈 소스 모니터링 도구로, 네트워크 트래픽, 시스템 자원 사용량, 애플리케이션 응답 시간 등 다양한 표준 메트릭을 모니터링하는 데 강점을 가지고 있습니다. 그러나 표준 메트릭만으로는 조직의 특정 요구사항을 모두 충족시키기 어렵습니다. 예를 들어, 특정 비즈니스 로직에 대한 성능을 모니터링하거나, 사용자 행동 패턴을 분석하기 위해서는 커스텀 메트릭이 필요합니다. 커스텀 메트릭은 표준 메트릭이 제공하지 않는 특정한 데이터를 수집하고 분석할 수 있게 하여, 보다 세밀한 운영상의 통찰을 제공합니다. 이를 통해 조직은 문제를 더 신속하게 발견하고 대응할 수 있으며, 운영 효율성을 극대화할 수 있습니다.

하지만 현재 많은 조직들이 Prometheus를 통해 다양한 표준 메트릭을 모니터링하는 것에 비해, 커스텀 메트릭을 효과적으로 수집하고 활용하는 데에는 어려움을 겪고 있습니다. 이는 주로 커스텀 메트릭을 정의하고 수집하는 과정이 복잡하고, 이를 기존 시스템과 통합하는 데 추가적인 노력이 필요하기 때문입니다. 또한, 커스텀 메트릭을 위한 데이터 모델링, 수집 주기 설정, 알람 구성 등 다양한 세부 사항을 고려해야 하므로, 많은 리소스와 전문 지식이 요구됩니다.

이 블로그에서는 Prometheus에서 제공하는 클라이언트 라이브러리로 커스텀 메트릭을 생성하고 AWS의 오픈 소스 관찰 가능성 도구인 Amazon Managed Service for Prometheus (AMP)Amazon Managed Service for Grafana (AMG)를 활용하여 해당 메트릭을 가공하고 시각화하는 방법을 알아보고자 합니다.

Prometheus

Prometheus는 주로 Pull 방식을 사용하여 데이터를 수집하는 모니터링 시스템입니다. 특정 경우에만 Agent가 데이터를 Push하며, 이는 주로 작은 배치 작업이나 서버리스 환경, OpenTelemetry를 사용하여 메트릭을 전달하는 상황에서 발생합니다. 데이터 수집의 주요 소스는 /metric 엔드포인트를 통해 이루어지며, 이는 주로 Exporters에 의해 제공됩니다. Exporters는 코드를 직접 수정할 수 없는 패키징 소프트웨어에서 메트릭을 노출시키는 데 사용되며, 네트워크, 스토리지, 데이터베이스 솔루션, 시스템 계측 등의 여러 상황에서 활용됩니다. 또한 Prometheus는 다양한 클라이언트 라이브러리를 통해 코드 기반으로 메트릭을 수집할 수 있도록 구성할 수 있습니다. 공식 라이브러리로는 Go, Python, Java, Ruby가 있으며, 비공식 라이브러리로는 Bash, C/C++, Lua, Node.js, Perl, PHP, R, Rust, Dart 등이 있습니다.

서비스 디스커버리는 Static Target, File-Based, Kubernetes API, DNS 기반 등과 같이 여러 가지 방법을 통해 자동으로 메트릭을 스크랩하는 대상을 등록하는데 AWS EC2 DescribeInstances API를 사용하면 EC2 인스턴스에서 스크랩 대상을 검색하고, Private IP 주소를 대상으로 등록할 수 있습니다.

Prometheus에서 메트릭을 노출할 때는 Prometheus Exposition Format이라는 표준 형식을 사용합니다. 이 형식은 주로 텍스트 기반 포맷이나 OpenMetrics 텍스트 포맷으로 정의됩니다. 일반적으로 사용하는 텍스트 기반 포맷의 예시는 다음과 같습니다:

# HELP <metric_name> <help_text>
# TYPE <metric_name> <type>
<metric_name>{<label_name>=<label_value>, ...} <metric_value> [<timestamp>]
- `# HELP`: 메트릭에 대한 설명
- `# TYPE`: 메트릭의 유형 (gauge, counter, histogram, summary 등)
- `<metric_name>`: 메트릭 이름
- `{<label_name>=<label_value>, ...}`: 선택적 레이블
- `<metric_value>`: 메트릭 값
- `[<timestamp>]`: 선택적 타임스탬프

이 형식은 Prometheus 모니터링 시스템에 최적화되어 있으며, Amazon ECS 또는 EKS에서 생성되는 Prometheus 지표를 CloudWatch Container Insights 에서 수집하여 사용할 수 있습니다.

AWS Distro for OpenTelemetry Collector

AWS는 Amazon 환경에서 관찰 가능성 데이터를 쉽게 수집할 수 있도록 OpenTelemetry기반의 ADOT Collector를 제공합니다. 위에서 수집하고자 하는 Prometheus 기반 커스텀 메트릭도 Prometheus Receiver 를 통해 수집할 수 있습니다.

솔루션 개요

애플리케이션에서 생성하는 데이터를 커스텀 메트릭으로 변환하여 AMP로 저장하고 Amazon Managed Grafana를 통해 대시보드를 구성해 시각화하는 방법은 다음과 같습니다.

  • 단계 1 : 커스텀 메트릭 Exporter 빌드 및 실행
  • 단계 2 : EC2기반 ADOT Collector 구성하고 AMP로 메트릭 전달
  • 단계 3 : AMG 대시보드로 Custome Metric 시각화

EC2 인스턴스에서 구동중인 비즈니스 애플리케이션에서 제공하는 데이터로 커스텀 메트릭 데이터를 생성합니다. Node Exporter로 시스템 수준의 자원 사용량 메트릭을 수집하고 Prometheus에서 제공하는 클라이언트 라이브러리로 작성된 커스텀 메트릭 Exporter를 사용하여 비즈니스 애플리케이션의 커스텀 메트릭을 수집합니다. 이렇게 생성된 메트릭을 AWS Distro for Opentelemtry Collecter의 Receiver가 EC2 인스턴스 메타데이터를 통해 자동으로 서비스 디스커버리를 수행하고 별도의 타겟 엔드포인트 설정이 없이도 메트릭을 수신할 수 있습니다. AWS Distro for OpenTelemetry Collector는 데이터를 Amazon Managed Service for Prometheus로 원격 전송(Remote Write)합니다.

Amazon Managed Service for Prometheus (AMP)는 수신된 노드의 시스템 메트릭과 커스텀 메트릭 데이터를 저장하고, PromQL을 사용하여 쿼리 및 분석을 수행할 수 있도록 합니다. Amazon Managed Service for Grafana (AMG)는 AMP에서 수집된 데이터를 시각화하고 대시보드를 생성하여 실시간 모니터링 및 분석을 용이하게 합니다. 또한 다양한 데이터 소스와 통합하여 종합적인 모니터링 환경을 제공합니다. 이로 인해 사용자는 인프라와 애플리케이션의 성능을 종합적으로 모니터링하고 분석할 수 있습니다.

사전 준비사항

솔루션을 배포하기 위해서는 아래와 같은 사항을 미리 준비해야 합니다.

  • AWS account
  • 애플리케이션이 구동 중이며 Node Exporter 가 설치된 EC2 인스턴스
  • Amazon Managed Service for Prometheus 워크스페이스
  • Amazon Managed Servcie for Granafa 워크스페이스

단계 1 : 커스텀 메트릭 Exporter 빌드 및 실행

현재 운영중인 애플리케이션에서 관리를 위해 API를 통해 실시간으로 데이터를 생성하고 있다고 가정합니다. 결과 응답 데이터를 간단히 살펴보면, projectCode별로 API Request Count누적 데이터인 apiRequestCount 값을 number 유형의 데이터로 생성하는 것을 확인 할 수 있습니다.

[
  {
    "currentTime": 1716479897,
    "apiRequestCount": 3015,
    "projectCode": "4"
  },
  {
    "currentTime": 1716479837,
    "apiRequestCount": 17470,
    "projectCode": "5"
  },
  {
    "currentTime": 1716479777,
    "apiRequestCount": 43475,
    "projectCode": "6"
}
…
]

이 데이터를 가공하여 원하는 커스텀 메트릭을 생성하기 위해 EC2에서 운영 중인 애플리케이션에서 Prometheus Golang Client 의존성 다운로드 및 빌드를 수행하고, exporter 실행 바이너리를 생성합니다. 먼저 Go 모듈을 초기화하고 필요한 Prometheus 패키지를 다운로드합니다.

$ go mod init custom_exporter
$ go get -u github.com/prometheus/client_golang/prometheus
$ go get -u github.com/prometheus/client_golang/prometheus/promhttp

다음으로, 수집할 API 서버의 데이터를 가공하여 Prometheus에서 수집 가능한 형식으로 노출하는 Exporter 코드를 작성합니다.

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
    "log"
    "net/http"
    "time"
)

var (
    apiRequests = prometheus.NewGaugeVec(
        prometheus.GaugeOpts{
            Name: "api_requests_total",
            Help: "Total number of API requests by projectCode",
        },
        []string{"projectCode"},
    )
)

type APIResponse struct {
    CurrentTime      int64  `json:"currentTime"`
    ApiRequestCount  int    `json:"apiRequestCount"`
    ProjectCode      string `json:"projectCode"`
}

func init() {
    prometheus.MustRegister(apiRequests)
}

func fetchAPIData(apiEndpoint string) ([]APIResponse, error) {
    resp, err := http.Get(apiEndpoint)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var apiResponses []APIResponse
    err = json.NewDecoder(resp.Body).Decode(&apiResponses)
    if err != nil {
        return nil, err
    }

    return apiResponses, nil
}

func recordMetrics(apiEndpoint string) {
    go func() {
        for {
            apiResponses, err := fetchAPIData(apiEndpoint)
            if err != nil {
                log.Printf("Error fetching API data: %v", err)
                time.Sleep(10 * time.Second)
                continue
            }

            for _, response := range apiResponses {
                apiRequests.With(prometheus.Labels{"projectCode": response.ProjectCode}).Set(float64(response.ApiRequestCount))
            }

            time.Sleep(60 * time.Second) // Adjust the sleep duration as needed
        }
    }()
}

func main() {
    var (
        port = flag.Int("port", 8080, "Port to listen on")
        ip   = flag.String("ip", "localhost", "API endpoint")
    )
    flag.Parse()

    apiEndpoint := fmt.Sprintf("%s", *ip)
    recordMetrics(apiEndpoint)

    http.Handle("/metrics", promhttp.Handler())
    log.Printf("Beginning to serve on port :%d", *port)
    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
}

위 코드를 작성한 후, 다음 명령어를 사용하여 Exporter 실행 binary를 생성합니다.

$ go build -o custom_exporter

생성된 binary를 실행하여 API 서버의 데이터를 수집합니다. API 서버의 엔드포인트와 포트 번호를 인자로 전달합니다.

$ ./custom_exporter -port 8080 -ip {{API endpoint}}

Exporter가 실행 중이라면, Prometheus 서버에서 텍스트 기반 메트릭을 확인할 수 있습니다. 다음 명령어를 통해 수집된 메트릭을 확인합니다.

$ curl localhost:8080/metrics
# HELP api_requests_total Total number of API requests by projectCode.
# TYPE api_requests_total gauge
api_requests_total{pcode="0"} 0
api_requests_total{pcode="4"} 8.0788987e+07
api_requests_total{pcode="5"} 2.28921601e+08
api_requests_total{pcode="6"} 5

단계2 : EC2기반 ADOT Collector 구성하기

먼저 AWS Linux EC2 호스트에서 ADOT Collector를 설치하기 위한 환경을 준비합니다. AWS Distro for OpenTelemetry공식 문서에서 제공하는 설치 가이드를 참조할 수 있습니다.

AWS Linux EC2 호스트에서 다음 명령을 사용하여 ADOT Collector 소스 코드를 다운로드하고, 이를 기반으로 RPM 파일을 빌드합니다.

$ git clone https://github.com/aws-observability/aws-otel-collector.git
$ cd aws-otel-collector
$ make package-rpm

위 명령어를 실행하면 aws-otel-collector.rpm 파일이 생성됩니다. 생성된 RPM 파일을 EC2 인스턴스에 설치합니다.

$ sudo rpm -Uvh ./aws-otel-collector.rpm

RPM 설치가 완료되면 ADOT Collector는 /opt/aws/aws-otel-collector/ 디렉토리에 위치하게 됩니다. Prometheus 메트릭을 수집하고, 이를 AWS Managed Service for Prometheus에 전달하기 위한 ADOT(Amazon Distro for OpenTelemetry) Collector 설정(config.yaml)을 정의합니다. receivers 섹션은 수집할 메트릭 소스를 정의합니다. 이 예제에서는 Prometheus 수집기를 사용하여 메트릭을 수집합니다.

receivers:
  prometheus:
    config:
      global:
        scrape_interval: 60s
      scrape_configs:
        - job_name: 'custom_exporter'
          scrape_interval: 1m
          ec2_sd_configs:
            - region: "ap-northeast-2"
              port: 8080
              filters:
               - name: tag:exporter
                 values:
                  - custom
        - job_name: node
          ec2_sd_configs:
            - region: "ap-northeast-2"
              port: 9100
              filters:
               - name: tag:service_name
                 values:
                  - node_exporter
          relabel_configs:
            - source_labels:
                - __meta_ec2_instance_id
              target_label: instance_id

global: Prometheus 전역 설정으로, 기본 scrape_interval을 60초로 설정합니다.

scrape_configs: 실제 수집 대상을 지정하는 설정입니다. 여러 대상을 설정할 수 있어 custom_exporter와 node를 대상으로 설정합니다.

  • custom_exporter job은 EC2 서비스 디스커버리(ec2_sd_configs)를 통해 ap-northeast-2 리전의 인스턴스에서 포트 8080에서 실행 중인 custom_exporter 태그가 있는 인스턴스를 대상으로 메트릭을 수집합니다.
  • node: ap-northeast-2 리전의 포트 9100에서 실행 중인 node_exporter 태그가 있는 인스턴스를 대상으로 메트릭을 수집합니다. relabel_configs는 수집된 메트릭의 레이블을 변환합니다. 여기서는 __meta_ec2_instance_id를 instance_id로 변환합니다.

exporters 섹션은 수집된 메트릭을 외부 시스템으로 내보내는 방식을 정의합니다.

exporters:
  prometheusremotewrite:
    endpoint: " https://aps-workspaces.ap-northeast-2.amazonaws.com/workspaces/ws-*****-b155-b012e6f4596a/api/v1/remote_write "
    auth:
      authenticator: sigv4auth

prometheusremotewrite: endpoint는 아래 그림과 같이 AWS Managed Service for Prometheus의 원격 쓰기 엔드포인트를 지정합니다. auth 섹션에서는 AWS SigV4 인증을 사용하여 엔드포인트에 접근합니다.



extensions 섹션은 Collector의 기능을 확장하는 모듈을 정의합니다.

extensions:
  sigv4auth:
    region: "ap-northeast-2"

sigv4auth: AWS SigV4 인증을 사용하는 확장 기능입니다.

  • region은 인증에 사용할 AWS 리전을 지정합니다.

service 섹션은 전체 Collector 서비스의 동작을 정의합니다.

service:
  extensions: [sigv4auth]
  pipelines:
    metrics:
      receivers:
        - prometheus
      exporters: [prometheusremotewrite]

extensions: sigv4auth 확장을 사용하도록 설정합니다.

pipelines:

  • metrics 파이프라인은 메트릭을 수집하고 내보내는 구성입니다.
  • receivers는 Prometheus 수집기를 사용하고, exporters는 수집된 메트릭을 prometheusremotewrite 엔드포인트로 내보냅니다.

아래는 전체 구성파일 예시 입니다.

receivers:
  prometheus:
    config:
      global:
        scrape_interval: 60s
      scrape_configs:
        - job_name: 'custom_exporter'
          scrape_interval: 1m
          ec2_sd_configs:
            - region: "ap-northeast-2"
              port: 8080
              filters:
               - name: tag:exporter
                 values:
                  - custom
        - job_name: node
          ec2_sd_configs:
            - region: "ap-northeast-2"
              port: 9100
              filters:
               - name: tag:service_name
                 values:
                  - node_exporter
          relabel_configs:
            - source_labels:
                - __meta_ec2_instance_id
              target_label: instance_id
exporters:
  prometheusremotewrite:
    endpoint: "https://aps-workspaces.ap-northeast-2.amazonaws.com/workspaces/ws-*****-b155-b012e6f4596a/api/v1/remote_write"
    auth:
      authenticator: sigv4auth
extensions:
  sigv4auth:
    region: "ap-northeast-2"
service:
  extensions: [sigv4auth]
  pipelines:
    metrics:
      receivers:
        - prometheus
      exporters: [prometheusremotewrite]

ADOT Collector를 관리하기 위해 제공되는 제어 스크립트를 사용하여 Collector를 시작, 중지 및 상태를 확인할 수 있습니다. 위와 같은 구성 파일(config.yaml)이 없으면 기본 설정이 적용됩니다.

$ sudo /opt/aws/aws-otel-collector/bin/aws-otel-collector-ctl -c </path/config.yaml> -a start
$ sudo /opt/aws/aws-otel-collector/bin/aws-otel-collector-ctl  -a stop
$ sudo /opt/aws/aws-otel-collector/bin/aws-otel-collector-ctl  -a status

단계3: AMG 대시보드로 Custome Metric 시각화

AMP 를AMG의 데이터 소스로 등록하고, 커스텀 메트릭을 시각화하는 방법을 설명합니다.

기본적으로 AMG의 워크스페이스를 생성할 때 그림과 같이 데이터 원본을 지정하게 되면 다음 JSON형태의 정책을 기반으로 AMP에 접근하는 IAM 역할이 생성됩니다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "aps:ListWorkspaces",
                "aps:DescribeWorkspace",
                "aps:QueryMetrics",
                "aps:GetLabels",
                "aps:GetSeries",
                "aps:GetMetricMetadata"
            ],
            "Resource": "*"
        }
    ]
}

AMG 워크스페이스에 로그인하고 이제 AMP 데이터 소스가 설정되었으므로, 커스텀 메트릭을 시각화하는 대시보드를 생성합니다.



  1. Grafana 대시보드로 이동하여 상단 메뉴에서 “+” 아이콘을 클릭하고, “Create Dashboard”를 선택합니다.
  2. 새로운 패널을 추가하려면 “Add new panel” 버튼을 클릭합니다.
  3. Data Source를 “Prometheus ws-****” 로 시작되는 기존 생성한 AMP 워크스페이스를 선택합니다.
  4. 패널 편집 화면에서 다음을 설정합니다:
    • Query: Prometheus 쿼리(PromQL)를 입력합니다. 예를 들어, api_requests_total 메트릭을 시각화하려면 api_requests_total{} 쿼리를 입력합니다.
    • Visualization: 시각화 타입을 “Time series”로 선택합니다.
  5. 패널 설정을 완료한 후 “Apply” 버튼을 클릭합니다.

적용이 완료되면 다음과 같은 커스텀 메트릭 대시보드를 확인할 수 있습니다.

PromQL은 다양한 함수들을 제공하는데 , 범위 벡터 (Range Vector) 연산 함수는 특정 시간 범위 내의 시계열 데이터 집합을 처리하여 메트릭의 변화를 분석하는 데 유용하게 사용할 수 있습니다. 다음으로 메트릭을 PromQL 범위 벡터 연산 함수를 사용하여 유의미한 변화 추세를 확인하는 대시보드를 추가해보겠습니다. 아래는 자주 사용되는 함수로 메트릭을 확인해보겠습니다.

PromQL irate 함수는 범위 벡터에서 짧은 인터벌 사이의 시작점과 끝점의 차이의 초당 증가율을 계산합니다. irate(api_requests_total{}[10m]) 쿼리를 입력하여 적용합니다.

PromQL deriv 함수는 좀 더 긴 시간 동안 데이터가 어떻게 변화하는지를 측정하는 미분값(기울기)을 계산합니다. deriv(api_requests_total{}[10m]) 쿼리를 입력하여 적용합니다.

PromQL idelta 함수는 범위 벡터에서 짧은 인터벌 사이의 변화량을 제공합니다. idelta(api_requests_total{}[10m]) 쿼리를 입력하여 적용합니다.

범위 벡터 (Range Vector) 연산 함수를 아래 그림과 같이 각 함수를 적용한 패널들을 생성하고, 이를 하나의 대시보드로 등록하면 새롭게 생성한 커스텀 메트릭의 변화를 다각도록 파악할 수 있으며, 효율적인 모니터링과 문제 확인에 큰 도움이 될 수 있습니다.

결론

이 블로그에서는 Prometheus 클라이언트 라이브러리를 사용하여 커스텀 메트릭을 생성하고, Amazon Managed Service for Prometheus (AMP)와 Amazon Managed Service for Grafana (AMG)를 통해 이러한 메트릭을 수집, 가공, 시각화하는 방법을 설명했습니다. 특히, Prometheus의 데이터 수집 방식, AWS Distro for OpenTelemetry Collector를 활용한 메트릭 전송, 그리고 AMG를 통한 대시보드 구성 과정을 다루었습니다.

또한 PromQL에서 제공하는 범위 벡터 연산 함수를 적용하여 커스텀 메트릭의 변화를 추가적으로 분석하고 대시보드를 구성할 수 있었습니다.

완전 관리형 방식인 AMP, AMG를 사용하게 되면 Prometheus와 Grafana의 오픈 소스 생태계의 도구와 워크플로우를 지속적으로 사용할 수 있으면서도 관찰 가능성 플랫폼을 관리하는 부담을 줄일 수 있습니다. 이를 통해 데이터를 수집, 처리, 시각화하는 과정에만 집중할 수 있기 때문에 조직은 보다 효율적인 모니터링 환경을 구축하는 데 도움을 받을 수 있습니다.

Jinwoong Kim

Jinwoong Kim

김진웅 Cloud Architect는 인프라 엔지니어로 시작해서 클라우드 엔지니어, DevOps, SRE 업무를 거쳐 현재 Korea Professional Services 팀에서 고객의 모더나이제이션 여정을 도와드리는 클라우드 아키텍트 업무를 수행하고 있습니다. 관측 가능성과 컨테이너 기술에 관심이 많습니다.

minhyeon park

minhyeon park

박민현 어플리케이션 아키텍트는 Professional Services 팀에서 고객의 서비스를 클라우드에 이관하고 현대화 하는 과정에서 생기는 어려움을 해결하고 최적화된 아키텍처를 찾아드리고 있습니다.