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

จัดการ Datadog เป็น Code ด้วย Terraform

Terraform Datadog provider ตัวทางการ ทำให้คุณ define monitor, dashboard, SLO, synthetic, log pipeline และแม้แต่ “ใครมีสิทธิ์แก้ของเหล่านี้” เป็น code ที่ versioned และ review ได้ แทนที่จะคลิกประกอบเองทีละ environment ใน UI

คลิกประกอบ monitor ใน UI ครั้งแรกก็เร็วดี ปัญหาจะโผล่มาตอนครั้งที่สอง — คุณต้องการ monitor ตัวเดียวกันใน staging, dashboard ตัวเดียวกันใน region ใหม่ หรือต้องรู้ให้ได้ว่า threshold ที่เปลี่ยนไปเมื่อเดือนก่อนคืออะไรและทำไม config ที่ประกอบผ่าน UI ไม่มี diff ไม่มีขั้นตอน review และไม่มี single source of truth — สองคนอาจเข้าใจ threshold ของ monitor ไม่ตรงกันแบบเงียบ ๆ แล้วไม่มีอะไรจับได้เลย

การจัดการ monitor, dashboard, SLO และอื่น ๆ ผ่าน Terraform Datadog provider เปลี่ยนทุกอย่างนี้ให้เป็น code change — config อยู่ใน repository, ถูก review ผ่าน pull request เหมือน change อื่น ๆ ที่ลง production และ module เดียวกันนำไป instantiate ซ้ำได้ทั้ง prod, staging และทุก service ใหม่ที่เพิ่มเข้ามา แค่เปลี่ยน variable แทนที่จะคลิก wizard ใหม่ทุกครั้ง

นี่คือ metric alert monitor ที่เขียนเป็น Terraform

terraform/high-cpu-monitor.tf
resource "datadog_monitor" "high_cpu" {
name = "High CPU on checkout-service"
type = "metric alert"
message = "CPU is high on checkout-service. Notify: @slack-sre-alerts"
query = "avg(last_5m):avg:system.cpu.user{service:checkout-service} by {host} > 85"
monitor_thresholds {
warning = 70
warning_recovery = 65
critical = 85
critical_recovery = 80
}
notify_no_data = true
no_data_timeframe = 20
tags = ["service:checkout-service", "team:checkout"]
}
  • type — metric alert เป็นหนึ่งใน monitor type หลายแบบ (ตัวอื่นมี log alert, apm alert และ cost alert ที่จะเจอในบทถัดไป)
  • message — เนื้อหาที่แจ้งเตือน handle @slack-sre-alerts เป็น notification target ของ Datadog resolve เหมือนกับที่พิมพ์เข้าไปใน message box ของ UI ทุกอย่าง
  • query — เงื่อนไข alert จริง ๆ ประเมินเป็น avg(last_5m):avg:system.cpu.user{service:checkout-service} by {host} > 85
  • monitor_thresholds.critical/.warning — threshold ปกติที่ trigger alert ซึ่งใน UI คุณต้องพิมพ์หรือลากเอง
  • monitor_thresholds.critical_recovery/.warning_recovery — เส้นที่ แยกออกมาและต่ำกว่า ซึ่ง metric ต้องกลับลงมาต่ำกว่าก่อน monitor จึงจะเคลียร์ alert state ยืนยันกับ schema จริงของ provider (terraform providers schema -json, block datadog_monitor.monitor_thresholds): critical_recovery และ warning_recovery เป็น attribute จริงที่แยกจาก critical/warning ไม่ใช่สิ่งที่ต้องอนุมานจาก threshold ตัวเดียว
  • notify_no_data / no_data_timeframe — ให้ monitor alert หรือไม่เมื่อ query หยุดคืนข้อมูลไปเลย (ต่างจากคืนข้อมูลที่ต่ำกว่า threshold) และให้ทนความเงียบได้กี่นาทีก่อน ยืนยันแล้วว่าเป็น top-level attribute จริงบน schema เดียวกัน
  • tags — tag ปกติของ Datadog ที่ติดกับตัว monitor เอง ใช้ filter หน้า Manage Monitors และ route ผ่าน Service Catalog ownership ได้

