Unified Service Tagging ในทางปฏิบัติ
ไอเดียหลักในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียหลักในหนึ่งประโยค”ตั้ง env, service, และ version ครั้งเดียวบน deployable unit หนึ่งตัว แล้ว triple เดียวกันนั้นแหละที่ทำให้ metrics dashboard, APM service page และ Logs Explorer เห็นตรงกันว่ากำลังดู service ไหนอยู่
Deployable unit เดียว แท็กครั้งเดียว
หัวข้อที่มีชื่อว่า “Deployable unit เดียว แท็กครั้งเดียว”เอา deployable unit เดียว checkout-service มาแท็กด้วย DD_ENV=production, DD_SERVICE=checkout-service, และ DD_VERSION=2.4.1 แทนที่จะ hardcode สามค่านี้ไว้หลายที่ pattern Kubernetes label/downward-API เดียวกันจาก Foundations ตั้งค่าครั้งเดียวบน pod แล้ว reflect เข้า environment variable
apiVersion: apps/v1kind: Deploymentmetadata: name: checkout-servicespec: template: metadata: labels: tags.datadoghq.com/env: "production" tags.datadoghq.com/service: "checkout-service" tags.datadoghq.com/version: "2.4.1" spec: containers: - name: checkout-service image: registry.example.com/checkout-service:2.4.1 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 คือ single source of truth downward API reflect มันเข้าไปใน DD_ENV/DD_SERVICE/DD_VERSION ทำให้ environment variable พวกนี้ตรงกับสิ่งที่ Kubernetes — และ autodiscovery ของ Agent — เห็นบน pod อยู่แล้วเสมอ เปลี่ยน label ครั้งเดียว แล้วทุก consumer ที่อยู่ downstream ได้ค่าใหม่ใน rollout ถัดไป
สาม consumer หนึ่ง identity
หัวข้อที่มีชื่อว่า “สาม consumer หนึ่ง identity”- Agent’s infrastructure/container metrics — autodiscovery แท็ก container กับ infra metric ตรงจาก label ของ pod ทำให้ metric CPU, memory, request-rate ของ pod
checkout-serviceพกenv,service, และversionโดยไม่ต้อง config แยก - tracer initialization สำหรับ APM —
dd-traceอ่านDD_ENV,DD_SERVICE, และDD_VERSIONจาก environment อัตโนมัติ ทำให้ span ทุกตัวที่มันสร้างถูกแท็กด้วย triple เดียวกัน
// dd-trace picks up DD_ENV, DD_SERVICE, and DD_VERSION from the environment automaticallyrequire('dd-trace').init({ logInjection: true});- log integration จากบทก่อนหน้า — เพราะ log injection อ่าน active service configuration ตัวเดียวกับที่ tracer ใช้ ฟิลด์
dd.env,dd.service, และdd.versionที่แปะลงบน log ทุกบรรทัดก็คือ triple ตัวเดียวกันนี้ด้วย
พิสูจน์: case mismatch ทำให้ join พังเงียบ ๆ
หัวข้อที่มีชื่อว่า “พิสูจน์: case mismatch ทำให้ join พังเงียบ ๆ”การ pivot ทำงานได้ก็เพราะ สตริง เหมือนกันเป๊ะทั้งสามผลิตภัณฑ์ — และปรากฏว่าไม่มีอะไรใน client library normalize สตริงนั้นให้เราเลย รัน dd-trace ด้วย DD_ENV=Prod (P ตัวใหญ่) แล้ว dump config จริงที่ resolve แล้วของ tracer แทนที่จะสมมติว่ามันถูก lowercase ให้:
$ DD_ENV=Prod DD_SERVICE=checkout-Service DD_VERSION=2.4.1 node -e " const t = require('dd-trace').init({ startupLogs: false }); console.log('tags:', JSON.stringify(t._tracer._config.tags)); "tags: {"service":"checkout-Service","env":"Prod","version":"2.4.1", ...}Prod กับ checkout-Service ออกไปทาง wire ตรงตามที่พิมพ์เป๊ะ — dd-trace ไม่ lowercase หรือ normalize อะไรฝั่ง client เลย เอกสาร tagging ของ Datadog เอง (docs.datadoghq.com/getting_started/tagging/) ระบุตรง ๆ ว่า “tag key ตัวเดียวมีหลายค่าได้ ถ้าถูก config ต่างกันในแต่ละ source” — platform ไม่ merge env:Prod กับ env:production เข้าเป็นค่าเดียว มันอยู่คู่กันเป็นสองค่าแยกกันใต้ key เดียวกัน ดังนั้นถ้า Kubernetes label ใน manifest ข้างบนเป็น tags.datadoghq.com/env: "Prod" ในขณะที่ service ที่จัดการด้วย Terraform ของเพื่อนร่วมทีมข้าง ๆ ตั้ง DD_ENV=production (lowercase) สอง service นี้ไม่ fail แบบดัง ๆ — พวกมันแค่ไปจบที่ facet env คนละอันเงียบ ๆ dashboard ที่ scope เป็น env:production จะแสดงแค่ service เดียวเงียบ ๆ ไม่แสดงอีกตัว; APM service page สำหรับ env:production ก็ไม่ list service ที่ mismatch เลย นี่คือบั๊กประเภทเดียวกับ typo logInjection จากบทก่อนหน้า: ไม่มี exception มีแต่ filter ที่กีดกันข้อมูลออกไปเงียบ ๆ โดยที่ไม่มีใครตั้งใจ เอกสาร tagging ของ Datadog มีระบุแยกไว้ว่า tag key ถูก normalize เป็น lowercase (“Span tags and metric tags are normalized to lowercase, so avoid using camel case in tag keys”) — แต่กฎนั้นพูดถึง key (env, service) ไม่ใช่ value ซึ่งเป็นช่องว่างที่พิสูจน์ข้างบนนี้เผยออกมาเป๊ะ
Agent-level tags: block tags: ของ datadog.yaml เอง
หัวข้อที่มีชื่อว่า “Agent-level tags: block tags: ของ datadog.yaml เอง”unified service tagging เป็นแบบต่อ-deployable-unit; Agent เองก็มี list tags: ของตัวเองที่ครอบทั้ง host ใน datadog.yaml ใช้กับทุก metric/check ที่ Agent เก็บเองบน host นั้น แยกอิสระจาก DD_ENV/DD_SERVICE/DD_VERSION ของ service ตัวไหนตัวหนึ่ง:
api_key: "<DD_API_KEY>"hostname: probe-hosttags: - team:checkout - datacenter:local-probeรัน container datadog/agent:7 จริง (docker run --name dd-corr, DD_API_KEY เป็น fake ศูนย์ล้วน, DD_DD_URL ชี้ไปที่ local port ที่ปิดอยู่เลยไม่มีอะไรถูกส่งออกไปจริง) พร้อม mount ไฟล์นี้เป๊ะ แล้วรัน agent status:
$ docker exec dd-corr agent status===============Agent (v7.83.3)===============...Hostname======== hostname: probe-host host tags: datacenter:local-probe team:checkoutAgent จริง output จริง: entry tags: สองตัวใน datadog.yaml โผล่มาเป็น “host tags” เป๊ะตัวอักษร — ใช้ทั้ง host โดยรวม (ไม่ใช่แทนที่) ไม่ว่า service ไหนบน host นั้นจะตั้ง env/service/version ของตัวเองยังไง
Container labels: com.datadoghq.tags.* กับ Kubernetes tags.datadoghq.com/*
หัวข้อที่มีชื่อว่า “Container labels: com.datadoghq.tags.* กับ Kubernetes tags.datadoghq.com/*”Kubernetes label ที่ใช้ก่อนหน้านี้ในบทนี้มี equivalent สำหรับ Docker Compose/plain Docker ตรง ๆ — container label com.datadoghq.tags.env/com.datadoghq.tags.service/com.datadoghq.tags.version (docs.datadoghq.com/getting_started/tagging/unified_service_tagging/) เริ่ม container ธรรมดาด้วย label พวกนั้นแล้วชี้ Autodiscovery ของ dd-corr Agent ตัวเดิมที่ใช้ docker.sock ไปที่มัน — ไม่มี config file ไม่มี annotation มีแค่ label บน docker run:
$ docker run -d --name labeled-app \ --label com.datadoghq.tags.env=production \ --label com.datadoghq.tags.service=checkout-service \ --label com.datadoghq.tags.version=2.4.1 \ alpine:3 sleep 300
$ docker exec dd-corr agent tagger-list | grep -A2 'container_name:labeled-app'Tags: [container_id:e0f42036... container_name:labeled-app docker_image:alpine:3env:production ... service:checkout-service ... version:2.4.1 ...]agent tagger-list (command จริงที่ dump ทุกอย่างที่ tagger ของ Agent resolve ไว้สำหรับทุก entity ที่มันเห็น) แสดง env:production, service:checkout-service, และ version:2.4.1 แปะบน container ล้วน ๆ จาก Docker label ของมัน — convention สามคีย์เดียวกับ Kubernetes label แปลงสำหรับ deployment แบบ plain-Docker หรือ Compose ที่ไม่มี Kubernetes downward API มา reflect label เข้า env var
service override บน span เดียว
หัวข้อที่มีชื่อว่า “service override บน span เดียว”unified service tagging ตั้งค่า default ให้ทั้งโปรเซส แต่ span เดียวยังประกาศ service ต่างออกไปได้ — มีประโยชน์กับ span ของ database client ที่ควรจะ roll up ใต้ identity ของ database เองแทนที่จะเป็นของแอปที่เรียก:
$ DD_SERVICE=checkout-service DD_ENV=production node -e " const tracer = require('dd-trace').init({ startupLogs: false }); tracer.trace('pg.query', { service: 'checkout-postgres' }, (span) => { console.log(JSON.stringify(span.context()._tags)); }); "{"service.name":"checkout-postgres","service":"checkout-service","env":"production", ...}tag service.name ของ span เองคือ checkout-postgres (override ที่ส่งเข้า option ของ tracer.trace()) ในขณะที่ tag service ของทั้งโปรเซสยังเป็น checkout-service อยู่ข้างใต้ — เป็นข้อยกเว้นเฉพาะจุดของกฎ “ตั้งครั้งเดียว” ข้างบน ไม่ใช่การขัดแย้งกัน: span ส่วนใหญ่ในโปรเซสยัง default ไปที่ identity ทั้งโปรเซสอยู่ มีแค่ span เดียวที่เลือก opt out ถึงจะพก service ต่างออกไป
การ pivot: triple เดียว สามผลิตภัณฑ์ ไม่ต้อง cross-reference
หัวข้อที่มีชื่อว่า “การ pivot: triple เดียว สามผลิตภัณฑ์ ไม่ต้อง cross-reference”พอ triple ไหลเข้าทั้งสามผลิตภัณฑ์ การเดินตาม incident ครั้งหนึ่งจะหน้าตาแบบนี้: เริ่มที่ metrics dashboard scope เป็น service:checkout-service env:production เห็น CPU, latency, request rate ไต่สูงขึ้น กระโดดไปที่ APM service page สำหรับ service, env, และ version เดียวกัน เห็น trace latency, error rate, และเพื่อนบ้านของ service นี้บน service map กระโดดอีกครั้งไปที่ Logs Explorer ที่ pre-filter ด้วย triple เดียวกัน เห็น log บรรทัดที่ตรงเป๊ะ — รวมถึง error ที่แท็กด้วย dd.trace_id จากบทก่อนหน้า — สำหรับ version นั้น ใน environment นั้น
เทียบกับสถานะก่อนหน้า: metric แท็ก service:checkout, Java tracer แท็ก service:checkout-svc, และ log แท็ก application:checkout ไม่มีอะไรตรงกันเลย แล้วใครก็ตามที่ debug ต้องจำสามชื่อต่างกันตอนย้ายไปมาระหว่างผลิตภัณฑ์ พอแท็กสม่ำเสมอแล้ว สตริงเหมือนกันทุกที่ UI ของทั้งสามผลิตภัณฑ์เลย pre-filter ไปที่ identity เดียวกันได้ และ saved view ในผลิตภัณฑ์หนึ่งอธิบาย slice ของโลกเดียวกันเป๊ะในอีกผลิตภัณฑ์หนึ่ง
flowchart LR T["env=production\nservice=checkout-service\nversion=2.4.1"] --> Agent[Agent autodiscovery] T --> Tracer[Tracer init] T --> LogInj[Log injection] Agent --> M[Metrics dashboard] Tracer --> A[APM service page] LogInj --> L[Logs Explorer] M -.pivot.-> A A -.pivot.-> L L -.pivot.-> M
Reading AI-generated code
หัวข้อที่มีชื่อว่า “Reading AI-generated code”🐛 หา bug ในโค้ดนี้
metadata:labels: tags.datadoghq.com/env: "Production" tags.datadoghq.com/service: "checkout-service" tags.datadoghq.com/version: "2.4.1"---# k8s/checkout-worker.yaml (AI comment: "same env as checkout-service")metadata:labels: tags.datadoghq.com/env: "production" tags.datadoghq.com/service: "checkout-worker" tags.datadoghq.com/version: "2.4.1"เฉลย
ทั้งสอง manifest ดูสอดคล้องกันแวบแรก และทั้งคู่เป็น YAML ที่ valid สมบูรณ์ — ไม่มีอะไรตรงนี้ throw error หรือทำให้ kubectl apply fail บั๊กที่พิสูจน์แล้วข้างบนด้วย tracer config dump จริง: "Production" กับ "production" ไม่ใช่ tag value เดียวกัน checkout-service กับ checkout-worker จะไปจบที่ facet env คนละอันใน Datadog dashboard, monitor, หรือ SLO ที่ scope เป็น env:production จะรวม checkout-worker เข้าไปเงียบ ๆ และตัด checkout-service ออก — service ที่คนส่วนใหญ่น่าจะตั้งใจรวมเข้าไปมากที่สุด การแก้เป็นเรื่องกลไกล้วน ๆ: เลือก casing convention เดียว (lowercase คือ default ที่ปลอดภัยที่สุด ตรงกับที่ Datadog ปฏิบัติกับ tag key) แล้วใช้ให้เหมือนกันทุกที่ที่ tags.datadoghq.com/env (หรือ DD_ENV) ถูกตั้ง ทุก manifest ทุกไฟล์ Terraform ทุก label ใน Dockerfile ทั้งองค์กร
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”มองว่า triple env/service/version เป็นเหมือนรหัสผ่านที่แชร์กันข้ามสามระบบที่ไม่เคยคุยกันตรง ๆ Metrics, APM, และ Logs คือสามผลิตภัณฑ์แยกกัน มี storage และ query engine แยกกัน เหตุผลเดียวที่ dashboard ในตัวหนึ่ง pre-filter ไปที่ “สิ่งเดียวกัน” กับ service page ในอีกตัวได้ก็เพราะทั้งสามบังเอิญได้รับสตริงเดียวกันเป๊ะ ไม่มีอะไรบังคับความเหมือนนั้นจากส่วนกลางเลย ไม่มี registry ที่บอกว่า “พวกนี้คือ service เดียวกัน” มันทำงานได้ด้วย convention ล้วน ๆ — convention เดียวกับที่บทนี้แสดงให้เห็นว่าไหลผ่าน Kubernetes label, Docker label, และ environment variable — และมันพังตรงจุดที่ convention นั้นถูกใช้ไม่สม่ำเสมอ เงียบ ๆ ไม่มี error ที่ไหนใน pipeline เลย
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”- Kubernetes label + downward API เทียบกับ Docker label (
com.datadoghq.tags.*) — pattern downward API ให้ single source of truth ตัวเดียว (pod label) แล้ว reflect เข้า env var ให้ container ไหนก็ได้ใน pod อัตโนมัติ; Docker/Compose label (พิสูจน์แล้วข้างบนผ่านagent tagger-list) ไม่ต้องมีกลไกแบบ downward-API เลย แต่ทำงานได้แค่ตอนที่ Agent เข้าถึง Docker socket สำหรับ Autodiscovery และไม่ reflect เข้า environment ของแอปเองอัตโนมัติ - identity ทั้งโปรเซส เทียบกับ
serviceoverride เฉพาะ span — ปล่อยให้ทุก span ใช้serviceทั้งโปรเซสทำให้ service map เรียบง่าย หนึ่ง node ต่อหนึ่ง deployable unit; overrideserviceบาง span (พิสูจน์แล้วข้างบน spanpg.queryแท็กcheckout-postgres) ให้ service map แม่นยำกว่า (database โผล่เป็น node ของตัวเอง) แลกกับทุกคนที่อ่านต้องรู้จัก convention นี้ก่อนถึงจะเข้าใจ service map - บังคับ tag value เป็น lowercase ด้วย convention เทียบกับด้วย tooling — กฎ lowercase-only ที่เขียนไว้ในเอกสารไม่มีต้นทุนอะไรเลย แต่ไม่มีอะไรหยุด Terraform module ใหม่หรือไฟล์ YAML ที่ copy-paste มาจากทำผิดกฎ (พิสูจน์แล้วข้างบน); CI lint step ที่ปฏิเสธค่า
tags.datadoghq.com/env/DD_ENV/DD_SERVICE/DD_VERSIONที่มีตัวพิมพ์ใหญ่ปนอยู่จับได้ก่อน merge แลกกับอีกหนึ่ง check ที่ทุก deploy ต้องผ่าน
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “Datadog merge
env:Prodกับenv:productionเข้าเป็นค่าเดียวอัตโนมัติ” ไม่จริง — พิสูจน์แล้วข้างบนด้วย tracer config dump จริงและเอกสาร tagging ของ Datadog เอง: casing ที่ mismatch ทำให้เกิด tag value สองอันที่อยู่คู่กันใต้ key เดียวกัน ไม่ใช่ค่าเดียวที่ merge - “tag value ถูก lowercase เหมือนกับ tag key” ไม่จริง — เอกสารของ Datadog ระบุว่า tag key ถูก normalize เป็น lowercase; เอกสารไม่ได้อ้างแบบนั้นกับ value เลย และการพิสูจน์ฝั่ง client ข้างบนแสดงว่า
dd-traceส่งค่าenv/service/versionตามที่ config ไว้เป๊ะ รวมตัวพิมพ์ใหญ่ด้วย - “ตั้ง
serviceบน span เดียว เปลี่ยน identity ของ service ทั้งโปรเซส” ไม่จริง — พิสูจน์แล้วข้างบน: overrideserviceใน option ของtracer.trace()เปลี่ยนแค่ tagservice.nameของ span นั้นตัวเดียว ค่าDD_SERVICEทั้งโปรเซสไม่ถูกแตะและยังใช้กับ span อื่นทุกตัว - “Container label เป็นของ Kubernetes อย่างเดียว plain Docker/Compose ไม่มี equivalent” ไม่จริง — พิสูจน์แล้วข้างบน: label
com.datadoghq.tags.env/service/versionของ Docker ถูก Autodiscovery ของ Agent ที่ใช้docker.sockจับได้เหมือน Kubernetes pod label เป๊ะ แค่ผ่านกลไกคนละแบบ (ไม่ต้องมี downward API)