ข้ามไปยังเนื้อหา

Tag กับ Unified Service Tagging

tag คือ key:value ธรรมดา แต่มีสาม tag เฉพาะ — env, service และ version — ที่ถูกใส่แบบเดียวกันข้าม metric, log และ trace คือสิ่งที่ทำให้คุณ pivot จาก signal หนึ่งไปอีก signal หนึ่งของ deployable unit เดียวกันได้ บทนี้พิสูจน์กฎ syntax, ที่มาของ tag และจุดที่ cardinality เริ่มแพงจริง ๆ ทั้งหมดพิสูจน์กับ Agent จริง ไม่ใช่จากความจำ

metric, log และ trace ทุกตัวใน Datadog พก tag ได้ เขียนเป็น key:value — env:prod, region:ap-southeast-1, team:payments tag มาจากไม่กี่ที่

  • Host tag — Agent แปะให้อัตโนมัติ (hostname, OS, cloud provider/instance metadata) หรือตั้งค่าครั้งเดียวใน list tags: ของ datadog.yaml
  • DD_TAGS — เทียบเท่า tags: ในรูปแบบ environment variable ทั้ง Agent และ (บางส่วน) tracer library อ่านค่านี้
  • Container label — ผ่าน DD_CONTAINER_LABELS_AS_TAGS พิสูจน์ด้านล่าง เป็น mapping ที่ opt-in ชัดเจน ไม่ใช่อัตโนมัติ
  • Integration tag — ถูกเพิ่มโดย config ของ check เช่น tag service:orders-db ในตัวอย่าง Postgres จากบทที่แล้ว
  • Custom tag — อะไรก็ตามที่คุณเพิ่มเอง บน host, บน container หรือแปะตรงกับ metric submission เลย

tag คือสิ่งที่ทำให้ UI ของ Datadog ใช้งานได้จริงในระดับใหญ่ — แทนที่จะไล่หาใน list ของ host 4,000 ตัว คุณ filter ด้วย env:prod AND team:payments แล้วได้เฉพาะส่วนที่ต้องการพอดี ใช้ได้เหมือนกันไม่ว่าจะดู Infrastructure, Logs หรือ APM — tag เดียวกัน syntax filter เดียวกัน ทุกที่

มี tag key ไม่กี่ตัวที่ backend ของ Datadog ปฏิบัติเป็นพิเศษ ไม่ใช่แค่ตาม convention — host (host ที่รายงาน ปกติตั้งค่าให้อัตโนมัติ ไม่ใช่อะไรที่คุณควร override มือตามปกติ) และ trio ของ unified service tagging env, service, version (ลงลึกด้านล่าง) UI ของ Datadog, หน้า service ของ APM และ Software Catalog ทั้งหมดสมมติว่า key เหล่านี้มีความหมายตามที่ platform คาดหวัง — เอา host หรือ service ไปใช้กับอะไรที่ไม่เกี่ยวกับความหมาย reserved ของมัน จะทำให้ feature ที่อ่านค่าพวกนี้พัง ไม่ใช่แค่ dashboard ของคุณเอง

syntax ของ tag และเรื่องที่น่าแปลกใจ: การ normalize เกิดฝั่ง server ไม่ใช่ที่ Agent

หัวข้อที่มีชื่อว่า “syntax ของ tag และเรื่องที่น่าแปลกใจ: การ normalize เกิดฝั่ง server ไม่ใช่ที่ Agent”

ตาม docs.datadoghq.com/getting_started/tagging/#defining-tags: tag มี letter (Unicode letter ใดก็ได้), number, underscore, minus, colon, period และ forward slash ได้หลัง letter ตัวแรก “ตัวอักษรอื่นทั้งหมด (รวมถึง comma, emoji, backslash และ space) ถูกแปลงเป็น underscore”; tag จำกัดที่ 200 ตัวอักษรรวม (key + : + value); และส่วนที่น่าแปลกใจ — “Span tags and metric tags are normalized to lowercase… This normalization occurs at Datadog’s backend, not the Agent level.”

พิสูจน์: datadog/agent:7 container จริงที่ตั้งค่า list tags: แบบรก ๆ ตั้งใจ —

datadog.yaml
api_key: "<DD_API_KEY>"
tags:
- "Team:Payments API"
- "env:PROD"
- "region:ap-southeast-1"

— และ output จริงของ agent status โชว์ tag ไม่เปลี่ยนแปลงเลย ทั้งตัวพิมพ์ใหญ่และ space ข้างใน:

$ docker exec dd-tags agent status
host tags:
Team:Payments API
env:PROD
region:ap-southeast-1

