OTLP กับมาตรฐานเปิด
ไอเดียหลักในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียหลักในหนึ่งประโยค”OTLP เป็น wire format ไม่ใช่ฟีเจอร์ของ Datadog จึงมีตัวแปลสองตัวอยู่ระหว่าง OpenTelemetry SDK กับหน้าจอ Datadog — OTLP receiver ของ Agent และ Datadog exporter ของ Collector (หรือเส้นทาง OTLP HTTP ที่ใหม่กว่า) — และแต่ละตัวใช้ การ map semantic convention ชุดเดียวกัน (service.name เป็น service, http.request.method เป็น http.method, span kind กับ db.system เป็น operation name และ span type) คำถามที่มีประโยชน์จึงไม่ใช่ “รับ OTLP ไหม” แต่คือ “มันเปลี่ยน attribute ของฉันเป็นอะไร”
สองเส้นทาง ในภาพเดียว
หัวข้อที่มีชื่อว่า “สองเส้นทาง ในภาพเดียว”flowchart LR SDK["App with OpenTelemetry SDK"] -->|"OTLP grpc :4317 or http :4318"| AG["Agent OTLP receiver: otlp_config"] SDK -->|"OTLP"| COL["OpenTelemetry Collector"] AG --> MAP1["Semantic mapping and sampling in the Agent"] MAP1 --> INT["Datadog intake"] COL -->|"datadog exporter, or otlp_http exporter"| MAP2["Semantic mapping in exporter or intake"] MAP2 --> INT
OTLP ingest ใน Agent
หัวข้อที่มีชื่อว่า “OTLP ingest ใน Agent”ปิดอยู่จนกว่าคุณจะเปิด
หัวข้อที่มีชื่อว่า “ปิดอยู่จนกว่าคุณจะเปิด”docs บอกตรง ๆ: “OTLP ingestion is off by default” (OTLP ingestion by the Datadog Agent) container แบบ default:
$ docker exec dd-internals agent statusOTLP==== Status: Not enabled Collector status: Not running
$ curl -X POST -H 'Content-Type: application/json' --data @otlp_trace.json http://127.0.0.1:14318/v1/tracescurl: (52) Empty reply from server # HTTP 000: nothing is listening behind the published portไม่มีอะไร listen ดังนั้น OTel SDK ที่ชี้มาที่ Agent จะ export ไม่สำเร็จ (SDK ออกแบบให้ log แล้วทิ้ง) เมื่อเปิดแล้วจะมีสองบรรทัด Status ที่ความหมายต่างกัน และความต่างนั้นคือเรื่องทั้งหมดของหัวข้อถัดไป
key, ค่า default และ “คำโกหกสองข้อ” ของ configcheck
หัวข้อที่มีชื่อว่า “key, ค่า default และ “คำโกหกสองข้อ” ของ configcheck”agent config --all ใน container ที่รันอยู่แสดง setting ที่สำคัญกับค่า default:
otlp_config: metrics: enabled: true histograms: mode: distributions sums: cumulative_monotonic_mode: to_delta resource_attributes_as_tags: false logs: enabled: false traces: enabled: true internal_port: 5003 span_name_as_resource_name: false probabilistic_sampler: sampling_percentage: 100 receiver: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318(ตัดออก: block ของ batch, keepalive และ CORS สอง endpoint แสดงเป็น 0.0.0.0 เพราะ env ของ container นี้ตั้งไว้ ตัว receiver ไม่มี endpoint จนกว่าคุณจะตั้ง) docs ให้ปุ่มสองตัวที่เปิดมัน: “For gRPC (localhost:4317): DD_OTLP_CONFIG_RECEIVER_PROTOCOLS_GRPC_ENDPOINT; for HTTP (localhost:4318): DD_OTLP_CONFIG_RECEIVER_PROTOCOLS_HTTP_ENDPOINT” และประโยคที่พลาดง่าย: “These must be passed to both the core Agent and trace Agent processes.” log ปิดเป็น default “to prevent unexpected logs billing.”
ทีนี้หลักฐานว่า configcheck ไม่ได้ตรวจเรื่องนี้ datadog.yaml ที่ผิดหนึ่งคำ protocol แทน protocols:
# @expect-error otlpreceiver.Config has invalid keys: protocolapi_key: <your-api-key>otlp_config: receiver: protocol: http: endpoint: 0.0.0.0:4318เริ่มด้วยไฟล์นั้น (container แบบ --network none) แล้วสามคำสั่งที่คนใช้ตรวจ:
$ agent configcheck ; echo "configcheck exit $?"configcheck exit 0
$ agent statusOTLP==== Status: Enabled Collector status: Closed
$ grep "OTLP ingest" /var/log/datadog/agent.logCORE | ERROR | (comp/otelcol/collector/impl-pipeline/pipeline.go:116 in func1) | Error running the OTLP ingest pipeline: failed to get config: cannot unmarshal the configuration: decoding failed due to the following error(s):'receivers' error reading configuration for "otlp": decoding failed due to the following error(s):'otlpreceiver.Config' has invalid keys: protocolagent configcheck ลิสต์ config ของ check ที่ resolve แล้วจาก conf.d/ มันไม่เคยดู otlp_config ของ datadog.yaml และ exit 0 agent status บอก Status: Enabled (มี setting อยู่) แต่ Collector status: Closed (pipeline ตายตอน startup) มีแค่บรรทัดสุดท้ายของ log ที่บอกชื่อ key ที่ผิด สองบรรทัดที่ต่างกันจึงมีความหมายต่างกัน และ gate --agent ของ harness ซึ่งรัน configcheck บน fence # datadog.yaml ของบทเรียน พิสูจน์ว่า Agent เริ่มทำงาน กับไฟล์นั้นได้ ไม่ได้พิสูจน์ว่า OTLP ใช้งานได้ ให้อ่าน Collector status และ grep log เมื่อสะกด protocols ถูก (ทางแก้ ที่ harness collect):
api_key: <your-api-key>otlp_config: receiver: protocols: http: endpoint: 0.0.0.0:4318 logs: enabled: falseสามคำสั่งเดิมให้ configcheck exit 0, Status: Enabled, Collector status: Running
localhost กับ 0.0.0.0 คือเส้นแบ่งของ network
หัวข้อที่มีชื่อว่า “localhost กับ 0.0.0.0 คือเส้นแบ่งของ network”ค่า env default ใน docs คือ localhost:4317 และ localhost:4318 และคำแนะนำสำหรับ container คือ: “If running in a containerized environment, use 0.0.0.0 instead of localhost to ensure the server is available on non-local interfaces.” ผมตั้ง gRPC เป็น localhost:4317 และ HTTP เป็น 0.0.0.0:4318 ใน container เดียว อ่าน socket ที่ listen จาก /proc/net/tcp แล้วเชื่อมต่อจาก container ตัวที่สอง:
listening (inside the Agent container): 127.0.0.1:4317 [::]:4318from another container, to the Agent's IP: 4317 ConnectionRefusedError [Errno 111] Connection refused 4318 openAgent ที่อยู่ใน pod หรือ container ของตัวเองด้วย bind แบบ default จะเพื่อนบ้านเข้าถึงไม่ได้ docs เพิ่มข้อจำกัดที่ config แก้ไม่ได้: telemetry จาก collector หรือแอปบน host หนึ่งส่งไปยัง Agent บนอีก host ไม่ได้ (“The supported setup is an ingesting Agent deployed on every OpenTelemetry-data generating host”) — topology ที่รองรับคือหนึ่ง Agent ต่อหนึ่ง host
สิ่งที่ Agent ทำกับ OTLP span
หัวข้อที่มีชื่อว่า “สิ่งที่ Agent ทำกับ OTLP span”หนึ่ง trace สอง span ส่งเป็น OTLP/HTTP JSON resource มี service.name=checkout-otel, service.version=1.4.2, deployment.environment.name=lab; server span คือ GET /orders/{id} พร้อม http.request.method=GET, http.route=/orders/{id}, http.response.status_code=500, status Error (“upstream failed”); ตัวลูกคือ client span ที่มี db.system=postgresql และ db.statement สิ่งที่ไปถึง fake intake (/api/v0.2/traces, ถอดรหัสแล้ว):
tracer_version 'otlp-' env lab hostname internals-host app_version 1.4.2chunk priority 1 tags {_dd.p.dm: -9, _dd.otlp_sr: 1.00} span service=checkout-otel name=http.server.request resource="GET /orders/{id}" type=web error=1 duration 50 ms meta http.method=GET http.route=/orders/{id} span.kind=server env=lab version=1.4.2 error.msg="upstream failed" otel.status_code=Error otel.status_description="upstream failed" otel.scope.name=lab.instrumentation otel.trace_id=5b8efff798038103d269b633813fc60c metric http.status_code=500.0 _top_level=1.0 span service=checkout-otel name=postgresql.query resource="SELECT * FROM orders WHERE id = ?" type=sql duration 30 ms meta db.system=postgresql db.statement="SELECT * FROM orders WHERE id = $1" sql.query="SELECT * FROM orders WHERE id = ?"การแปลแต่ละอย่างเป็นกฎ และทุกอย่างตรงกับตารางใน docs (semantic mapping): service.name เป็น service, service.version เป็น version, deployment.environment.name เป็น env, http.request.method เป็น http.method, http.response.status_code เป็น http.status_code (และวัดได้ว่ากลายเป็น metric ตัวเลข ไม่ใช่ string tag) ส่วนที่เหลือเป็นการอนุมานของ Agent ที่ docs อธิบายเป็นตรรกะ span type ตามลำดับความสำคัญ — span.type ที่ระบุชัดก่อน แล้ว “Span kind server” เป็น web — จึงเป็นเหตุผลที่ server span เป็น web และ client span ของ postgresql เป็น sql operation name http.server.request และ resource GET /orders/{id} ถูกคำนวณ ไม่ได้ copy: resource คือ method บวก http.route (run แบบ convention เก่าด้านล่างคง span name OTel เดิมแต่ได้ resource เป็น GET) และ resource ของ DB span คือ statement ที่ obfuscate $1 เป็น ? ไม่ใช่ span name SELECT orders ของ OTel trace เก็บต้นกำเนิด OTel ไว้เป็น tag otel.*
สามรูปแบบ แต่ละอันรันจริง:
old semantic conventions (http.method, http.url, http.status_code, deployment.environment): span name=http.server.request resource="GET" env=lab http.status_code=500.0 # no http.route, so the resource is only the method; deployment.environment still maps to env
otlp_config.traces.span_name_as_resource_name=true: span name=GET_orders_id resource="GET /orders/{id}" span name=SELECT_orders resource="SELECT orders"
otlp_config.traces.probabilistic_sampler.sampling_percentage=25, 400 traces posted: chunks forwarded 98 _dd.p.dm=-9 _dd.otlp_sr=0.25ทั้งชื่อ attribute เก่าและใหม่ไปถึง env/http.method บน 7.83.3 (footnote ใน docs บอกว่า deployment.environment.name “Requires Datadog Agent 7.58.0+, Datadog Exporter v0.110.0+, or Datadog OTLP intake”) ถ้าไม่มี http.route resource ของ server span ยุบเหลือแค่ method — สาเหตุคลาสสิกของ resource GET ก้อนยักษ์ในหน้า service sampler คือจุดเชื่อมกลับไปที่ The Trace Sampling Pipeline: OTLP trace ไม่เคยผ่าน head sampler ของ Datadog tracer ดังนั้น otlp_config.traces.probabilistic_sampler ของ Agent คือสิ่งที่ sample มัน (98 จาก 400 คือ 24.5% เมื่อตั้ง 25%; mechanism -9 ตัวเดียวกับ probabilistic sampler ของ Agent ในบทนั้น บวก _dd.otlp_sr ที่บันทึก rate) docs ลิสต์ข้อจำกัดที่ตามมา: “Data from applications instrumented with OpenTelemetry SDK cannot be used in some Datadog proprietary products, such as App and API Protection, Continuous Profiler, and Ingestion Rules.” OTLP ผ่าน Agent ไม่มี DD_TRACE_SAMPLING_RULES และไม่มี rate จากหน้า Ingestion Control
สิ่งที่ Agent ทำกับ OTLP metric
หัวข้อที่มีชื่อว่า “สิ่งที่ Agent ทำกับ OTLP metric”ส่ง OTLP metric สามชนิด แล้ว export รอบสองหลังสิบวินาที พร้อม resource attribute service.name และ deployment.environment.name:
OTLP gauge lab.queue.depth = 42 {queue=jobs} -> series gauge 42.0 tags [queue:jobs, service:checkout-otel, env:lab, instrumentation_scope:lab.meter, instrumentation_scope_version:n/a]
OTLP cumulative monotonic sum lab.requests = 10, then 25 {route=home} -> first export: no point at all -> second export: series count 15.0 (a delta, 25 - 10)
OTLP delta histogram lab.latency count 4, sum 1060, min 10, max 1000, bounds [50, 500], buckets [2,1,1] -> /api/beta/sketches cnt 4 min 10.0 max 1000.0 sum 1060.0 k=[1590, 1739] n=[3, 1]นั่นคือ cumulative_monotonic_mode: to_delta และ histograms.mode: distributions ทำงาน resource attribute ถูกยกเป็น unified-service tag (service, env) ทั้งที่ resource_attributes_as_tags เป็น false (setting นั้นคุม resource attribute ตัวอื่น) และ instrumentation_scope ถูกเพิ่มเป็น tag โดย default สองผลที่ควรรู้ก่อนสร้าง dashboard ค่าแรกของ cumulative counter เป็น baseline และถูกทิ้ง ช่วงแรกหลัง restart จึงว่าง และ histogram มาถึงเป็น DDSketch ที่สร้างจาก explicit bucket — sketch key ที่ต่างกันมีแค่สองตัวสำหรับ OTel bucket สามอัน percentile จากมันจึงละเอียดเท่า explicitBounds ของคุณเท่านั้น (ดู DogStatsD and Aggregation ว่า sketch ตอบ percentile อย่างไร)
เส้นทาง Collector
หัวข้อที่มีชื่อว่า “เส้นทาง Collector”docs แนะนำสอง setup ของ Collector ในปัจจุบัน และ config ที่ AI เขียนมักปนกัน จาก Set Up the OpenTelemetry Collector: config ที่ “tested with the OpenTelemetry Collector Contrib distribution v0.154.0” ซึ่งใช้ OTLP HTTP exporter (“Sends telemetry to Datadog’s OTLP intake endpoints”, endpoint https://otlp.<YOUR_DD_SITE>), span_metrics connector สำหรับ RED metrics และ Datadog extension จาก Configure the Datadog Exporter and Connector: “For new OpenTelemetry Collector configurations, Datadog recommends the OTLP HTTP exporter and span_metrics connector setup. The Datadog Exporter and Datadog Connector remain supported, and existing configurations do not need to migrate.” ดังนั้น datadog exporter เป็น legacy แต่ยังรองรับ และเป็นตัวที่บท Phase 3 กับ config เดิมส่วนใหญ่ใช้ รูปแบบของมัน validate จริง:
receivers: otlp: protocols: grpc: endpoint: localhost:4317 http: endpoint: localhost:4318
processors: batch: send_batch_max_size: 100 send_batch_size: 10 timeout: 10s
connectors: datadog/connector:
exporters: datadog/exporter: api: site: datadoghq.com key: ${env:DD_API_KEY}
service: pipelines: metrics: receivers: [otlp, datadog/connector] processors: [batch] exporters: [datadog/exporter] traces: receivers: [otlp] processors: [batch] exporters: [datadog/connector, datadog/exporter] logs: receivers: [otlp] processors: [batch] exporters: [datadog/exporter]otelcol-contrib validate กับไฟล์นี้พิมพ์แค่บรรทัด debug และ exit 0 สามตัวแปรที่พัง ใช้คำสั่งเดียวกัน:
api.key renamed to api_key exit 1 Error: exporters::datadog/exporter: api.key is not setexporter id "datadogexporter" exit 1 Error: failed to get config: cannot unmarshal the configuration: decoding failed due to the following error(s): 'exporters' unknown type: "datadogexporter" for id: "datadogexporter" (valid values: [logzio bmchelix clickhouse ...connector removed from traces exporters (still a receiver in metrics) exit 1 Error: failed to build pipelines: connector "datadog/connector" used as receiver in [metrics] pipeline but not used in any supported exporter pipelineต่างจาก configcheck ของ Agent, validate ของ Collector สร้าง pipeline graph จริง จึงจับความผิดเรื่อง wiring ได้ — อันสุดท้ายถูกจับเพราะทั้ง validate และการเริ่มรันจริงสร้าง pipeline (container ที่เริ่มด้วยไฟล์เดียวกัน exit 1 พร้อมข้อความเดียวกันตอนที่ผมกลับมาดูหลังสิบวินาที) connector ถูกตั้งใจให้เป็น exporter ของ traces pipeline และ receiver ของ metrics pipeline: มันแปลง span เป็น trace metrics กับ exporter แบบ legacy log ตอน start ของ 0.161.0 บอกว่า “Trace metrics are now disabled in the Datadog Exporter by default. To continue receiving Trace Metrics, configure the Datadog Connector or disable the feature gate.” — connector คือสิ่งที่สร้าง APM trace metrics จาก span ของคุณ
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”คิดว่า OTLP คือ wire เดียว ผู้บริโภคสองแบบที่ฟีเจอร์ต่างกัน ผู้บริโภคฝั่ง Agent แปลง OTLP เป็นรูป span และ metric แบบ native ของ Datadog แล้วทำตัวเหมือน Agent ทั่วไป: มี sampler, tag, forwarder ของตัวเอง และรันบน host ผู้บริโภคฝั่ง Collector อยู่ในโครงสร้างพื้นฐานของคุณ และ component ของ Datadog แปลระหว่างทางออก ในทั้งสองแบบ สิ่งสำคัญคือ ตาราง mapping: attribute ที่ตรง convention กลายเป็น first-class (service, env, http.method) ที่ไม่ตรงยังเป็น raw tag และสิ่งที่ mapping ต้องการแต่ SDK ไม่ส่ง (http.route, deployment.environment.name) จะปรากฏทีหลังเป็นชื่อ resource ที่เสื่อมสภาพหรือ env ที่หายไป การเปิดดู payload อย่างข้างบนเร็วกว่าอ่าน dashboard ใด ๆ
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”| ทางเลือก | ข้อดี | ข้อเสีย |
|---|---|---|
| OTLP เข้า Agent | endpoint ในเครื่องเดียว; ใช้ tagging, sampler และ forwarder ของ Agent; ไม่ต้องรัน Collector เพิ่ม | ตาม docs ผลิตภัณฑ์ Datadog บางตัวใช้กับแอปที่ instrument ด้วย OTel ไม่ได้; topology ที่รองรับคือหนึ่ง Agent ต่อ host |
Collector กับ datadog exporter | ประมวลผลส่วนกลาง (filter, tail sampling ใน gateway) ก่อนถึง Datadog; pipeline ที่ไม่ผูก vendor | คุณเป็นเจ้าของ config, version และการ upgrade ของ Collector; ต้อง wire connector ไม่งั้น trace metrics หาย |
Collector กับ otlp_http exporter (คำแนะนำปัจจุบันของ docs) | ใช้ OTLP intake ของ Datadog; exporter ง่ายกว่า | รูปแบบ config ต่างจากตัวอย่างเดิมส่วนใหญ่; ตรวจขีดจำกัด intake ที่ docs ลิสต์ |
| Datadog tracer แทน OTel SDK | ฟีเจอร์ native (Ingestion Rules, profiler, App and API Protection) | instrumentation เฉพาะ vendor |
bind 0.0.0.0 | container และ pod อื่นเข้าถึงได้ | docs เองบอกว่า localhost ปลอดภัยกว่านอก container |
span_name_as_resource_name | resource ตาม span name ของคุณ | เปลี่ยน operation name ด้วย (GET_orders_id ข้างบน); ตรวจ monitor ที่อิง operation name ใหม่ |
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “
agent configcheckvalidate OTLP config ของฉันแล้ว” มัน exit0กับไฟล์ที่ OTLP pipeline ตายแล้ว (Collector status: Closed) ให้ดูagent statusและ log - “
Status: Enabledแปลว่า OTLP ใช้งานได้” แปลว่ามี setting อยู่Collector status: Runningคือสถานะที่ใช้งานได้ - “OTLP เปิดเมื่อ Agent เปิด” ปิดเป็น default; container default ปฏิเสธการเชื่อมต่อ
- “OTLP span ถูกเก็บตามที่ส่ง” มันถูก map:
http.request.methodเป็นhttp.method, span kind บวก attribute เป็น span type และ operation name - “cumulative counter ปรากฏตั้งแต่ export แรก” ค่าแรกเป็น baseline และไม่ได้ point; ครั้งที่สองได้ delta 15
- “OTLP trace ใช้
DD_TRACE_SAMPLING_RULES” ไม่มี Datadog tracer เกี่ยวข้อง; OTLP probabilistic sampler ของ Agent ทำงานแทน (98 จาก 400 ที่ 25%) - “
otelcol validateตรวจแค่ syntax ของ YAML” ใน 0.161.0 มันสร้าง pipeline และปฏิเสธ connector ที่ wire ผิดด้วย exit1
Reading AI-generated code
หัวข้อที่มีชื่อว่า “Reading AI-generated code”🐛 หา bug ในโค้ดนี้
# datadog.yaml -- "enable OTLP so our OpenTelemetry services can send traces"api_key: <your-api-key>otlp_config:receiver: protocol: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318เฉลย
protocol ต้องเป็น protocols Agent จริงรับไฟล์นี้, agent configcheck exit 0 และ agent status พิมพ์ Status: Enabled — แต่ OTLP pipeline ไม่เคยเริ่ม (Collector status: Closed) และหลักฐานมีแค่บรรทัดเดียวใน agent.log: 'otlpreceiver.Config' has invalid keys: protocol OTel SDK ทุกตัวเชื่อมต่อไม่ได้ สิ่งที่ควรจำคือควรเชื่อคำสั่งไหน: Collector status: Running block ที่แก้แล้ว ตรงกับที่ได้ Running ข้างบนพอดี:
# corrected datadog.yamlapi_key: <your-api-key>otlp_config: receiver: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318🐛 หา bug ในโค้ดนี้
receivers:otlp: protocols: http: endpoint: localhost:4318processors:batch:connectors:datadog/connector:exporters:datadog/exporter: api: site: datadoghq.com key: ${env:DD_API_KEY}service:pipelines: metrics: receivers: [otlp, datadog/connector] processors: [batch] exporters: [datadog/exporter] traces: receivers: [otlp] processors: [batch] exporters: [datadog/exporter]เฉลย
connector เป็น receiver ของ metrics pipeline แต่ไม่มีอะไรป้อนมัน: มันหายไปจาก exporters ของ traces pipeline otelcol-contrib 0.161.0 จริงปฏิเสธที่จะเริ่ม: Error: failed to build pipelines: connector "datadog/connector" used as receiver in [metrics] pipeline but not used in any supported exporter pipeline (exit 1 ทั้งจาก validate และการเริ่มรันจริง) ทางแก้คือเพิ่ม datadog/connector ใน exporter ของ traces เพื่อให้ trace metrics ที่สร้างจาก span ไปถึง metrics pipeline ไฟล์ที่ทำงานได้เต็มคือ collector-datadog-exporter.yaml ข้างบนซึ่ง validate ได้ exit 0 อย่า “แก้” error ด้วยการลบ connector ทุกที่: log ตอน start ของ Collector รุ่นนี้บอกว่า “Trace metrics are now disabled in the Datadog Exporter by default” ถ้าไม่มี connector exporter ก็ไม่สร้างมันอีกแล้ว (ผมไม่ได้รันตัวแปรนั้นเพื่อสังเกตฝั่ง metric)