ทำไม recovery threshold ถึงสำคัญ แบบเป็นรูปธรรม: ถ้าไม่มี critical_recovery monitor ที่ critical = 85 จะพลิกเป็น Alert ทันทีที่ CPU ข้าม 85% และพลิกกลับเป็น OK ทันทีที่ลดเหลือ 84.9% — ถ้า CPU แกว่งอยู่รอบ 85 monitor ก็จะ flapping ระหว่าง Alert กับ OK ทุกรอบ evaluation และส่ง notification ทุกครั้งที่พลิก การตั้ง critical_recovery = 80 หมายความว่า CPU ต้องลดลงต่ำกว่าจุด trigger จริง ๆ ไม่ใช่แค่ต่ำกว่าเฉียด ๆ monitor ถึงจะเคลียร์ — ช่วง 85 ถึง 80 คือ hysteresis band ที่ตั้งใจเว้นไว้

ข้อควรระวังสำคัญ: string query ใน resource datadog_monitor ไม่ใช่ syntax เดียวกับที่พิมพ์ใน monitor query builder ของ UI UI builder เป็น widget ที่ช่วยประกอบ query ให้ทีละขั้น แต่ field query ใน Terraform คือ API query syntax ดิบ ๆ ต้องเขียนเองให้ถูกต้องตั้งแต่รอบแรก โดย default terraform plan จะส่ง query นี้ไปที่ API ของ Datadog เพื่อ validate ดังนั้น query ที่ผิดรูปแบบจะ fail ตั้งแต่ตอน plan ไม่ใช่ไปสร้าง monitor พังแบบเงียบ ๆ ถ้าจำเป็นต้องข้าม check นี้จริง ๆ — เช่น อ้างถึง metric ที่ยังไม่มีใน environment นี้ — ตั้ง validate = false บน resource ได้ แต่นั่นคือการแลก safety net ทิ้งไป ควรเป็นข้อยกเว้น ไม่ใช่ default

บทเรียน dashboard จากโมดูล Infrastructure & Metrics คุยเรื่อง named query, formula และ template variable ในมุมของ UI widget model เดียวกันนี้โผล่มาใน Terraform ผ่าน datadog_dashboard และรูปทรงของ block request คือจุดที่ตัวอย่างที่ AI generate พังบ่อยที่สุด

ด้วย layout_type = "ordered" widget แต่ละตัวจะเรียงซ้อนบนลงล่างตามลำดับที่ declare ไว้ — ตัวเลือกที่ง่ายที่สุด และเป็น default ที่ดี นี่คือรูปแบบที่ ถูกต้องและ validate แล้ว

terraform/checkout-dashboard.tf
resource "datadog_dashboard" "checkout_overview" {
title = "Checkout Service Overview"
layout_type = "ordered"
widget {
timeseries_definition {
request {
query {
metric_query {
name = "query1"
data_source = "metrics"
query = "avg:system.cpu.user{service:checkout-service} by {host}"
}
}
formula {
formula_expression = "query1"
}
display_type = "line"
}
}
}
}

ด้วย layout_type = "free" widget ทุกตัวต้องมี block widget_layout ของตัวเอง ให้ตำแหน่งแบบ pixel-grid ที่แน่นอนด้วย x, y, width และ height

terraform/checkout-dashboard-free.tf
resource "datadog_dashboard" "checkout_overview_free" {
title = "Checkout Service Overview (free layout)"
layout_type = "free"
widget {
timeseries_definition {
request {
query {
metric_query {
name = "query1"
data_source = "metrics"
query = "avg:system.cpu.user{service:checkout-service} by {host}"
}
}
formula {
formula_expression = "query1"
}
display_type = "line"
}
}
widget_layout {
x = 0
y = 0
width = 4
height = 2
}
}
}

ทั้งสองอันข้างบน: terraform validate → Success! The configuration is valid. รูปทรงคือ request → block query → block metric_query (ที่ name/data_source/query อยู่จริง) → block formula ระดับเดียวกับ query (formula_expression อ้างถึง name ของ query) → display_type กลับมาอยู่ระดับ request ไม่ใช่ใน query นี่คือการแยก request/query/formula เดียวกับคู่ formulas/queries ของ JSON dashboard API — Terraform แค่ซ้อนลึกขึ้นอีกหนึ่ง block

