Chapter 6.7 오픈서치로 구현하는 o11y

이번에는 사용한 관찰가능성입니다. 유튜브 동영상을 제공합니다.

이제까지 그라파나를 사용해서 관찰가능성을 구현하였습니다. 하지만 또 다른 오픈소스인 오픈서치를 사용해서 관찰가능성을 구현해 보도록 하겠습니다.

제공되는 소스는 자바로 개발된 analytics-service를 제외하면, 파이썬으로 개발된 모든 서비스는 수동 계측을 합니다. analytics-service는 오픈텔레메트리 자바에이전트로 자동 계측됩니다.

관찰가능성 어플리케이션은 에러를 자동적으로 생성합니다. 이는 유용한 기능입니다. 에러가 발생하지 않는다면, 근본 원인 분석이 필요하지 않기 때문입니다. 소스 내 에러 비율을 조절함으로써, 발생하는 에러와 문제점을 분석할 수 있습니다.

이번 글을 통해서 배울 점은 아래와 같습니다.

  1. 오픈텔레메트리 수동 계측과 자동 계측을 사용해서 마이크로서비스를 구축합니다.
  2. 오픈서치를 사용해서 메트릭, 로그, 추적을 수집하고 관찰가능성을 구현합니다. 또한 오픈서치로 구현하는 상관 관계도 이해합니다.

오픈서치를 사용한 관찰가능성

오픈서치는 단순한 로그 오픈소스가 아닙니다. 물론 로그에서 시작했고, 로그에 유용한 기능이 많이 있습니다. 하지만 근래 들어 다양한 기술과 결합해서 새로운 가능성을 보여주고 있습니다.

  1. 오픈서치를 사용한 관찰가능성
  2. AIOps, 데이터 분석
  3. 이상탐지와 SIEM(보안 정보 및 이벤트 관리)
  4. ChatGPT, 벡터 데이터베이스
  5. 머신러닝과 다양한 알고리즘

개인적으로도 오픈서치는 기대되는 오픈소스임에 틀림이 없습니다.

AWS에서 관리하는 오픈서치는 계속 기능이 향상되고 있으며, 성능은 엘라스틱서치에 비교해서 더 뛰어난 것으로 알려져 있습니다.

하지만 엘라스틱서치에 비교해서 오픈서치를 운영환경에 적용 시에 자료가 아직 충분하지 않습니다. 아래의 데모는 추적, 메트릭, 로그가 마이크로서비스와 잘 결합되어 있으며, 오픈서치를 사용해서 관찰가능성을 데모하기에 좋은 예제입니다.

먼저 시스템을 구성합니다. 아래의 순서로 진행합니다.

  1. 미니 쿠베를 시작
  2. 오픈서치를 설치
  3. 오픈서치 대시보드를 설치
  4. 데이터 프리페리를 설치
  5. 플루언트 비트를 설치
  6. 오픈텔레메트리 컬렉터를 설치
  7. 9개의 마이크로서비스를 설치

하나씩 진행해 보도록 하겠습니다.

o11y 구성

미니쿠베에서 쿠버네티스 1.20을 시작합니다.

minikube start --vm-driver=none --kubernetes-version v1.23.0 --memory=12000 --cpus=4

현재 설치 스크립트는 일반적인 YAML로 제공됩니다. 깃에서 다운받으십시요. 추후에는 헬름 차트를 제공하고, 자동화할 것입니다. 이러한 과정은 곧 다룰 내용입니다.

오픈서치를 설치합니다.

kubectl apply -f 01-config.yaml
kubectl apply -f 02-deployment.yaml

오픈서치 대시보드를 설치합니다.

kubectl apply -f 12-deployment.yaml

데이터 프레페르를 설치합니다.

kubectl apply -f 21-config.yaml 
kubectl apply -f 22-deployment.yaml

메트릭을 위한 오픈텔레메트리 컬렉터를 설치합니다.

kubectl apply -f 30-roles.yaml
kubectl apply -f 31-config.yaml
kubectl apply -f 32-deployment.yaml

추적을 위한 오픈텔레메트리 컬렉터와 어플리케이션을 설치합니다.

chmod 755 apply-k8s-manifests.sh
./apply-k8s-manifests.sh

도커 허브에 어플리케이션은 이미 등록되어 있습니다.