ตรงกับ docs เป๊ะ: การ normalize (ทำเป็นตัวพิมพ์เล็ก, space เป็น underscore) คือ behavior ฝั่ง backend ของ Datadog ไม่ใช่สิ่งที่ Agent ทำก่อนส่งออกไป agent status โชว์ tag ตามที่คุณตั้งค่า ไม่ใช่ตามที่ Datadog จะ index ในที่สุด — ช่องว่างจริงที่ไม่ชัดเจนระหว่าง “สิ่งที่ฉันเห็นในเครื่อง” กับ “สิ่งที่จะ filter ได้จริง”

DD_TAGS มีกับดักที่คมกว่า tags: — มัน split ตาม whitespace แบบเงียบ ๆ

หัวข้อที่มีชื่อว่า “DD_TAGS มีกับดักที่คมกว่า tags: — มัน split ตาม whitespace แบบเงียบ ๆ”

docs.datadoghq.com/getting_started/tagging/#defining-tags ยังบอกด้วยว่า: “Whitespace in DD_TAGS values is not converted to underscores” — ต่างจาก item ใน list YAML tags: (แต่ละ item คือ string เต็มหนึ่งตัว) DD_TAGS คือ list ที่คั่นด้วย space ดังนั้น space ข้างใน tag value ที่คุณตั้งใจให้เป็นค่าเดียวจะ split ออกเป็นสอง tag แทน พิสูจน์จริง container ที่ตั้งค่า DD_TAGS="team:payments api region:ap-southeast-1" (ตั้งใจให้เป็น tag เดียว team:payments api บวก region:ap-southeast-1):

$ docker exec dd-ddtags agent status
host tags:
api
region:ap-southeast-1
team:payments

tag ที่ตั้งใจไว้ team:payments api กลายเป็นสอง host tag แบบเงียบ ๆ: team:payments (ถูกตัดทอน) กับ tag เปล่า ไม่มี key ชื่อ api ไม่มีอะไร error เลย — นี่คือความผิดพลาดแบบ AI-generated-config ที่ ship ออกไปสะอาดและโผล่มาเป็น tag แปลก ๆ ใน UI อีกหลายสัปดาห์ถัดมา

label ของ Docker/container ไม่ กลายเป็น Datadog tag อัตโนมัติ — ต้อง map ชัดเจนด้วย DD_CONTAINER_LABELS_AS_TAGS เป็น JSON object {"<ชื่อ label>": "<tag key>"} พิสูจน์จริงว่า env var นี้ถูกอ่านจริง (ไม่ใช่แค่รับไว้เฉย ๆ) ผ่านคำสั่ง diagnostic agent config ของ Agent เองที่ dump runtime config ที่ resolve แล้วเต็ม:

$ docker run -d --name dd-labeltags \
-e DD_API_KEY=<fake> \
-e DD_CONTAINER_LABELS_AS_TAGS='{"com.docker.compose.service":"service_name"}' \
datadog/agent:7
$ docker exec dd-labeltags agent config | grep container_labels_as_tags
container_labels_as_tags: '{"com.docker.compose.service":"service_name"}'

นี่คือกลไกที่ต่างจาก label Autodiscovery com.datadoghq.ad.* ที่คุมในบทที่แล้ว — ตัวนั้นตั้งค่า check ไหนรัน; DD_CONTAINER_LABELS_AS_TAGS ตั้งค่า label ของ container ตัวไหนที่มีอยู่แล้วกลายเป็น tag บน telemetry ที่ container นั้นผลิต ไม่มีตัวไหน imply อีกตัว

ไม่ใช่ทุกข้อมูลที่ควรกลายเป็น tag value Datadog track time series (และ log facet ที่ถูก index) ต่อ combination ของ tag value ที่ไม่ซ้ำกัน ดังนั้น tag ที่ value ต่างกันแทบทุก event — raw user ID, request ID, URL เต็มพร้อม query parameter — จะสร้าง time series หรือ facet value ใหม่แทบทุก data point เรื่องนี้เรียกว่า high cardinality และแพงสองทาง — ทำให้บิล custom metric พองขึ้น (แต่ละ combination ของ tag ที่ไม่ซ้ำกันบน metric คือ series ที่ถูกคิดเงินแยกกัน) และทำให้ UI ใช้งานยากขึ้น เพราะ dropdown filter tag ที่มี distinct value เป็นล้านตัวก็ไม่ใช่สิ่งที่คนใช้จริงในการ narrow ลงได้อีกต่อไป