ordered เป็น default ที่เหมาะกับ dashboard operational ส่วนใหญ่ — ไม่ค่อยต้องมาสู้กับตำแหน่ง pixel สำหรับ dashboard ที่ส่วนใหญ่ scroll ดูบนลงล่างอยู่แล้ว free คุ้มค่าตอนทำ dashboard ติดจอ TV-wall หรือ layout ที่ตั้งใจ design ให้ widget อยู่ตำแหน่งสัมพัทธ์กันแบบเจาะจง

ใน Terraform มีอีกวิธีจัดการ dashboard: datadog_dashboard_json รับ body ทั้งหมดของ dashboard เป็น JSON string เดียวผ่าน jsonencode(...) แทนการสร้าง HCL tree ทีละ widget

terraform/checkout-dashboard-json.tf
resource "datadog_dashboard_json" "checkout_from_json" {
dashboard = jsonencode({
title = "Checkout Service Overview (JSON)"
layout_type = "ordered"
widgets = [
{
definition = {
type = "timeseries"
requests = [
{
queries = [
{
name = "query1"
data_source = "metrics"
query = "avg:system.cpu.user{service:checkout-service} by {host}"
}
]
formulas = [{ formula = "query1" }]
display_type = "line"
}
]
}
}
]
})
}

validate แบบเดียวกัน — terraform validate ผ่าน สังเกตว่า JSON body นี้ใช้รูปแบบ flat (name/data_source/query เป็น sibling ใน object queries[] เดียวกัน) — ซึ่งถูกต้องตรงนี้ เพราะนี่คือ payload shape จริงของ Dashboards JSON API ไม่ใช่ HCL block tree bug ก่อนหน้านี้คือการเอา flat shape ของ JSON API ไปใช้ ใน syntax block ของ HCL; datadog_dashboard_json คือที่ที่ flat shape นั้นอยู่ถูกที่

  • datadog_dashboard — ทุก widget และ query เป็น HCL block ที่มี type schema ของ Terraform เองจะ validate โครงสร้าง (นี่คือสิ่งที่จับ bug ข้างบนได้ตอน terraform validate ก่อนแตะ API จริง) diff ใน pull request อ่านเป็นโครงสร้าง: “display_type ของ widget 3 เปลี่ยนจาก line เป็น bars” อ่านชัด
  • datadog_dashboard_json — JSON blob ทึบก้อนเดียว คุณเสีย schema validation ระดับ field (typo ใน JSON string จะถูกจับโดย API ตอน apply หรือ Terraform ไม่จับเลย — jsonencode รับประกันแค่ syntax JSON ที่ถูก ไม่ใช่ dashboard payload ที่ถูก) แต่ได้ความสามารถในการ paste dashboard ที่ export จาก UI (Dashboard settings → Export dashboard JSON) ได้ตรง ๆ โดยไม่ต้องแปลงทุก widget เป็น HCL block ด้วยมือ

trade-off ในทางปฏิบัติ: dashboard ใหม่ที่เขียนเองเขียนให้ถูกและ review ง่ายกว่าเป็น datadog_dashboard; dashboard ที่ใครสักคนคลิกสร้างไว้แล้วและตอนนี้อยากเอาเข้า version control เร็วกว่ามากถ้านำเข้าเป็น datadog_dashboard_json

flowchart LR
  A[Write datadog_monitor / datadog_dashboard in HCL] --> B[terraform plan]
  B --> C{Query validation}
  C -->|valid, or validate = false| D[Pull request review]
  C -->|invalid| E[Plan fails: fix query]
  D --> F[terraform apply]
  F --> G[Monitor / Dashboard / SLO created in Datadog]
  G --> H[Reused across prod, staging, new services via variables]
จาก HCL สู่ resource จริงใน Datadog

บท SLOs (โมดูล Monitors, Alerting, and SLOs) คุยเรื่อง SLO สามประเภทและรูปแบบ query จริงของ burn_rate()/error_budget() เป็น raw JSON นี่คือ SLO แบบ metric-based ตัวเดียวกันเป็น Terraform

terraform/checkout-slo.tf
resource "datadog_service_level_objective" "checkout_availability" {
name = "Checkout API availability"
type = "metric"
description = "Ratio of non-5xx checkout-api requests"
query {
numerator = "sum:trace.http.request.hits{service:checkout-api,!http.status_code:5*}.as_count()"
denominator = "sum:trace.http.request.hits{service:checkout-api}.as_count()"
}
thresholds {
timeframe = "30d"
target = 99.9
}
tags = ["service:checkout-api", "team:checkout"]
}