https://hub.docker.com/repositories/yohaim1511

로그 파이프라인을 설치합니다.

설치된 플루언트비트는 오픈서치와 직접적으로 연계되지 않고, 데이터 프리페르를 통해서 연계됩니다. 플루언트비트의 OUTPUT에 설정된 구성을 확인하실 수 있습니다.

일반적으로 오픈서치는 플루언트비트를 사용해서 로그와 메트릭을 수집합니다. 데모는 메트릭과 이벤트를 수집하는 파이프라인과 로그를 수집하는 파이프라인을 분리하였습니다.

  1. 메트릭을 수집하는 파이프라인은 데이터 프리페르를 사용해서 오픈서치에 적재합니다.
  2. 로그를 수집하는 파이프라인은 플루언트비트와 데이터 프리페르를 사용해서 오픈서치에 적재합니다.

아래처럼 로그를 수집하도록 기능을 쉽게 추가할 수 있습니다

./apply-k8s-manifests.sh

아래처럼 오픈서치에서도 상관 관계를 구성할 수 있습니다. 하지만 그라파나와 비교하면, 상관 관계 기능은 부족합니다. 데이터독, 그라파나 화면에 익숙하다면 오픈서치의 화면이 부족하다고 느낄 것 입니다. 사용 목적이 다르다는 것을 이해해야 합니다.

오픈서치에서 결과를 확인

그라파나와 오픈서치는 프로세스도 다릅니다. 다음 기회에 오픈서치 상관 관계에 대해서 자세히 설명하도록 하겠습니다.

이를 통해 3개의 파이프라인을 구성하였습니다.

  1. 오픈텔레메트리 컬렉터를 사용한 추적 파이프라인
  2. 오픈텔레메트리 컬렉터를 사용한 메트릭 파이프라인
  3. 플루언트비트를 사용한 로그 파이프라인

추적의 결과를 확인합니다.

로그의 결과를 확인합니다.

메트릭의 결과를 확인합니다. 별도의 익스포터를 사용하지 않습니다. 오픈텔레메트리 컬렉터를 통해서 메트릭을 수집하고, 데이터 프리페르를 거쳐 오픈서치에 적재하고 있습니다.

프로메테우스에 메트릭을 적재하면 항상 메모리와 디스크 문제로 곤란하게 됩니다. 오픈서치로 보다 확장성있게 관리하는 것도 좋은 방법입니다.

상관 관계를 구현하는 화면입니다.

상관 관계를 구현하려면 그라파나를 사용하는 것이 좋습니다. 만약 ChatGPT, AIOps, 데이터분석 등이 목적이라면 오픈서치를 사용하는 것을 권장합니다.

그라파나 대시보드를 사용하면, 예거와 로키를 사용해서 로그와 추적 간에 상관 관계를 구현하는 것이 가능합니다. 로키는 다양한 추적 솔루션의 추적 아이디와 연계할 수 있습니다.

기술 아키텍처

The following are the backend services running on different ports:

  1. inventoryService.py
  2. databaseService.py
  3. paymentService.py
  4. authenticationService.py
  5. recommendationService.py
  6. orderService.py
  7. analytics-service
  8. otel-collector

The following are the database/storage we run on different ports.

  1. mysql
  2. opensearch

The client makes API calls that produces the APM data that falls into the following trace groups:

  1. load_main_screen
  2. client_checkout
  3. client_create_order
  4. client_cancel_order
  5. client_delivery_status
  6. client_pay_order

Correspondingly, on the server side, the API calls are as follows

  1. /server_request_login (autheticationService:8085) -> /recommend (recommendationService:8086) -> /read_inventory
  2. (inventoryService:8082) -> /get_inventory (databaseService:8083) -> mysql
  3. /checkout (paymentService:8084) -> /update_inventory (inventoryService:8082) -> /update_item (databaseService:8083) -> mysql
  4. /update_order (orderService:8088) -> /add_item_to_cart or /remove_item_from_cart (databaseService:8083) -> mysql
  5. /clear_order (orderService:8088) -> /cart_empty (databaseService:8083) -> mysql
  6. /get_order (orderService:8088) -> /get_cart (databaseService:8083) -> mysql
  7. /pay_order (orderService:8088) -> /cart_sold (databaseService:8083) -> mysql

