콘텐츠로 이동

OpenTelemetry to Guance


앞선 두 편에서는 OpenTelemetry를 기반으로 관측 가능성을 구현하는 방법을 각각 소개하고 시연했습니다.

OpenTelemetry to Jaeger, Grafana, ELK

클래식한 관측 가능성 아키텍처로서, 로그는 ELK, trace는 Jaeger와 같은 APM, 메트릭은 Prometheus에 저장하고 Grafana로 시각화하는 등 데이터 유형별로 다른 플랫폼에 저장합니다.

OpenTelemetry to Grafana

Grafana Tempo와 Loki를 결합하면 로그 트레이스 상황을 직관적으로 확인할 수 있습니다. 하지만 Loki의 특성상 대규모 프로덕션 시스템에 적합한 로그 처리 및 분석 기능을 제공하기는 어렵습니다. 로그 트레이스는 관측 가능성의 일부일 뿐이며, 로그 트레이스 조회만으로는 대부분의 문제를 해결할 수 없습니다. 특히 마이크로서비스 및 클라우드 네이티브 아키텍처 시대에는 문제의 다양성으로 인해 여러 측면을 종합적으로 분석해야 합니다. 예를 들어 사용자 액세스 지연은 반드시 프로그램 문제가 아니라 현재 시스템 네트워크, CPU 등 다른 종합적인 요인으로 인해 발생할 수 있습니다. 사용자가 멀티 클라우드를 사용하는 경우 Grafana는 비즈니스 발전을 효과적으로 지원할 수 없습니다.

Guance

는 메트릭 데이터, 로그 데이터, APM, RUM, 인프라스트럭처, 컨테이너, 미들웨어, 네트워크 성능 등 다양한 데이터를 통합 수집 및 관리하는 플랫폼입니다. Guance을 사용하면 로그 트레이스 간의 관측뿐만 아니라 애플리케이션을 전방위적으로 관측할 수 있습니다. Guance에 대한 자세한 정보는 제품 장점을 참조하세요.

DataKit은 Guance의 프런트엔드 게이트웨이입니다. 데이터를 Guance으로 전송하려면 DataKit을 올바르게 구성해야 합니다. 또한 DataKit을 사용하면 다음과 같은 장점이 있습니다.

  1. 호스트 환경에서는 각 호스트에 datakit이 있으며, 데이터는 먼저 로컬 datakit으로 전송되어 캐싱, 전처리된 후 보고됩니다. 이를 통해 네트워크 지터를 방지하고 에지 처리 기능을 제공하여 백엔드 데이터 처리의 부담을 완화합니다.

  2. Kubernetes 환경에서는 각 노드에 DataKit의 DaemonSet이 있습니다. k8s의 local traffic 메커니즘을 활용하여 각 노드의 파드에 있는 데이터가 먼저 로컬 노드의 DataKit으로 전송되도록 합니다. 이를 통해 네트워크 지터를 방지하고 APM 데이터에 파드 및 노드 레이블을 추가하여 분산 환경에서 위치 파악을 용이하게 합니다.

DataKit은 OTLP 프로토콜을 수신하므로 collector를 거치지 않고 직접 DataKit으로 전송하거나, collector의 exporter를 OTLP(DataKit)으로 설정할 수 있습니다.

아키텍처

image.png

아키텍처는 여전히 두 가지 방식을 제공합니다.

datakit은 다양한 방식으로 로그를 수집할 수 있습니다. 이번 모범 사례에서는 주로 소켓 방식을 통해 로그를 수집합니다. Spring Boot 애플리케이션은 주로 Logback-logstash를 통해 로그를 datakit으로 푸시합니다.

방식 1

  1. 애플리케이션 server와 client는 metric, trace 데이터를 otlp-exporter를 통해 otel-collector로 푸시합니다.

  2. front-app은 프런트엔드 트레이스로, 트레이스 정보를 otel-collector로 푸시하고 애플리케이션 서비스 API에 액세스합니다.

  3. otel-collector는 데이터를 수집 및 변환한 후 metric과 trace 데이터를 otlp-exporter를 통해 datakit으로 전송합니다.

  4. 동시에 애플리케이션 server와 client는 logs를 datakit으로 푸시합니다.

Exporter

otel-collector에는 1개의 exporter인 otlpExporter가 구성되어 있습니다.

  otlp:
    endpoint: "http://192.168.91.11:4319"
    tls:
      insecure: true
    compression: none # gzip 사용 안 함

매개변수 설명

endpoint : "http://192.168.91.11:4319" # 현재 datakit opentelemetry 수집기 주소를 입력하며, 프로토콜은 GRPC입니다.

tls.insecure : true # tls 보안 검증 비활성화

compression: none # gzip 사용 안 함, 기본값은 활성화