terraform validate ผ่าน รูปทรงของ block ใกล้เคียง JSON payload — query.numerator/query.denominator และ block thresholds ที่มี timeframe/target — ความต่างหลักจากการคลิกใน SLO editor ของ UI คือทั้ง SLO และ monitor burn-rate/error-budget alert (จากบทก่อน) อยู่ใน pull request ที่ review แล้วอันเดียวกันได้

บท Synthetics & Uptime Checks ครอบคลุม datadog_synthetics_test ลึกไปแล้ว รวมถึง error จริงของ terraform validate สำหรับ type ที่ไม่ถูกต้อง ประเด็นเรื่อง as-code ที่ควรย้ำ: synthetic test เป็นแค่ resource อีกตัวในไฟล์เดียวกับ monitor และ dashboard ดังนั้น “checkout-api มี monitor, dashboard และ uptime check” เป็นหนึ่ง change ที่ review ครั้งเดียวได้ แทนที่จะเป็นสามขั้นตอนแมนนวลแยกกันข้ามสามหน้าจอ UI

terraform/checkout-uptime.tf
resource "datadog_synthetics_test" "checkout_api_up" {
name = "checkout-api uptime"
type = "api"
subtype = "http"
status = "live"
message = "@pagerduty-checkout-critical checkout-api synthetic failed"
locations = ["aws:eu-central-1"]
request_definition {
method = "GET"
url = "https://checkout.example.com/health"
}
assertion {
type = "statusCode"
operator = "is"
target = "200"
}
options_list {
tick_every = 60
}
}

terraform validate ผ่านกับ provider ที่ pin ไว้

โมดูล Log Management ครอบคลุม Processing Pipeline และ index ในมุมของ UI ทั้งสองเป็น Terraform resource ปกติเหมือนกัน

terraform/checkout-log-pipeline.tf
resource "datadog_logs_custom_pipeline" "checkout_pipeline" {
name = "checkout-api pipeline"
is_enabled = true
filter {
query = "service:checkout-api"
}
processor {
grok_parser {
name = "Parse checkout-api log line"
is_enabled = true
source = "message"
grok {
match_rules = "rule %%{date(\"yyyy-MM-dd'T'HH:mm:ss\"):timestamp} %%{word:log_level} %%{data:msg}"
support_rules = ""
}
}
}
processor {
date_remapper {
name = "Define timestamp as the official date"
is_enabled = true
sources = ["timestamp"]
}
}
processor {
status_remapper {
name = "Define log_level as the official status"
is_enabled = true
sources = ["log_level"]
}
}
}
terraform/checkout-log-index.tf
resource "datadog_logs_index" "checkout_index" {
name = "checkout-api"
filter {
query = "service:checkout-api"
}
exclusion_filter {
name = "drop-debug-logs"
is_enabled = true
filter {
query = "status:debug"
sample_rate = 0
}
}
daily_limit = 5000000
retention_days = 15
}
resource "datadog_logs_index_order" "index_order" {
name = "checkout index order"
indexes = [
datadog_logs_index.checkout_index.id,
"main",
]
}

terraform validate ผ่านทั้งสองอัน datadog_logs_index_order สำคัญด้วยเหตุผลที่พลาดง่าย: log index ถูก evaluate ตามลำดับ และ index แรกที่ filter.query match จะ claim log นั้น — datadog_logs_index_order.indexes คือลำดับแบบ literal ที่ระบุชัด โดยอ้าง id ของ index ใหม่คู่กับ index เดิมชื่อ "main" ถ้าลำดับผิด index ที่แคบและสร้างมาเฉพาะอย่าง checkout-api อาจไม่ได้รับ log เลยแม้แต่ตัวเดียวแบบเงียบ ๆ เพราะ index main ที่กว้างกว่า (ถ้าอยู่ก่อน) claim ทุกอย่างไปก่อนที่ filter ของ checkout-api จะถูก evaluate

ความสัมพันธ์เดียวกับ datadog_dashboard เทียบ datadog_dashboard_json — datadog_monitor_json รับ body ทั้งหมดของ monitor เป็น JSON string เดียว