Each API call in the chains above calls /logs (analytics-service:8087) in the analytics service.

소스 설명

본 데모는 파이썬, 자바로 개발되었으며, 다수의 마이크로서비스로 구성되었습니다.

라이브러리를 설치합니다.

opentelemetry-exporter-otlp==1.9.1
opentelemetry-instrumentation-flask==0.28b1
opentelemetry-instrumentation-mysql==0.28b1
opentelemetry-instrumentation-requests==0.28b1
opentelemetry-instrumentation-logging==0.28b1

수동 계측에서 개발자는 추적 코드를 애플리케이션에 추가해야 합니다. 오픈텔레메트리 추적은 trace, span, attributes, event를 정의하는 것이 필요합니다.

Import dependent packages in the code.

from opentelemetry import trace
from opentelemetry.instrumentation.logging import LoggingInstrumentor
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.requests import RequestsInstrumentor
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
ConsoleSpanExporter,
SimpleSpanProcessor,
)

TraceProvider를 생성합니다.

trace.set_tracer_provider(
TracerProvider(
resource=Resource.create(
{
"service.name": "payment",
"service.instance.id": str(id(app)),
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.language": "python",
"telemetry.sdk.version": pkg_resources.get_distribution("opentelemetry-sdk").version,
"host.hostname": socket.gethostname(),
}
)
)
)
tracerProvider = trace.get_tracer_provider()
tracer = tracerProvider.get_tracer(__name__)
tracerProvider.add_span_processor(
SimpleSpanProcessor(ConsoleSpanExporter())
)
otlp_exporter = OTLPSpanExporter(endpoint="{}:55680".format(OTLP), insecure=True)
tracerProvider.add_span_processor(
SimpleSpanProcessor(otlp_exporter)
)

추적과 로그의 상관 관계를 구현하기 위해서 trace _id, span _id, resource.service.name이 필요합니다. LoggingInstrumentor를 사용하여 어플리케이션을 계측합니다.

LoggingInstrumentor().instrument(set_logging_format=True)

컬렉터는 아래처럼 출력합니다.

2022-12-07 15:38:07,241 INFO [__main__] [orderService.py:151] [trace_id=5a02ffc6d0be3cd83d15a96834c94c6e span_id=02a69f453a0ca4b2 resource.service.name=order] - Order - Update operation successful

플라스크 어플리케이션 내 웹 요청을 추적하기 위해서 FlaskInstrumentor를 사용합니다.

FlaskInstrumentor().instrument_app(app)

파이썬 requests 라이브러리에 의해서 Trace HTTP 요청이 생성됩니다.

 with tracer.start_as_current_span("checkout"):

소스는 아래와 같습니다.

@app.route("/checkout", methods=["POST", "GET"])
def payment():
errorRate = random.randint(0,99)
if errorRate < ERROR_RATE_THRESHOLD:
logs('Payment', 'Checkout operation failed - Service Unavailable: 503')
logger.error('Payment - Checkout operation failed - Service Unavailable: 503')
raise Error('Checkout Failed - Service Unavailable', status_code=503)
else:
with tracer.start_as_current_span("checkout"):
rawData = request.form
data = {}
for itemId in rawData.keys():
data[itemId] = sum([-val for val in rawData.getlist(itemId, type=int)])

soldInventorySession = requests.Session()
soldInventorySession.mount("http://", HTTPAdapter(max_retries=retry_strategy))
soldInventoryUpdateResponse = soldInventorySession.post(
"http://{}:80/update_inventory".format(INVENTORY),
data=data,
)
soldInventorySession.close()
if soldInventoryUpdateResponse.status_code == 200:
logs('Payment', 'Customer successfully checked out cart')
logger.info('Payment - Customer successfully checked out cart')
return "success"
else:
failedItems = soldInventoryUpdateResponse.json().get("failed_items")
return make_response(
"Failed to checkout following items: {}".format(','.join(failedItems)),
soldInventoryUpdateResponse.status_code)

어플리케이션 실행 방법

어플리케이션은 쿠버네티스에 전개됩니다.

  1. 오픈텔레메트리 추적 API를 사용합니다.
  2. 플루언트비트를 사용해서 로그를 수집합니다.
  3. 메트릭은 span metrics를 사용해서 생성합니다.
kubectl apply -f .