กฎง่าย ๆ — tag ด้วยสิ่งที่คุณอยากจะ filter หรือ group จริง ๆ — env, service, version, region, team, endpoint (เป็น route pattern ที่ normalize แล้ว ไม่ใช่ raw path) — ส่วน identifier ที่ไม่ซ้ำกันจริง ๆ (user ID, request ID, order ID) ให้ใส่ใน log message หรือ trace attribute แทนที่จะเป็น tag

นอกจาก “อย่า tag ข้อมูล high-cardinality เอง” แล้ว Agent มี dial cardinality ชัดเจนสำหรับ tag ที่ตัว Agent เองแปะให้ check metric ตรงจาก pkg/config/config_template.yaml ของ Agent 7.83.3 เองบน GitHub:

## @param checks_tag_cardinality - string - optional - default: "low"
## @env DD_CHECKS_TAG_CARDINALITY - string - optional - default: "low"
## Configure the level of granularity of tags to send for checks metrics and events. Choices are:
## * low: add tags about low-cardinality objects (clusters, hosts, deployments, container images, ...)
## * orchestrator: add tags about pod (in Kubernetes), or task (in ECS/Mesos) -level of cardinality
## * high: add tags about high-cardinality objects (individual containers, user IDs in requests, ...)

พิสูจน์จริงว่า env var ถูกอ่านจริง ผ่าน agent config อีกครั้ง:

$ docker run -d --name dd-card -e DD_API_KEY=<fake> \
-e DD_CHECKS_TAG_CARDINALITY=orchestrator datadog/agent:7
$ docker exec dd-card agent config | grep checks_tag_cardinality
checks_tag_cardinality: orchestrator

setting คู่กัน DD_DOGSTATSD_TAG_CARDINALITY คุม choice สามระดับเดียวกันสำหรับ custom metric ที่ส่งผ่าน DogStatsD โดยเฉพาะ — ลงในบท custom-metrics ของ module ถัดไป การยก cardinality จาก low เป็น high คือ trade ที่ตั้งใจ ชัดเจน ไม่ใช่สิ่งที่เกิดขึ้นเองแบบที่ custom tag value ที่ไม่มี bound เกิดขึ้นโดยไม่ตั้งใจ

นี่คือ convention การ tag ที่ส่วนที่เหลือของคอร์สนี้พึ่งพาอยู่ มี tag key สามตัวที่ถูก reserve และถูกปฏิบัติเป็นพิเศษข้ามทุก product ของ Datadog

  • env — กำลังรันอยู่ใน environment ไหน (prod, staging, dev)
  • service — ชื่อของ deployable unit (orders-api, checkout-worker)
  • version — build/release เฉพาะของ service นั้น (1.4.2, git SHA หรืออะไรก็ตามที่ release process ของคุณผลิตออกมา)

เหตุผลที่สามตัวนี้มีอยู่เป็น convention เฉพาะ แทนที่จะเป็นแค่ “tag ที่คุณเพิ่มเองก็ได้” คือเรื่อง correlation — เมื่อ Agent ของ host, tracer ของแอป และ log shipper ทั้งหมดใส่ค่า env, service และ version เดียวกันเป๊ะ ให้ telemetry ของแต่ละตัว Datadog เชื่อม telemetry พวกนั้นเข้าด้วยกันได้ คุณเห็น error-rate metric ของ orders-api พุ่งขึ้นที่ env:prod, version:1.4.2 กระโดดตรงไปที่ trace ที่พก tag เดียวกันสามตัวนั้น แล้วจากตรงนั้นไปที่ log บรรทัดที่ถูกส่งออกมาระหว่าง request เหล่านั้น — โดยไม่ต้องมานั่ง correlate timestamp หรือ hostname มือเอง การ pivot นั้นทำงานได้ก็เพราะ value ของ tag ไม่ใช่แค่ key ตรงกันเป๊ะข้ามทั้งสาม telemetry type — พิมพ์ผิดแบบ Prod เทียบกับ prod ทำให้ join พังแบบเงียบ ๆ (และพิสูจน์ด้านบน ความไม่ตรงกันแบบนี้จะไม่ถูกจับด้วยการ normalize ฝั่ง Agent เลย)

flowchart LR
    subgraph Tags["env:prod  service:orders-api  version:1.4.2"]
      M["Metrics"]
      T["Traces"]
      L["Logs"]
    end
    M -.same 3 tags.-> T
    T -.same 3 tags.-> L
    L -.same 3 tags.-> M
Unified service tagging ผูก telemetry เข้าด้วยกัน

บน host ธรรมดาหรือ container เดี่ยว unified service tagging ถูกตั้งผ่านสาม environment variable ที่ทั้ง Agent และ tracer อ่าน

Terminal window
export DD_ENV="prod"
export DD_SERVICE="orders-api"
export DD_VERSION="1.4.2"