terraform/checkout-monitor-json.tf
resource "datadog_monitor_json" "high_cpu_json" {
monitor = jsonencode({
name = "High CPU on checkout-service (JSON)"
type = "metric alert"
query = "avg(last_5m):avg:system.cpu.user{service:checkout-service} by {host} > 85"
message = "CPU is high on checkout-service."
tags = ["service:checkout-service"]
options = {
thresholds = {
critical = 85
warning = 70
}
notify_no_data = true
}
})
}

terraform validate ผ่าน trade-off เดียวกับคู่ dashboard: datadog_monitor ให้ HCL block ที่ schema validate แล้ว; datadog_monitor_json เร็วกว่าสำหรับ paste monitor ที่ export จาก UI โดยแลกกับการเสีย structural validation ของ Terraform บน JSON body นั้น

ทุก resource ที่ผ่านมาสมมติว่าคุณสร้างของใหม่ resource ของ Datadog ที่สร้างด้วยมือใน UI ก่อนที่คุณจะใช้ Terraform ไม่ต้องสร้างใหม่ — import เข้า Terraform state ได้ เพื่อให้ Terraform เริ่มจัดการสิ่งที่มีอยู่แล้ว

$ terraform import datadog_monitor.bytes_received_localhost 2081

นี่คือ syntax import จริงจากเอกสารของ provider เอง (terraform-provider-datadog/docs/resources/monitor.md แท็ก v4.22.0): resource address (datadog_monitor.bytes_received_localhost — ต้องมีอยู่แล้วเป็น block ว่าง resource "datadog_monitor" "bytes_received_localhost" {} ในไฟล์ .tf ของคุณ) ตามด้วย numeric ID ของ monitor จาก Datadog UI หรือ API datadog_monitor_json import แบบเดียวกัน — ด้วย numeric ID ของ resource ตรง ๆ เพราะ source ของ provider (resource_datadog_monitor_json.go) ต่อสายด้วย schema.ImportStatePassthroughContext แปลว่า ID ที่ส่งทาง command line ถูกใช้ตามที่เป็นเพื่อดึง monitor

การ import เติมแค่ state ของ Terraform — ไม่ได้ generate HCL ให้ หลัง terraform import ให้รัน terraform plan: ความต่างใด ๆ ระหว่าง resource block ที่คุณเขียนกับสิ่งที่มีอยู่จริงใน Datadog จะโผล่เป็น planned change ซึ่งเป็นสัญญาณให้แก้ HCL ให้ตรงกับความจริง (หรือ accept plan ให้ Terraform reconcile) ก่อนที่ resource นี้จะอยู่ใต้ code review อย่างปลอดภัย

ทุกตัวอย่างข้างบนสมมติว่ามี block provider "datadog" อยู่แล้ว ในทางปฏิบัติ นี่คือที่มาของ credential ปลอมในการ validate ของบทนี้เอง

# This IS the provider block -- not collected by the harness's --tf mode
# (which generates its own providers.tf per fence and would conflict with
# a second one), validated standalone in scratch with the exact fake
# all-zeros key/app-key this lesson uses everywhere else: terraform init +
# terraform validate -> Success.
terraform {
required_providers {
datadog = {
source = "DataDog/datadog"
version = "4.22.0"
}
}
}
provider "datadog" {
api_key = "<your-api-key>"
app_key = "<your-app-key>"
api_url = "https://api.datadoghq.eu/"
}
  • api_key/app_key — จำเป็น เว้นแต่ validate = false ตั้งผ่าน environment variable DD_API_KEY/DD_APP_KEY แทนการ hardcode ใน HCL ได้ด้วย (ยืนยันกับเอกสาร schema ของ provider เอง) — pattern มาตรฐานในการเก็บ credential จริงออกจากไฟล์ Terraform ที่ commit ลง repository
  • api_url — ไม่ได้ คุมด้วย DD_SITE เอกสารของ provider ชัดเจน: api_url “can also be set via the DD_HOST environment variable” (ไม่ใช่ DD_SITE) default เป็น https://api.datadoghq.com และห้ามลงท้ายด้วย /api/ ถ้า org ของคุณอยู่ EU site ใช้ https://api.datadoghq.eu/; region อื่นเป็น pattern เดียวกัน (https://api.us5.datadoghq.com/, https://api.us3.datadoghq.com/, https://api.ddog-gov.com/ — ดู docs.datadoghq.com/getting_started/site/ สำหรับรายการเต็ม) นี่คือ trap จริงที่เจอง่าย: DD_SITE คือ environment variable ที่ Agent และ tracer dd-trace/ddtrace อ่าน (ดูโมดูล Foundations) แต่ Terraform provider อ่านตัวแปรชื่อต่างกันสำหรับการตั้งค่าเทียบเท่า การตั้ง DD_SITE=datadoghq.eu ใน shell แล้วคาดว่า Terraform provider จะหยิบไปใช้เองจะไม่เกิดอะไรขึ้นเลยแบบเงียบ ๆ — มันยังคุยกับ datadoghq.com ต่อ เว้นแต่ตั้ง api_url/DD_HOST ให้ถูกด้วย