오픈텔레메트리 컬렉터 구성파일입니다.

apiVersion: v1
kind: ConfigMap
metadata:
name: otel-collector-config
namespace: otel-collector
data:
otel-collector-config.yml: |
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:55680

exporters:
logging:

service:
pipelines:
traces:
receivers: [otlp]
exporters: [logging]

컬렉터를 실행합니다.

root@philip-virtual-machine:~/observability-with-amazon-opensearch/sample-apps/02-otel-collector/kubernetes# kubectl logs otel-collector-6cfbc5ffd-d4kt5 -n otel-collector
2022-12-07T05:17:57.902Z info builder/exporters_builder.go:217 Ignoring exporter as it is not used by any pipeline {"kind": "exporter", "name": "otlp/data-prepper"}
2022-12-07T05:17:57.903Z info builder/exporters_builder.go:255 Exporter was built. {"kind": "exporter", "name": "logging"}
2022-12-07T05:17:57.903Z info builder/pipelines_builder.go:223 Pipeline was built. {"name": "pipeline", "name": "traces"}
2022-12-07T05:17:57.903Z info builder/receivers_builder.go:226 Receiver was built. {"kind": "receiver", "name": "otlp", "datatype": "traces"}
2022-12-07T05:17:57.903Z info service/service.go:82 Starting extensions...
2022-12-07T05:
17:57.903Z info service/service.go:87 Starting exporters...
2022-12-07T05:17:57.903Z info builder/exporters_builder.go:40 Exporter is starting... {"kind": "exporter", "name": "logging"}
2022-12-07T05:17:57.903Z info builder/exporters_builder.go:48 Exporter started. {"kind": "exporter", "name": "logging"}
2022-12-07T05:17:57.903Z info builder/exporters_builder.go:40 Exporter is starting... {"kind": "exporter", "name": "otlp/data-prepper"}
2022-12-07T05:17:57.903Z info builder/exporters_builder.go:48 Exporter started. {"kind": "exporter", "name": "otlp/data-prepper"}
2022-12-07T05:17:57.903Z info service/service.go:92 Starting processors...
2022-12-07T05:17:57.903Z info builder/pipelines_builder.go:54 Pipeline is starting... {"name": "pipeline", "name": "traces"}
2022-12-07T05:17:57.903Z info builder/pipelines_builder.go:65 Pipeline is started. {"name": "pipeline", "name": "traces"}
2022-12-07T05:17:57.903Z info service/service.go:97 Starting receivers...
2022-12-07T05:17:57.903Z info builder/receivers_builder.go:68 Receiver is starting... {"kind": "receiver", "name": "otlp"}
2022-12-07T05:17:57.903Z info otlpreceiver/otlp.go:69 Starting GRPC server on endpoint 0.0.0.0:55680 {"kind": "receiver", "name": "otlp"}
2022-12-07T05:17:57.903Z info builder/receivers_builder.go:73 Receiver started. {"kind": "receiver", "name": "otlp"}
2022-12-07T05:17:57.903Z info service/telemetry.go:109 Setting up own telemetry...
2022-12-07T05:17:57.905Z info service/telemetry.go:129 Serving Prometheus metrics {"address": ":8888", "level": "basic", "service.instance.id": "35f1b51b-34e3-4c2b-a27a-2afc2c74fc9e", "service.version": "latest"}
2022-12-07T05:17:57.905Z info service/collector.go:248 Starting otelcol... {"Version": "0.46.0", "NumCPU": 8}
2022-12-07T05:17:57.905Z info service/collector.go:144 Everything is ready. Begin running and processing data.
2022-12-07T05:21:46.517Z INFO loggingexporter/logging_exporter.go:40 TracesExporter {"#spans": 1}
2022-12-07T05:21:46.519Z INFO loggingexporter/logging_exporter.go:40 TracesExporter {"#spans": 1}
2022-12-07T05:21:50.012Z INFO loggingexporter/logging_exporter.go:40 TracesExporter {"#spans": 2}
2022-12-07T05:21:58.924Z INFO loggingexporter/logging_exporter.go:40 TracesExporter {"#spans": 1}
2022-12-07T05:21:58.928Z INFO loggingexporter/logging_exporter.go:40 TracesExporter {"#spans": 1}
2022-12-07T05:22:00.029Z INFO loggingexporter/logging_exporter.go:40 TracesExporter {"#spans": 4}