참고

모든 애플리케이션이 동일한 머신(IP: 192.168.91.11)에 배포되어 있습니다. 애플리케이션과 일부 미들웨어가 별도로 분리 배포된 경우 해당 IP를 수정해야 합니다. 클라우드 서버인 경우 관련 포트를 개방하여 액세스 실패를 방지해야 합니다.

방식 2

방식 2는 기본적으로 otel-collector를 DataKit으로 대체하는 것입니다.

백엔드 server와 client를 시작할 때 otel.exporter.otlp.endpoint 주소를 수정하여 datakit을 직접 가리키도록 합니다.

-Dotel.exporter.otlp.endpoint=http://192.168.91.11:4319

프런트엔드 변경 사항

const otelExporter = new OTLPTraceExporter({
  // optional - url default value is http://localhost:55681/v1/traces
  url: 'http://192.168.91.11:9529/otel/v1/trace',
  headers: {},
});

DataKit 설치 및 구성

DataKit 설치

OpenTelemetry 수집기 활성화

OpenTelemetry 수집기 연동 문서를 참조하세요.

다음 매개변수 조정

[inputs.opentelemetry.grpc] 매개변수 설명

  • trace_enable: true # grpc trace 활성화
  • metric_enable: true # grpc metric 활성화
  • addr: 0.0.0.0:4319 # 포트 열기

DataKit 재시작

datakit service restart

로그 수집 활성화

  1. Logging 플러그인 활성화 및 Sample 파일 복사
cd /usr/local/datakit/conf.d/log
cp logging.conf.sample logging-socket-4560.conf
  1. logging-socket-4560.conf 수정
[[inputs.logging]]
  ## required
#  logfiles = [
#    "/var/log/syslog",
#    "/var/log/message",
#  ]

  sockets = [
   "tcp://0.0.0.0:4560"
  ]

  ## glob filteer
  ignore = [""]

  ## your logging source, if it's empty, use 'default'
  source = "otel"

  ## add service tag, if it's empty, use $source.
  service = "otel"

  ## grok pipeline script path
  pipeline = "log_socket.p"

  ## optional status:
  ##   "emerg","alert","critical","error","warning","info","debug","OK"
  ignore_status = []

  ## optional encodings:
  ##    "utf-8", "utf-16le", "utf-16le", "gbk", "gb18030" or ""
  character_encoding = ""

  ## The pattern should be a regexp. Note the use of '''this regexp'''
  ## regexp link: https://golang.org/pkg/regexp/syntax/#hdr-Syntax
  # multiline_match = '''^\S'''

  ## removes ANSI escape codes from text strings
  remove_ansi_escape_codes = false

  [inputs.logging.tags]
  # some_tag = "some_value"
  # more_tag = "some_other_value"

매개변수 설명

  • sockets # 소켓 정보 구성
  • pipeline: log_socket.p # 로그 파싱

  • pipeline 구성

cd pipeline vim log_socket.p

json(_,message,"message")
json(_,class,"class")
json(_,serverName,"service")
json(_,thread,"thread")
json(_,severity,"status")
json(_,traceId,"trace_id")
json(_,spanId,"span_id")
json(_,`@timestamp`,"time")
set_tag(service)
default_time(time)
  1. DataKit 재시작
datakit --restart

메트릭 수집 활성화

  1. prom 플러그인 활성화 및 Sample 파일 복사
cd /usr/local/datakit/conf.d/prom
cp prom.conf.sample prom-otel.conf
  1. prom-otel.conf 수정