การ validate ของบทนี้ไม่เคยเรียก API จริงเลย — validate = false บน provider block บวก api_key/app_key ปลอมที่เป็นศูนย์ล้วน คือสิ่งที่ทำให้รัน terraform init + terraform validate ได้อย่างปลอดภัยโดยไม่มีบัญชี Datadog อยู่ใกล้เลย

ทุก fence ที่ผ่านมาเขียนให้ถูก นี่คืออันที่พังโดยตั้งใจ เพื่อให้เห็นว่า schema violation จริงหน้าตาเป็นยังไง แทนที่จะเป็น error ที่ฟังดูเหมือนแต่งขึ้น

# @expect-error terraform validate real error: Unsupported argument — An argument named "notify_no_data_timeframe" is not expected here.
resource "datadog_monitor" "high_cpu" {
name = "High CPU on checkout-service"
type = "metric alert"
message = "CPU is high on checkout-service."
query = "avg(last_5m):avg:system.cpu.user{service:checkout-service} by {host} > 85"
monitor_thresholds {
critical = 85
}
notify_no_data_timeframe = 20
}

รันผ่าน terraform init + terraform validate เดียวกับทุก fence ในบทนี้

Error: Unsupported argument
on main.tf line 11, in resource "datadog_monitor" "high_cpu":
11: notify_no_data_timeframe = 20
An argument named "notify_no_data_timeframe" is not expected here.

ไม่มี attribute แบบนี้ — ชื่อจริงที่ยืนยันกับ schema ก่อนหน้าในบทนี้คือ notify_no_data (boolean) กับ no_data_timeframe (number) แยกกัน notify_no_data_timeframe คือชื่อประเภทที่ AI model หรือคนที่เหนื่อยอาจประดิษฐ์ขึ้นจากการผสมสองชื่อจริงเข้าด้วยกัน และ schema validation ของ Terraform จับได้ทันที ก่อนที่ apply จะรันเสียอีก

datadog_role หรือ monitor ที่อ้าง query value อ่อนไหวมักไม่ใช่ความเสี่ยงหลัก — ตัว state file ของ Terraform ต่างหาก ทุก attribute ที่ Terraform อ่านกลับจาก API ของ Datadog รวมถึงสิ่งที่ provider เองถือว่า sensitive ถูกเขียนลง terraform.tfstate เป็น plain text โดย default schema ของ provider ทำเครื่องหมาย app_key (และ api_key, bearer_token) เป็น Sensitive: true ซึ่งซ่อนจาก CLI output (ข้อความ console ของ terraform plan/terraform apply) — แต่ Sensitive ใน provider schema ไม่ได้แปลว่า เข้ารหัสใน state ใครอ่าน raw state file ได้ก็อ่านค่าเหล่านี้ได้

คอร์สนี้ไม่สอน Terraform state management ซ้ำ — คอร์ส Terraform (AWS) ครอบคลุม remote state backend, state encryption และ team workflow สำหรับปัญหานี้ลึกอยู่แล้ว: ดู state-management/sensitive-data-and-team-workflows สิ่งที่เจาะจง Datadog ที่ควรจำ: block provider "datadog" ที่มี credential จริง ที่ apply โดยไม่มี remote backend ที่คุมสิทธิ์ดี จะเอา Datadog app key ของคุณ (ซึ่งตามบท RBAC ในโมดูลนี้ สามารถมีสิทธิ์ scoped เท่ากับ user จริง) ไปไว้ในไฟล์ที่ต้องการการปกป้องแบบเดียวกับ secret อื่น ๆ

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