ใน Kubernetes สิ่งที่เทียบเท่าคือชุด label บน Deployment กับ Pod template — tags.datadoghq.com/env, tags.datadoghq.com/service, tags.datadoghq.com/version — แล้วค่าเดียวกันนั้นถูก re-inject เป็น environment variable DD_ENV/DD_SERVICE/DD_VERSION ผ่าน Kubernetes downward API เพื่อให้ tracer ข้างใน container เห็นค่าเดียวกันด้วย

apiVersion: apps/v1
kind: Deployment
metadata:
labels:
tags.datadoghq.com/env: "prod"
tags.datadoghq.com/service: "orders-api"
tags.datadoghq.com/version: "1.4.2"
spec:
template:
metadata:
labels:
tags.datadoghq.com/env: "prod"
tags.datadoghq.com/service: "orders-api"
tags.datadoghq.com/version: "1.4.2"
spec:
containers:
- name: my-app
env:
- name: DD_ENV
valueFrom: { fieldRef: { fieldPath: "metadata.labels['tags.datadoghq.com/env']" } }
- name: DD_SERVICE
valueFrom: { fieldRef: { fieldPath: "metadata.labels['tags.datadoghq.com/service']" } }
- name: DD_VERSION
valueFrom: { fieldRef: { fieldPath: "metadata.labels['tags.datadoghq.com/version']" } }

label อยู่สองที่ (Deployment metadata กับ Pod template metadata) เพราะ Autodiscovery ของ Agent อ่าน pod label โดยตรงสำหรับ tagging ระดับ host/container ส่วนการ project ผ่าน downward API คือสิ่งที่เอาค่า เดียวกัน เข้าไปใน environment ของ process เองให้ tracer หยิบไปใช้ — source of truth เดียว ผู้ใช้สองฝั่ง

🐛 หา bug ในโค้ดนี้

Terminal window
# AI comment: "Tag this host with team and region for easier filtering"
export DD_API_KEY="<DD_API_KEY>"
export DD_TAGS="team:payments api region:ap-southeast-1 tier:critical"
เฉลย

ดูเหมือน DD_TAGS assignment ปกติ valid — สาม pair key:value คั่นด้วย space ซึ่งเป็น format ที่ถูก document จริงสำหรับ tag ที่แยกกัน bug ที่พิสูจน์ด้วย output จริงของ agent status ด้านบน: คนเขียนตั้งใจให้ tag value เป็น payments api (ชื่อ service สองคำมี space) แต่ DD_TAGS split ตาม whitespace แบบไม่มีเงื่อนไขเลย ไม่มีกลไก quoting ให้ escape ผลลัพธ์จริงคือ host tag สี่ตัว ไม่ใช่สาม: team:payments, tag เปล่าไม่มี key ชื่อ api, region:ap-southeast-1 และ tier:critical — ผิดแบบเงียบ ๆ ไม่มี error ที่ไหนเลย วิธีแก้: เอา space ออก (team:payments-api หรือ team:payments_api) หรือย้าย tag ตัวนี้ไปไว้ใน list YAML tags: แทน ที่แต่ละ item เป็น string เต็มไม่ถูกแตะต้อง space ข้างในไม่ทำให้ split