아래의 화면을 통해서 주문을 생성할 수 있습니다.

첫 번째 화면은 inventory 서비스를 호출합니다.

/server_request_login (autheticationService:8085) -> /recommend (recommendationService:8086) -> /read_inventory

(inventoryService:8082) -> /get_inventory (databaseService:8083) -> mysql

payment 서비스를 호출합니다.

/pay_order (orderService:8088) -> /cart_sold (databaseService:8083) -> mysql

/pay_order (orderService:8088) -> /cart_sold (databaseService:8083) -> mysql

order 서비스를 호출합니다.

/get_order (orderService:8088) -> /get_cart (databaseService:8083) -> mysql

/clear_order (orderService:8088) -> /cart_empty (databaseService:8083) -> mysql

/update_order (orderService:8088) -> /add_item_to_cart or /remove_item_from_cart (databaseService:8083) -> mysql

/checkout (paymentService:8084) -> /update_inventory (inventoryService:8082) -> /update_item (databaseService:8083) -> mysql

order 서비스의 추적을 확인합니다.

root@philip-virtual-machine:~/observability-with-amazon-opensearch/sample-apps/02-otel-collector/kubernetes# kubectl logs order-service-85978784d4-gmmz8 -n order-service
2022-12-07 15:38:07,241 INFO [__main__] [orderService.py:151] [trace_id=5a02ffc6d0be3cd83d15a96834c94c6e span_id=02a69f453a0ca4b2 resource.service.name=order] - Order - Update operation successful
{
"name": "/pay_order",
"context": {
"trace_id": "0x5a02ffc6d0be3cd83d15a96834c94c6e",
"span_id": "0xf0f50e6abd19d781",
"trace_state": "[]"
},
"kind": "SpanKind.SERVER",
"parent_id": "0xa9459c72065bd9a6",
"start_time": "2022-12-07T15:38:05.933534Z",
"end_time": "2022-12-07T15:38:07.244902Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"http.method": "POST",
"http.server_name": "0.0.0.0",
"http.scheme": "http",
"net.host.port": 5000,
"http.host": "order-service.order-service.svc.cluster.local",
"http.target": "/pay_order",
"net.peer.ip": "172.17.0.1",
"http.user_agent": "python-requests/2.28.1",
"net.peer.port": 63213,
"http.flavor": "1.1",
"http.route": "/pay_order",
"http.status_code": 200
},
"events": [],
"links": [],
"resource": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.9.1",
"service.name": "order",
"service.instance.id": "140508135234528",
"host.hostname": "order-service-85978784d4-gmmz8"
}
}
2022-12-07 15:38:07,247 INFO [werkzeug] [_internal.py:224] [trace_id=0 span_id=0 resource.service.name=order] - 172.17.0.1 - - [07/Dec/2022 15:38:07] "POST /pay_order HTTP/1.1" 200 -
{
"name": "HTTP GET",
"context": {
"trace_id": "0x36e9805a068cd777a0e70c714f468bad",
"span_id": "0xa10dcb4429824c18",
"trace_state": "[]"
},
"kind": "SpanKind.CLIENT",
"parent_id": "0x408849eaf1a6e5eb",
"start_time": "2022-12-07T15:39:22.644454Z",
"end_time": "2022-12-07T15:39:23.037582Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"http.method": "GET",
"http.url": "http://database-service.database-service.svc.cluster.local:80/get_cart",
"http.status_code": 200
},
"events": [],
"links": [],
"resource": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.9.1",
"service.name": "order",
"service.instance.id": "140508135234528",
"host.hostname": "order-service-85978784d4-gmmz8"
}
}
{
"name": "HTTP POST",
"context": {
"trace_id": "0x36e9805a068cd777a0e70c714f468bad",
"span_id": "0x6aef2eed27b1f46e",
"trace_state": "[]"
},
"kind": "SpanKind.CLIENT",
"parent_id": "0x408849eaf1a6e5eb",
"start_time": "2022-12-07T15:39:23.127227Z",
"end_time": "2022-12-07T15:39:23.314716Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"http.method": "POST",
"http.url": "http://analytics-service.analytics-service.svc.cluster.local:80/logs",
"http.status_code": 200
},
"events": [],
"links": [],
"resource": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.9.1",
"service.name": "order",
"service.instance.id": "140508135234528",
"host.hostname": "order-service-85978784d4-gmmz8"
}
}
{
"name": "get_order",
"context": {
"trace_id": "0x36e9805a068cd777a0e70c714f468bad",
"span_id": "0x408849eaf1a6e5eb",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": "0x664f17989fb507e1",
"start_time": "2022-12-07T15:39:22.644084Z",
"end_time": "2022-12-07T15:39:23.317160Z",
"status": {
"status_code": "UNSET"
},
"attributes": {},
"events": [],
"links": [],
"resource": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.9.1",
"service.name": "order",
"service.instance.id": "140508135234528",
"host.hostname": "order-service-85978784d4-gmmz8"
}
}
{
"name": "/get_order",
"context": {
"trace_id": "0x36e9805a068cd777a0e70c714f468bad",
"span_id": "0x664f17989fb507e1",
"trace_state": "[]"
},
"kind": "SpanKind.SERVER",
"parent_id": "0xd4a9dfec1ee4ea3a",
"start_time": "2022-12-07T15:39:22.642380Z",
"end_time": "2022-12-07T15:39:23.319333Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"http.method": "GET",
"http.server_name": "0.0.0.0",
"http.scheme": "http",
"net.host.port": 5000,
"http.host": "order-service.order-service.svc.cluster.local",
"http.target": "/get_order",
"net.peer.ip": "172.17.0.1",
"http.user_agent": "python-requests/2.28.1",
"net.peer.port": 40210,
"http.flavor": "1.1",
"http.route": "/get_order",
"http.status_code": 200
},
"events": [],
"links": [],
"resource": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.9.1",
"service.name": "order",
"service.instance.id": "140508135234528",
"host.hostname": "order-service-85978784d4-gmmz8"
}
}
2022-12-07 15:39:23,316 INFO [__main__] [orderService.py:121] [trace_id=36e9805a068cd777a0e70c714f468bad span_id=408849eaf1a6e5eb resource.service.name=order] - Order - Read operation successful