[[inputs.prom]]
  ## Exporter URLs
  urls = ["http://127.0.0.1:8888/metrics"]

  ## 忽略对 url 的请求错误
  ignore_req_err = false

  ## 采集器别名
  source = "prom"

  ## 采集数据输出源
  # 配置此项,可以将采集到的数据写到本地文件而不将数据打到中心
  # 之后可以直接用 datakit --prom-conf /path/to/this/conf 命令对本地保存的指标集进行调试
  # 如果已经将 url 配置为本地文件路径,则 --prom-conf 优先调试 output 路径的数据
  # output = "/abs/path/to/file"

  ## 采集数据大小上限,单位为字节
  # 将数据输出到本地文件时,可以设置采集数据大小上限
  # 如果采集数据的大小超过了此上限,则采集的数据将被丢弃
  # 采集数据大小上限默认设置为32MB
  # max_file_size = 0

  ## 指标类型过滤, 可选值为 counter, gauge, histogram, summary, untyped
  # 默认只采集 counter 和 gauge 类型的指标
  # 如果为空,则不进行过滤
  metric_types = []

  ## 指标名称筛选:符合条件的指标将被保留下来
  # 支持正则,可以配置多个,即满足其中之一即可
  # 如果为空,则不进行筛选,所有指标均保留
  # metric_name_filter = ["cpu"]

  ## 指标集名称前缀
  # 配置此项,可以给指标集名称添加前缀
  measurement_prefix = ""

  ## 指标集名称
  # 默认会将指标名称以下划线"_"进行切割,切割后的第一个字段作为指标集名称,剩下字段作为当前指标名称
  # 如果配置measurement_name, 则不进行指标名称的切割
  # 最终的指标集名称会添加上measurement_prefix前缀
  # measurement_name = "prom"

  ## 采集间隔 "ns", "us" (or "µs"), "ms", "s", "m", "h"
  interval = "10s"

  ## 过滤 tags, 可配置多个tag
  # 匹配的tag将被忽略
  # tags_ignore = ["xxxx"]

  ## TLS 配置
  tls_open = false
  # tls_ca = "/tmp/ca.crt"
  # tls_cert = "/tmp/peer.crt"
  # tls_key = "/tmp/peer.key"

  ## 自定义认证方式,目前仅支持 Bearer Token
  # token 和 token_file: 仅需配置其中一项即可
  # [inputs.prom.auth]
  # type = "bearer_token"
  # token = "xxxxxxxx"
  # token_file = "/tmp/token"

  ## 自定义指标集名称
  # 可以将包含前缀 prefix 的指标归为一类指标集
  # 自定义指标集名称配置优先 measurement_name 配置项
  #[[inputs.prom.measurements]]
  #  prefix = "cpu_"
  #  name = "cpu"

  # [[inputs.prom.measurements]]
  # prefix = "mem_"
  # name = "mem"

  ## 重命名 prom 数据中的 tag key
    [inputs.prom.tags_rename]
        overwrite_exist_tags = false
        [inputs.prom.tags_rename.mapping]
            # tag1 = "new-name-1"
            # tag2 = "new-name-2"
            # tag3 = "new-name-3"

  ## 自定义Tags
  [inputs.prom.tags]
  # some_tag = "some_value"
  # more_tag = "some_other_value"

매개변수 설명

  • urls # otel-collector 메트릭 URL
  • metric_types = []: 모든 메트릭 수집

  • DataKit 재시작

datakit --restart

OpenTelemetry-Collector 설치

소스 코드 주소

https://github.com/lrwh/observable-demo/tree/main/opentelemetry-collector-to-guance

otel-collector-config.yaml 구성

collector 구성을 추가하고, 1개의 receiver(otlp)와 4개의 exporter(prometheus, zipkin, jaeger, elasticsearch)를 구성합니다.

receivers:
  otlp:
    protocols:
      grpc:
      http:
        cors:
          allowed_origins:
            - http://*
            - https://*
exporters:
  otlp:
    endpoint: "http://192.168.91.11:4319"
    tls:
      insecure: true
    compression: none # gzip 사용 안 함

processors:
  batch:

extensions:
  health_check:
  pprof:
    endpoint: :1888
  zpages:
    endpoint: :55679

service:
  extensions: [pprof, zpages, health_check]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp]

docker-compose를 통해 otel-collector 설치

version: '3.3'

services:
    # Collector
    otel-collector:
        image: otel/opentelemetry-collector-contrib:0.51.0
        command: ["--config=/etc/otel-collector-config.yaml"]
        volumes:
            - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
        ports:
            - "1888:1888"   # pprof extension
            - "8888:8888"   # Prometheus metrics exposed by the collector
            - "8889:8889"   # Prometheus exporter metrics
            - "13133:13133" # health_check extension
            - "4350:4317"        # OTLP gRPC receiver
            - "55670:55679" # zpages extension
            - "4318:4318"

컨테이너 시작

docker-compose up -d

시작 상태 확인

docker-compose ps

image.png

Spring Boot 애플리케이션 연동 (APM & Log)

주로 Logstash-logback에서 제공하는 소켓 방식을 통해 로그를 Logstash에 업로드하며, 코드 일부를 수정해야 합니다.

1. 프로젝트 Maven에 Logstash-logback 추가

<dependency>
  <groupId>net.logstash.logback</groupId>
  <artifactId>logstash-logback-encoder</artifactId>
  <version>7.0.1</version>
</dependency>