ลองนึกภาพ Agent เป็น เสมียนขนส่งที่เขียนป้ายตามที่ได้รับมาเป๊ะ ๆ ไม่ใช่บรรณาธิการ ไม่ว่าคุณจะให้ตัวพิมพ์, การเว้นวรรค หรือ format แบบไหนมา Agent ก็แปะบนพัสดุไม่เปลี่ยนแปลง — agent status โชว์ป้ายนั้นให้คุณเห็นตรงตัว คลังสินค้า (backend ของ Datadog) ต่างหากคือที่ที่การ normalize เป็นตัวพิมพ์เล็กกับการ index เกิดขึ้นจริง อยู่นอกสายตา Agent เลย ในขณะเดียวกัน DD_TAGS คือเสมียนที่ถูกสั่งว่า “แยกรายการด้วย space” แล้วทำตามคำสั่งนั้นตรงตัวสุด ๆ แม้ space นั้นตั้งใจให้อยู่ข้างในคำอธิบายของรายการเดียว — ไม่มีทางบอกเสมียนว่า “space นี้ต่างออกไป” ซึ่งเป็นเหตุผลตรง ๆ ว่าทำไม list YAML tags: (string เดียวต่อบรรทัด ไม่กำกวม) ปลอดภัยกว่าทุกครั้งที่ tag value อาจมี space

  • tags: (YAML list) เทียบกับ DD_TAGS (env var) — DD_TAGS สะดวกกว่าสำหรับอะไรที่ parameterize ด้วย environment variable อยู่แล้ว (container orchestrator, ค่าที่ CI inject) และไม่ต้อง mount ไฟล์; tags: ใน datadog.yaml ไม่กำกวมสำหรับ value ที่มี space พิสูจน์ด้านบนว่าเป็นเคสที่ DD_TAGS ทำผิดแบบเงียบ ๆ พอดี
  • DD_CHECKS_TAG_CARDINALITY: high เทียบกับ default low — high ให้ความละเอียดระดับ per-container หรือ per-user-ID เมื่อคุณต้อง debug ที่ resolution นั้นจริง ๆ พิสูจน์ด้านบนว่ามันจริงและอ่านค่าถูก แต่ cost ของ high-cardinality เดียวกับที่บทนี้เตือนสำหรับ custom tag ก็ใช้กับตรงนี้ด้วย — เป็น trade-off เดียวกันแบบ explicit ผ่าน config ไม่ใช่ของฟรี
  • DD_CONTAINER_LABELS_AS_TAGS (mapping opt-in) เทียบกับ label Autodiscovery com.datadoghq.ad.* — labels-as-tags mapping คือ “map label นี้เป็น tag นี้บนทุกอย่าง” แบบ host-wide ตรงไปตรงมา; label Autodiscovery เฉพาะเจาะจงต่อ check และรวยกว่า (config instance เต็ม ไม่ใช่แค่ tag) แต่มีผลแค่ check ตัวเดียวที่แปะอยู่ ทั้งสองแก้ปัญหาคนละแบบและอยู่บน container เดียวกันได้บ่อยโดยไม่ขัดกัน
  • “agent status โชว์ tag ตรงตามที่ Datadog จะ index และให้ filter ได้” ผิด — พิสูจน์ด้านบน: tag ที่ตั้งค่า Team:Payments API ปรากฏใน agent status ไม่เปลี่ยนแปลงเลย ทั้งตัวพิมพ์ใหญ่และ space; การ normalize เป็นตัวพิมพ์เล็กและ space-เป็น-underscore เกิดที่ backend ของ Datadog ตาม docs ไม่ใช่ที่ Agent
  • “DD_TAGS="key:value with spaces" ให้ tag เดียวที่ value ถูก underscore เหมือน entry ของ YAML tags:” ผิด — พิสูจน์ด้านบน: DD_TAGS split ตาม whitespace ทุกตัวโดยไม่มี escape สร้างหลาย tag แบบเงียบ ๆ (รวมถึง tag เปล่าไม่มี key) แทนที่จะเป็น tag เดียวที่ตั้งใจไว้
  • “label ของ Docker/container กลายเป็น Datadog tag อัตโนมัติ” ผิด — พิสูจน์ด้านบนผ่าน agent config: จะกลายเป็น tag ก็ต่อเมื่อ map ชัดเจนด้วย DD_CONTAINER_LABELS_AS_TAGS เท่านั้น label ที่ไม่ได้ map มองไม่เห็นในการ tag ของ Datadog เลย
  • “การยก cardinality ของ tag เป็นแค่ setting UI ไม่มีผลด้าน cost” ผิด — DD_CHECKS_TAG_CARDINALITY: high (พิสูจน์ด้านบนว่าจริงและ Agent อ่านค่าได้) แลกกับ cost ด้าน billing ของ custom metric กับความใช้งานได้ของ dropdown เดียวกันเป๊ะที่บทนี้อธิบายสำหรับ tag high-cardinality ที่เพิ่มมือ — มันคือ trade-off เดียวกันผ่าน config knob ไม่ใช่ข้อยกเว้น
พิสูจน์ด้านบนด้วย Agent จริงที่ตั้งค่า tag "Team:Payments API" agent status โชว์อะไรจริง ๆ
พิสูจน์ด้านบนด้วย DD_TAGS="team:payments api region:ap-southeast-1" host tag จริงที่ agent status โชว์คืออะไร
label ของ Docker/container กลายเป็น Datadog tag บน telemetry ของ container นั้นอัตโนมัติหรือไม่
ตรงจาก config_template.yaml ของ Agent 7.83.3 เองบน GitHub DD_CHECKS_TAG_CARDINALITY คุมอะไร และ default คืออะไร
สาม tag ไหนที่ประกอบกันเป็น unified service tagging
ในตัวอย่าง Kubernetes ทำไม label tags.datadoghq.com/* ถึงถูก re-inject เป็น env var DD_ENV/DD_SERVICE/DD_VERSION ผ่าน downward API ด้วย