inventory 서비스의 추적을 확인합니다.

root@philip-virtual-machine:~/observability-with-amazon-opensearch/sample-apps/02-otel-collector/kubernetes# kubectl logs inventory-service-654977b84-wbm5g -n inventory-service
2022-12-07 05:14:24,633 INFO [__main__] [inventoryService.py:81] [trace_id=0d7f3fec3f838c3767cafd037e498a92 span_id=81454f14a339569c resource.service.name=inventory] - Inventory - Read operation successful
{
"name": "read_inventory",
"context": {
"trace_id": "0x0d7f3fec3f838c3767cafd037e498a92",
"span_id": "0x81454f14a339569c",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": "0x774b122ccc76d1ef",
"start_time": "2022-12-07T05:10:12.300287Z",
"end_time": "2022-12-07T05:14:24.633695Z",
"status": {
"status_code": "UNSET"
},
"attributes": {},
"events": [],
"links": [],
"resource": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.9.1",
"service.name": "inventory",
"service.instance.id": "139750154663104",
"host.hostname": "inventory-service-654977b84-wbm5g"
}
}
{
"name": "/read_inventory",
"context": {
"trace_id": "0x0d7f3fec3f838c3767cafd037e498a92",
"span_id": "0x774b122ccc76d1ef",
"trace_state": "[]"
},
"kind": "SpanKind.SERVER",
"parent_id": "0x508ddacf5c82c463",
"start_time": "2022-12-07T05:10:12.298518Z",
"end_time": "2022-12-07T05:14:24.636148Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"http.method": "GET",
"http.server_name": "0.0.0.0",
"http.scheme": "http",
"net.host.port": 5000,
"http.host": "inventory-service.inventory-service.svc.cluster.local",
"http.target": "/read_inventory",
"net.peer.ip": "172.17.0.1",
"http.user_agent": "python-requests/2.28.1",
"net.peer.port": 13735,
"http.flavor": "1.1",
"http.route": "/read_inventory",
"http.status_code": 200
},
"events": [],
"links": [],
"resource": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.9.1",
"service.name": "inventory",
"service.instance.id": "139750154663104",
"host.hostname": "inventory-service-654977b84-wbm5g"
}
}

