จัดการ Datadog เป็น Code ด้วย Terraform
ไอเดียหลักในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียหลักในหนึ่งประโยค”Terraform Datadog provider ตัวทางการ ทำให้คุณ define monitor, dashboard, SLO, synthetic, log pipeline และแม้แต่ “ใครมีสิทธิ์แก้ของเหล่านี้” เป็น code ที่ versioned และ review ได้ แทนที่จะคลิกประกอบเองทีละ environment ใน UI
ทำไมต้องจัดการ Datadog เป็น code
หัวข้อที่มีชื่อว่า “ทำไมต้องจัดการ Datadog เป็น code”คลิกประกอบ 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 ใหม่ทุกครั้ง
resource datadog_monitor ตัวจริง พร้อม recovery threshold และ notify_no_data
หัวข้อที่มีชื่อว่า “resource datadog_monitor ตัวจริง พร้อม recovery threshold และ notify_no_data”นี่คือ metric alert monitor ที่เขียนเป็น Terraform
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} > 85monitor_thresholds.critical/.warning— threshold ปกติที่ trigger alert ซึ่งใน UI คุณต้องพิมพ์หรือลากเองmonitor_thresholds.critical_recovery/.warning_recovery— เส้นที่ แยกออกมาและต่ำกว่า ซึ่ง metric ต้องกลับลงมาต่ำกว่าก่อน monitor จึงจะเคลียร์ alert state ยืนยันกับ schema จริงของ provider (terraform providers schema -json, blockdatadog_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 เป็น code: รูปแบบ widget query ที่ถูกต้องจริง
หัวข้อที่มีชื่อว่า “dashboard เป็น code: รูปแบบ widget query ที่ถูกต้องจริง”บทเรียน 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 แล้ว
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
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 อยู่ตำแหน่งสัมพัทธ์กันแบบเจาะจง
datadog_dashboard เทียบกับ datadog_dashboard_json
หัวข้อที่มีชื่อว่า “datadog_dashboard เทียบกับ datadog_dashboard_json”ใน Terraform มีอีกวิธีจัดการ dashboard: datadog_dashboard_json รับ body ทั้งหมดของ dashboard เป็น JSON string เดียวผ่าน jsonencode(...) แทนการสร้าง HCL tree ทีละ widget
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] datadog_service_level_objective
หัวข้อที่มีชื่อว่า “datadog_service_level_objective”บท SLOs (โมดูล Monitors, Alerting, and SLOs) คุยเรื่อง SLO สามประเภทและรูปแบบ query จริงของ burn_rate()/error_budget() เป็น raw JSON นี่คือ SLO แบบ metric-based ตัวเดียวกันเป็น Terraform
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 แล้วอันเดียวกันได้
datadog_synthetics_test แบบสั้น ๆ
หัวข้อที่มีชื่อว่า “datadog_synthetics_test แบบสั้น ๆ”บท 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
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 pipeline และ index เป็น code
หัวข้อที่มีชื่อว่า “Log pipeline และ index เป็น code”โมดูล Log Management ครอบคลุม Processing Pipeline และ index ในมุมของ UI ทั้งสองเป็น Terraform resource ปกติเหมือนกัน
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"] } }}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_monitor_json
หัวข้อที่มีชื่อว่า “datadog_monitor_json”ความสัมพันธ์เดียวกับ datadog_dashboard เทียบ datadog_dashboard_json — datadog_monitor_json รับ body ทั้งหมดของ monitor เป็น JSON string เดียว
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 นั้น
Import monitor ที่มีอยู่แล้ว
หัวข้อที่มีชื่อว่า “Import monitor ที่มีอยู่แล้ว”ทุก 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 อย่างปลอดภัย
Provider authentication ตาม Datadog site
หัวข้อที่มีชื่อว่า “Provider authentication ตาม Datadog site”ทุกตัวอย่างข้างบนสมมติว่ามี 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 variableDD_API_KEY/DD_APP_KEYแทนการ hardcode ใน HCL ได้ด้วย (ยืนยันกับเอกสาร schema ของ provider เอง) — pattern มาตรฐานในการเก็บ credential จริงออกจากไฟล์ Terraform ที่ commit ลง repositoryapi_url— ไม่ได้ คุมด้วยDD_SITEเอกสารของ provider ชัดเจน:api_url“can also be set via theDD_HOSTenvironment 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 อยู่ใกล้เลย
terraform validate error จริง โดยตั้งใจ
หัวข้อที่มีชื่อว่า “terraform validate error จริง โดยตั้งใจ”ทุก 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 = 20An 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 จะรันเสียอีก
State และ secret: app key ไปอยู่ใน state
หัวข้อที่มีชื่อว่า “State และ secret: app key ไปอยู่ใน state”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 อื่น ๆ
Reading AI-generated code
หัวข้อที่มีชื่อว่า “Reading AI-generated code”🐛 หา 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 แล้วเช่นกัน
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 ไว้จริงเท่านั้นที่จับได้
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”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
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”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 แคบสร้างมาเพื่อรับ
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “block widget query ของ
datadog_dashboardรับรูป flatname/data_source/queryเดียวกับ JSON API payload” ไม่ใช่ — พิสูจน์ข้างบน: รูป flat นั้นอยู่ใน bodyjsonencode(...)ของdatadog_dashboard_json(payload shape จริงของ API) แต่ใน HCL block ที่มี type ของdatadog_dashboardสามฟิลด์นั้นอยู่ลึกลงอีกชั้นในquery.metric_queryควบคู่กับ blockformulaระดับเดียวกัน - “
DD_SITEคุมว่า Terraform provider คุยกับ Datadog site ไหน” ไม่ใช่ — พิสูจน์ข้างบนด้วยการ quote เอกสาร schema ของ provider ตรง ๆ: attributeapi_urlของ provider ตั้งผ่านDD_HOSTไม่ใช่DD_SITEDD_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 มา