# AI comment: "Dashboard widget showing checkout-service CPU"
resource "datadog_dashboard" "checkout_overview" {
title = "Checkout Service Overview"
layout_type = "ordered"
widget {
timeseries_definition {
request {
query {
name = "query1"
data_source = "metrics"
query = "avg:system.cpu.user{service:checkout-service} by {host}"
}
display_type = "line"
}
}
}
}
เฉลย

HCL นี้ syntax ถูก — parse ผ่าน — และดูเหมือนการแบน array queries[] ของ JSON dashboard payload อย่างสมเหตุสมผล แต่ไม่ valid กับ schema จริงของ datadog_dashboard พิสูจน์ไว้ก่อนหน้าในบทนี้: terraform validate กับ provider 4.22.0 ที่ pin ไว้ปฏิเสธ name, data_source และ query ว่าเป็น unsupported argument ตรง ๆ ใน block query เพราะ query ไม่มี attribute ของตัวเองเลย — มีแต่ block type ซ้อนลงไป และ metric_query (ไม่ใช่ชุด attribute แบบ flat) คือที่ที่ทั้งสามฟิลด์นี้อยู่ ควบคู่กับ block formula ระดับเดียวกันที่อ้าง query ด้วย name วิธีแก้ ซึ่ง validate แล้วเช่นกัน

terraform/checkout-dashboard-fixed.tf
resource "datadog_dashboard" "checkout_overview_fixed" {
title = "Checkout Service Overview"
layout_type = "ordered"
widget {
timeseries_definition {
request {
query {
metric_query {
name = "query1"
data_source = "metrics"
query = "avg:system.cpu.user{service:checkout-service} by {host}"
}
}
formula {
formula_expression = "query1"
}
display_type = "line"
}
}
}
}

ทั้งคอมเมนต์ที่ AI เขียนและการอ่าน HCL เร็ว ๆ ไม่เผยเรื่องนี้ — การซ้อน block ดูสมเหตุสมผลทั้งสองแบบ มีแค่ terraform validate กับ schema ของ provider ที่ pin ไว้จริงเท่านั้นที่จับได้