2. logback-logstash.xml 추가

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
    <!-- 部分参数需要来源于properties文件 -->
    <springProperty scope="context" name="logName" source="spring.application.name" defaultValue="localhost.log"/>
    <!-- 配置后可以动态修改日志级别-->
    <jmxConfigurator />
    <property name="log.pattern" value="%d{HH:mm:ss} [%thread] %-5level %logger{10} [traceId=%X{trace_id} spanId=%X{span_id} userId=%X{user-id}] %msg%n" />

    <springProperty scope="context" name="logstashHost" source="logstash.host" defaultValue="logstash"/>
    <springProperty scope="context" name="logstashPort" source="logstash.port" defaultValue="4560"/>
    <!-- %m输出的信息,%p日志级别,%t线程名,%d日期,%c类的全名,,,, -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/${logName}/${logName}.log</file>    <!-- 使用方法 -->
        <append>true</append>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/${logName}/${logName}-%d{yyyy-MM-dd}.log.%i</fileNamePattern>
            <maxFileSize>64MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- LOGSTASH输出设置 -->
    <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <!-- 配置logStash 服务地址 -->
        <destination>${logstashHost}:${logstashPort}</destination>
        <!-- 日志输出编码 -->
        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <timestamp>
                    <timeZone>UTC+8</timeZone>
                </timestamp>
                <pattern>
                    <pattern>
                        {
                        "podName":"${podName:-}",
                        "namespace":"${k8sNamespace:-}",
                        "severity": "%level",
                        "serverName": "${logName:-}",
                        "traceId": "%X{trace_id:-}",
                        "spanId": "%X{span_id:-}",
                        "pid": "${PID:-}",
                        "thread": "%thread",
                        "class": "%logger{40}",
                        "message": "%message\n%exception"
                        }
                    </pattern>
                </pattern>
            </providers>
        </encoder>
        <!-- 保活 -->
        <keepAliveDuration>5 minutes</keepAliveDuration>
    </appender>

    <!-- 只打印error级别的内容 -->
    <logger name="net.sf.json" level="ERROR" />
    <logger name="org.springframework" level="ERROR" />

    <root level="info">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="LOGSTASH"/>
    </root>
</configuration>

3. application-logstash.yml 추가

logstash:
  host: localhost
  port: 4560
logging:
  config: classpath:logback-logstash.xml

4. 재패키징

mvn clean package -DskipTests

5. 서비스 시작

java -javaagent:opentelemetry-javaagent-1.13.1.jar \
-Dotel.traces.exporter=otlp \
-Dotel.exporter.otlp.endpoint=http://localhost:4350 \
-Dotel.resource.attributes=service.name=server,username=liu \
-Dotel.metrics.exporter=otlp \
-Dotel.propagators=b3 \
-jar springboot-server.jar --client=true \
--spring.profiles.active=logstash \
--logstash.host=192.168.91.11 \
--logstash.port=4560
java -javaagent:opentelemetry-javaagent-1.13.1.jar \
-Dotel.traces.exporter=otlp \
-Dotel.exporter.otlp.endpoint=http://localhost:4350 \
-Dotel.resource.attributes=service.name=client,username=liu \
-Dotel.metrics.exporter=otlp \
-Dotel.propagators=b3 \
-jar springboot-client.jar \
--spring.profiles.active=logstash \
--logstash.host=localhost \
--logstash.port=4560

JS 연동 (RUM)

소스 코드 주소

https://github.com/lrwh/observable-demo/tree/main/opentelemetry-js

OTLPTraceExporter 구성

const otelExporter = new OTLPTraceExporter({
  // optional - url default value is http://localhost:55681/v1/traces
  url: 'http://192.168.91.11:4318/v1/traces',
  headers: {},
});

여기서 url은 otel-collector의 otlp 수신 주소(HTTP 프로토콜)입니다.

server_name 구성

const providerWithZone = new WebTracerProvider({
      resource: new Resource({
        [SemanticResourceAttributes.SERVICE_NAME]: 'front-app',
      }),
    }
);

설치

npm install

시작

npm start

기본 포트는 8090입니다.

APM과 RUM 연동

APM과 RUM은 주로 헤더 매개변수를 통해 연동됩니다. 일관성을 유지하기 위해 통일된 Propagator를 구성해야 합니다. 여기서 RUM은 B3를 사용하므로 APM도 B3를 구성해야 합니다. APM 시작 매개변수에 -Dotel.propagators=b3를 추가하기만 하면 됩니다.

APM과 Log 연동

APM과 Log는 주로 로그에 traceId와 spanId를 심어서 연동합니다. 로그 연동 방식에 따라 심는 위치가 다릅니다.

Guance

프런트엔드 URL에 액세스하여 trace 정보를 생성합니다.

image.png

로그 탐색기

guance-log.gif

분산 추적 (애플리케이션 성능 모니터링(APM))

guance-trace.gif

트레이스에서 해당 로그 확인

guance-trace-log.gif

애플리케이션 메트릭

애플리케이션 메트릭은 모두 measurement가 otel-service인 메저먼트에 저장됩니다.

guance-metrics.gif

Otelcol 메트릭

guance-otelcol-metrics.gif

Otelcol 통합 뷰

Otelcol 통합 뷰

문서 평가

이 페이지가 도움이 되었나요?