데이터베이스 서비스의 추적을 확인합니다.

root@philip-virtual-machine:~/observability-with-amazon-opensearch/sample-apps/02-otel-collector/kubernetes# kubectl logs database-service-54d9f5bb77-7894b -n database-service
2022-12-07 15:46:42,215 INFO [werkzeug] [_internal.py:224] [trace_id=0 span_id=0 resource.service.name=database] - 172.17.0.1 - - [07/Dec/2022 15:46:42] "POST /add_item_to_cart HTTP/1.1" 200 -
{
"name": "UPDATE",
"context": {
"trace_id": "0x2d6db793a08ce388e307746429b878ca",
"span_id": "0x9d3eea0e511ad79b",
"trace_state": "[]"
},
"kind": "SpanKind.CLIENT",
"parent_id": "0x763c35d48b48125f",
"start_time": "2022-12-07T15:46:42.562211Z",
"end_time": "2022-12-07T15:46:42.562742Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"db.system": "mysql",
"db.name": "APM",
"db.statement": "UPDATE Inventory_Items SET TotalQty=IF(TotalQty >= %(Qty)s, TotalQty - %(Qty)s, TotalQty) WHERE ItemId=%(ItemId)s",
"db.user": "root",
"net.peer.name": "mysql.mysql.svc.cluster.local",
"net.peer.port": 3306
},
"events": [],
"links": [],
"resource": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.9.1",
"service.name": "database",
"service.instance.id": "140256800425344",
"host.hostname": "database-service-54d9f5bb77-7894b"
}
}
{
"name": "INSERT",
"context": {
"trace_id": "0x2d6db793a08ce388e307746429b878ca",
"span_id": "0x24cf4097332b0e1b",
"trace_state": "[]"
},
"kind": "SpanKind.CLIENT",
"parent_id": "0x763c35d48b48125f",
"start_time": "2022-12-07T15:46:42.564955Z",
"end_time": "2022-12-07T15:46:42.565614Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"db.system": "mysql",
"db.name": "APM",
"db.statement": "INSERT INTO User_Carts (ItemId, TotalQty) VALUES (%(ItemId)s, %(Qty)s) ON DUPLICATE KEY UPDATE TotalQty = TotalQty + %(Qty)s",
"db.user": "root",
"net.peer.name": "mysql.mysql.svc.cluster.local",
"net.peer.port": 3306
},
"events": [],
"links": [],
"resource": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.9.1",
"service.name": "database",
"service.instance.id": "140256800425344",
"host.hostname": "database-service-54d9f5bb77-7894b"
}
}
{
"name": "add_item_to_cart",
"context": {
"trace_id": "0x2d6db793a08ce388e307746429b878ca",
"span_id": "0x763c35d48b48125f",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": "0xdc3036ca1d22d7ce",
"start_time": "2022-12-07T15:46:42.304282Z",
"end_time": "2022-12-07T15:46:42.629916Z",
"status": {
"status_code": "UNSET"
},
"attributes": {},
"events": [],
"links": [],
"resource": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.9.1",
"service.name": "database",
"service.instance.id": "140256800425344",
"host.hostname": "database-service-54d9f5bb77-7894b"
}
}
2022-12-07 15:46:42,647 INFO [werkzeug] [_internal.py:224] [trace_id=0 span_id=0 resource.service.name=database] - 172.17.0.1 - - [07/Dec/2022 15:46:42] "POST /add_item_to_cart HTTP/1.1" 200 -
{
"name": "/add_item_to_cart",
"context": {
"trace_id": "0x2d6db793a08ce388e307746429b878ca",
"span_id": "0xdc3036ca1d22d7ce",
"trace_state": "[]"
},
"kind": "SpanKind.SERVER",
"parent_id": "0x505a7c7cb680f435",
"start_time": "2022-12-07T15:46:42.303411Z",
"end_time": "2022-12-07T15:46:42.640831Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"http.method": "POST",
"http.server_name": "0.0.0.0",
"http.scheme": "http",
"net.host.port": 5000,
"http.host": "database-service.database-service.svc.cluster.local",
"http.target": "/add_item_to_cart",
"net.peer.ip": "172.17.0.1",
"http.user_agent": "python-requests/2.28.1",
"net.peer.port": 19094,
"http.flavor": "1.1",
"http.route": "/add_item_to_cart",
"http.status_code": 200
},
"events": [],
"links": [],
"resource": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.9.1",
"service.name": "database",
"service.instance.id": "140256800425344",
"host.hostname": "database-service-54d9f5bb77-7894b"
}
}
{
"name": "TRUNCATE",
"context": {
"trace_id": "0x4b8d2e73db66f3a1cb617b32be8305c8",
"span_id": "0x06e05c11e8df548f",
"trace_state": "[]"
},
"kind": "SpanKind.CLIENT",
"parent_id": "0x57571c775bfd47f8",
"start_time": "2022-12-07T15:46:45.317472Z",
"end_time": "2022-12-07T15:46:45.809240Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"db.system": "mysql",
"db.name": "APM",
"db.statement": "TRUNCATE TABLE User_Carts",
"db.user": "root",
"net.peer.name": "mysql.mysql.svc.cluster.local",
"net.peer.port": 3306
},
"events": [],
"links": [],
"resource": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.9.1",
"service.name": "database",
"service.instance.id": "140256800425344",
"host.hostname": "database-service-54d9f5bb77-7894b"
}
}
{
"name": "cart_sold",
"context": {
"trace_id": "0x4b8d2e73db66f3a1cb617b32be8305c8",
"span_id": "0x57571c775bfd47f8",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": "0xbd3d3ed04054fbd1",
"start_time": "2022-12-07T15:46:45.233742Z",
"end_time": "2022-12-07T15:46:45.817225Z",
"status": {
"status_code": "UNSET"
},
"attributes": {},
"events": [],
"links": [],
"resource": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.9.1",
"service.name": "database",
"service.instance.id": "140256800425344",
"host.hostname": "database-service-54d9f5bb77-7894b"
}
}
{
"name": "/cart_sold",
"context": {
"trace_id": "0x4b8d2e73db66f3a1cb617b32be8305c8",
"span_id": "0xbd3d3ed04054fbd1",
"trace_state": "[]"
},
"kind": "SpanKind.SERVER",
"parent_id": "0x6419064ed3a46f7b",
"start_time": "2022-12-07T15:46:45.232658Z",
"end_time": "2022-12-07T15:46:45.829557Z",
"status": {
"status_code": "UNSET"
},
"attributes": {
"http.method": "DELETE",
"http.server_name": "0.0.0.0",
"http.scheme": "http",
"net.host.port": 5000,
"http.host": "database-service.database-service.svc.cluster.local",
"http.target": "/cart_sold",
"net.peer.ip": "172.17.0.1",
"http.user_agent": "python-requests/2.28.1",
"net.peer.port": 27212,
"http.flavor": "1.1",
"http.route": "/cart_sold",
"http.status_code": 200
},
"events": [],
"links": [],
"resource": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.9.1",
"service.name": "database",
"service.instance.id": "140256800425344",
"host.hostname": "database-service-54d9f5bb77-7894b"
}
}