Terraform Datadog provider คือประตูหน้าแบบมี type ที่ต่อเข้ากับ REST API ตัวเดียวกับที่ UI เรียกเอง ทุก block resource "datadog_X" คือคำอธิบาย schema ของ Terraform เองสำหรับรูปทรงของ API object หนึ่งตัว — ซึ่งเป็นเหตุผลที่ terraform validate จับ widget query ของ datadog_dashboard ที่ผิดรูปได้โดยไม่ต้องติดต่อ Datadog เลย: มันเช็ก HCL ของคุณกับ schema ที่ compile ไว้ใน provider เอง ไม่ใช่กับ response ของ API จริง resource ที่ลงท้าย _json (datadog_dashboard_json, datadog_monitor_json) คือช่องทางหนีจาก schema นั้น: ส่ง JSON blob ดิบให้ provider แล้วข้ามโครงสร้างที่มี type ทั้งหมด โดยแลก structural validation กับความสามารถในการ paste สิ่งที่ UI export ให้ Import (terraform import) คือทิศทางย้อนกลับของไอเดียเดียวกัน — ดึง object ที่มีอยู่นอก Terraform เข้ามาอยู่ในมุมมองแบบมี type ของ provider ทีละ resource

  • datadog_dashboard/datadog_monitor เทียบกับ _json คู่กัน — HCL block ที่มี type ให้ safety net เชิงโครงสร้างของ terraform validate (พิสูจน์ข้างบน: จับ bug widget query ได้ก่อน apply ใด ๆ) และ diff ใน pull request ที่สะอาดกว่า โดยแลกกับการต้องแปลงทุก field ด้วยมือ; ตัว _json ให้ paste UI export ได้ตรง ๆ โดยแลกกับการเสีย check นั้น — typo ใน JSON string มองไม่เห็นสำหรับ terraform validate และโผล่ตอน apply กับ API จริง หรือไม่โผล่เลยถ้า field นั้นถูกเมินไปเฉย ๆ
  • validate = false บน monitor query — ข้าม API round-trip ของ terraform plan ที่ validate query มีประโยชน์เมื่อ metric ยังไม่มีจริงใน environment นี้ แต่แลกโดยตรงกับ safety net ที่จะจับ query ผิดรูปก่อนถึง production
  • Import เทียบกับสร้างใหม่ — terraform import เก็บประวัติของ resource และหลีกเลี่ยงวงจร delete แล้ว recreate (และช่องว่างของ notification/alert ที่ตามมา) แต่เติมแค่ state — HCL ที่คุณเขียนยังต้อง reconcile กับ drift ที่ terraform plan รายงานภายหลัง ซึ่งเป็นงานแมนนวลตามสัดส่วนว่า resource ที่สร้างด้วยมือห่างจาก baseline ที่สะอาดแค่ไหน
  • Log index เดียวเทียบกับหลายอัน และลำดับระหว่างกัน — index กว้างอันเดียวคิดและจัดลำดับง่ายกว่า แต่ index เฉพาะทางต่อ service สำคัญ (พิสูจน์ข้างบน: checkout-api ที่มี retention และ daily limit ของตัวเอง) ต้องอาศัย datadog_logs_index_order ที่ถูกต้อง ไม่งั้น index กว้างที่มีอยู่แล้วและอยู่ก่อนในลิสต์อาจ claim log ที่ index แคบสร้างมาเพื่อรับ
  • “block widget query ของ datadog_dashboard รับรูป flat name/data_source/query เดียวกับ JSON API payload” ไม่ใช่ — พิสูจน์ข้างบน: รูป flat นั้นอยู่ใน body jsonencode(...) ของ datadog_dashboard_json (payload shape จริงของ API) แต่ใน HCL block ที่มี type ของ datadog_dashboard สามฟิลด์นั้นอยู่ลึกลงอีกชั้นใน query.metric_query ควบคู่กับ block formula ระดับเดียวกัน
  • “DD_SITE คุมว่า Terraform provider คุยกับ Datadog site ไหน” ไม่ใช่ — พิสูจน์ข้างบนด้วยการ quote เอกสาร schema ของ provider ตรง ๆ: attribute api_url ของ provider ตั้งผ่าน DD_HOST ไม่ใช่ DD_SITE DD_SITE เป็นของจริงและมีความหมายสำหรับ Agent และ dd-trace/ddtrace (โมดูล Foundations) แต่ Terraform provider ไม่อ่านมัน
  • “การทำเครื่องหมาย app_key เป็น Sensitive ใน provider schema แปลว่ามันถูกเข้ารหัสใน state file” ไม่ใช่ — Sensitive: true ซ่อนค่าจาก CLI console output เท่านั้น ค่าดิบยังถูกเขียนลง terraform.tfstate เป็น plain text โดย default; การปกป้องไฟล์นั้นเป็นเรื่อง state backend และ access control ซึ่งครอบคลุมในบทเรียนคอร์ส Terraform (AWS) ที่ลิงก์ไว้
  • “terraform import เขียน HCL ของ resource ที่ import ให้” ไม่ใช่ — import เติมแค่ state สำหรับ resource block ที่คุณเขียนไว้แล้ว (แม้จะว่าง) คุณยังต้อง terraform plan ตามมาเพื่อดูและ reconcile ความต่างระหว่าง HCL กับ object จริงที่ import มา
ข้อดีหลักของการจัดการ monitor และ dashboard ของ Datadog ผ่าน Terraform แทน UI คืออะไร
รูปแบบจริงที่ validate แล้วของ widget query ใน timeseries_definition ของ datadog_dashboard (พิสูจน์ข้างบนกับ provider 4.22.0) คืออะไร
environment variable ไหนที่คุม api_url ของ Terraform Datadog provider จริง ๆ (คือคุยกับ Datadog site ไหน)
ความต่างหลักระหว่าง datadog_monitor กับ datadog_monitor_json คืออะไร
หลังรัน terraform import datadog_monitor.bytes_received_localhost 2081 ควรทำอะไรต่อ และเพราะอะไร
datadog_logs_index_order ลิสต์ index "main" กว้างที่มีอยู่แล้ว ก่อน index "checkout-api" ใหม่ที่สร้างเฉพาะ ความเสี่ยงจริงในทางปฏิบัติคืออะไร