소스를 보면 분석하면, 복잡하지는 않습니다. 오픈텔레메트리를 도입하고자 하는 운영자들이 고민하는 점은 기간과 비용입니다. 기존 시스템의 많은 변경이 필요하고, 많은 공수를 투입해야 한다면, 아무리 좋은 기술이라고 해도 도입하는 것이 어려울 것입니다.

신속하게 관찰가능성을 도입하는 방법

운영자는 최소한의 변경으로 오픈텔레메트리를 도입하고, 관찰가능성을 구현하는데 초점을 맞추어야 합니다.

빠르고 쉬운 관찰가능성을 구현하기 위해서, 2가지 방법이 있습니다.

  1. 수동 계측을 해서, 추적과 로그에 개발합니다. 메트릭은 자동적으로 생성하고, 추후 필요 시에만 개발합니다.
  2. 자동 계측을 해서, 변경을 최소화합니다. 컬렉터를 최대한 활용합니다.

이번 글에서는 1번 방법을 사용합니다. 오픈텔레메트리 추적과 로그를 개발하고, 특정 관찰가능성 솔루션에 상관없이 텔레메트리를 분석할 수 있는 장점이 있습니다.

--

--

모니터링의 새로운 미래 관측 가능성

모니터링의 새로운 미래 관측 가능성의 소스를 설명합니다.