레퍼런스·설명기존 운영 문서

Datadog 감시 기준

Datadog Agent가 수집하는 신호, 초기 monitor 임계, DB 점검 query, 정의를 적용하고 검증하는 방법

이 문서는 서버 배포에서 확인할 최소 감시 기준이다. Kubernetes(Helm) 설치에서 플랫폼이 내는 지표는 플랫폼 지표에 있다. 앱에 Datadog SDK를 넣지 않는다. 세션 트레이스는 설정할 때만 표준 OTLP로 내보내고(기본은 꺼짐, 특정 수집기가 필요 없다), 키는 API 서버 운영 문서에 있다. 이 문서의 Datadog Agent는 트레이스가 아니라 로그·/readyz·DB·호스트를 본다. Datadog Agent가 Docker의 JSON stdout 로그, /readyz, 읽기 전용 PostgreSQL query, 호스트 지표를 수집한다. 정의는 infra/datadog/definitions에만 버전 관리하며 이 저장소의 도구나 CI는 Datadog API를 호출하지 않는다.

모든 임계는 서버 배포 뒤 첫 7일 동안 조정할 초기값이다. threshold_stage:first-week-tuning tag가 붙은 monitor를 첫 주 데이터 없이 장기 기준으로 간주하지 않는다. 이미 채운 공백은 없음으로, 그 범위 밖이어서 서버 배포 뒤 우선순위를 다시 정할 항목은 배포 뒤 판단으로 표시한다.

시작과 비밀 관리

Agent는 선택형 infra/compose.datadog.yml에서만 생긴다. 이 파일을 지정하지 않는 기본 stack에는 Agent, mount, label, 필수 환경 변수가 하나도 추가되지 않는다. Agent image는 gcr.io/datadoghq/agent:7.83.3으로 고정한다.

DD_API_KEY와 Datadog용 PostgreSQL password는 저장소나 shell history에 넣지 않는다. 서버에서 운영자가 /etc/werft/datadog.env를 만들고 mode 600을 확인한다. 다른 절대 경로를 써야 할 때만 DATADOG_ENV_FILE로 바꾸며, 값은 예시 자리표시자이므로 실제 값을 문서·티켓·로그에 복사하지 않는다.

DD_API_KEY=<operator-supplied>
DD_SITE=datadoghq.com
DD_ENV=alpha
DD_VERSION=<release-sha>
DD_POSTGRES_PASSWORD=<read-only-role-password>
sudo chown root:root /etc/werft/datadog.env
sudo chmod 600 /etc/werft/datadog.env
stat -c '%a %U:%G' /etc/werft/datadog.env

Agent service는 이 절대 경로를 env_file로만 읽는다. compose YAML에는 DD_API_KEY의 이름이나 값 interpolation이 없으므로 ambient DD_API_KEY로 우회할 수 없다. DD_ENV와 DD_VERSION은 같은 파일을 compose의 --env-file로 읽어 product container의 unified tags에 쓴다.

PostgreSQL role은 queue_messages, sessions, turns, attempts, provider_usage에 SELECT만 허용하고 기본 transaction을 read-only로 고정한다. 아래 명령은 서버 운영자가 admin connection에서 한 번 실행한다.

CREATE ROLE datadog LOGIN PASSWORD :'datadog_password';
ALTER ROLE datadog SET default_transaction_read_only = on;
GRANT CONNECT ON DATABASE sessions TO datadog;
GRANT USAGE ON SCHEMA public TO datadog;
GRANT SELECT ON TABLE queue_messages, sessions, turns, attempts, provider_usage TO datadog;

기존 배포 명령의 layer는 그대로 두고 마지막 -f로 이 overlay만 더하며, --env-file에 위 파일을 하나 더 준다. 아래는 로컬 stack의 진입 파일 infra/compose.yml 위에 더한 예다. 서버 설치에서는 infra/compose.yml 대신 그 설치의 layer를 그대로 적는다.

docker compose \
  --env-file /etc/werft/datadog.env \
  -f infra/compose.yml \
  -f infra/compose.datadog.yml \
  --profile apps up -d

이 형태를 저장소의 compose 파일로 docker compose config까지 확인했다. overlay를 더하면 서비스 목록에 datadog-agent가 하나 늘고, api, scheduler, reconciler, egress-proxy, postgres에 Datadog label이 붙는다. 서비스 목록은 렌더로 확인했지만 label은 infra/compose.datadog.yml의 내용을 읽은 것이고 렌더 결과로는 확인하지 못했다. DD_ENV나 DD_VERSION이 없으면 렌더가 거부된다. 실제 서버에서 up까지 돌리고 Agent가 Datadog에 보고하는 것은 확인하지 않았다(NOT VERIFIED).

Agent는 Docker socket과 container log directory를 read-only로 mount하고 host PID·cgroup namespace를 읽는다. DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL=true로 동적 worker도 수집한다. compose service는 service·env·version label을 받는다. 동적 worker의 service는 container short-image에서 유도되고, env는 Agent의 전역 DD_ENV, version은 전역 DD_TAGS에서 받는다. session_id·turn_id는 조사할 로그 attribute로만 남기며 metric tag나 facet으로 만들지 않는다.

신호와 초기 monitor

구조화 logger의 원본 record는 timestamp, level, 기계가 읽는 event, 사람이 읽는 message, 선택형 fields, context(session_id 등)다. log pipeline은 JSON을 parse하고, 이전 record에 event가 없을 때만 message를 fallback으로 쓴다. 상세 값은 fields.* 아래다. query의 metric 이름은 log-metrics.json과 PostgreSQL custom query가 만든 이름이다.

위험현재 신호(로그 event·필드, DB query, readyz)Datadog monitor(query·임계·창)경보 시 대응 runbook계측 공백
interrupt effect가 5초를 넘김control.result의 fields.operation:interrupt, effect_ms, outcome, reason_code, control_id → werft.control.effect.duration_ms. 단계별 진단은 같은 control_id의 worker.turn.interrupt.stage를 쓴다p95 avg(last_5m):p95:werft.control.effect.duration_ms{operation:interrupt} > 5000; unknown 결과 > 0/5minterrupt 뒤 결과를 알 수 없는 turn없음
terminate·control 수락 지연모든 /v1 완료의 api.request.completed(route_template, method, status, duration_ms, request_id). terminate 202는 werft.terminate.acceptance.duration_ms로 만들며 같은 request_id의 control transaction 단계와 상관한다p95 avg(last_5m):p95:werft.terminate.acceptance.duration_ms{*} > 200API 설정의 terminate 계측없음
세션 생성 p95api.request.completed의 route_template:/v1/sessions, method:POST, duration_ms → werft.session.create.duration_ms배포 첫 주 p95를 dashboard에서 관찰한 뒤 임계 확정API 설정없음. 임계 선택은 계측 공백과 구분
checkpoint 실패worker.checkpoint.failed(fields.stage, fields.reason, fields.revision) → werft.checkpoint.failedsum(last_5m):sum:werft.checkpoint.failed{*}.as_count() > 0checkpoint 복원이 계속 실패하는 세션없음
checkpoint pending 사유·경과sessions.checkpoint_pending_reason; custom query werft.checkpoint.pending_age_secondsmax(last_5m):max:werft.checkpoint.pending_age_seconds{*} > 300checkpoint 복원이 계속 실패하는 세션sessions.updated_at은 pending 시작의 보수적 proxy이며 exact 시작 시각은 배포 뒤 판단
capture 지연, PUT 수·bytes·manifest 크기성공·거절·실패마다 worker.checkpoint.result 하나가 duration_ms, durations_ms, put_count, uploaded_bytes, manifest_bytes, reused_count, conflict_count, outcome, reason_code를 남긴다. worker.checkpoint.published의 bundle·untracked 크기와 worker.checkpoint.late도 유지한다capture p95 > 20,000 ms/5m; late count > 0/5m; bundle·untracked > 214,748,364 bytes/15m; manifest > 6,710,886 bytes/15mcheckpoint 복원이 계속 실패하는 세션없음
S3 5xx·stallcheckpoint 결과의 outcome, reason_code, 단계별 durations_ms로 capture 결과는 구분하지만 S3 응답 자체는 구조화하지 않음신뢰할 S3 status query가 없어 monitor 정의 없음object store 고르기S3 operation/status/duration은 배포 뒤 판단
worker launch/exit 반복Launching execution failed; intent kept for retry, Execution resource exited right after launch, Launch attempt failed; retrying after a backoff → werft.launch.failed; Execution exited; resource reclaimed, Claimed execution resource vanished; binding released → werft.execution.exitedlaunch: sum(last_10m):sum:werft.launch.failed{*}.as_count() > 3; exit: sum(last_10m):sum:werft.execution.exited{*}.as_count() > 5시작 단계에서 계속 죽는 세션없음
저장소 장애 뒤 수렴Execution launched, 위 launch/exit event, Orphan session reconciliation completed의 fields.requeued_count·fields.blocked_count반복 launch/exit monitor로 증상은 감지reconciler·scheduler장애 시작부터 모든 session 수렴까지의 시간과 미수렴 session 수는 배포 뒤 판단
lease lostExpired lease reconciliation completed(fields.fenced_count, fields.ended_count); custom query werft.lease.stale_countmax(last_5m):max:werft.lease.stale_count{*} > 0reconciler·scheduler없음
queue oldest ageunclaimed·visible queue_messages.created_at의 최솟값 → werft.queue.oldest_age_secondsmax(last_5m):max:werft.queue.oldest_age_seconds{*} > 30reconciler·scheduler없음
readyz 가용률Agent HTTP check werft-readyz → http.can_connect마지막 3회 중 실패, 5분 no-dataAPI 설정없음
provider 429·5xxproxy는 upstream connect 실패만 기록하고 provider response status를 기록하지 않음신뢰할 status query가 없어 monitor 정의 없음provider·egress 설정provider status·retry 결과는 배포 뒤 판단
provider 비용·fallback pricingprovider_usage 1시간 합계 → werft.provider.cost_usd_1h, werft.provider.fallback_pricing_count_1h, werft.provider.unmetered_tokens_1hcost > 20 USD/1h; fallback count > 0/1h설치 상한없음. 비용 임계는 설치 예산에 맞춰 첫 주에 반드시 조정
disk·inode·XFS quotaAgent host system.disk.in_use, system.fs.inodes.in_use; 현재 xfs_quota 수동 확인host disk·inode > 0.8 for 10mworker workspace의 상한과 회수workspace별 XFS project quota 사용률 metric은 배포 뒤 판단
legal hold·orphan 증가현재 checkpoint inventory metric 없음monitor 정의 없음checkpoint 객체의 version과 holdinventory metric을 구현한 뒤 감시를 다시 판단
backup 실패scripts/backup.sh exit와 운영 history는 Docker JSON stdout에 들어오지 않음monitor 정의 없음백업backup 실행 결과 구조화는 배포 뒤 판단

DB 점검 query 계약

infra/datadog/conf.d/postgres.d/conf.yaml의 custom_queries가 정본이다. 모든 query는 SELECT 하나이며 table을 바꾸지 않는다.

  • queue oldest age: 아직 claim되지 않았고 이미 visible인 message의 가장 오래된 created_at을 DB clock과 비교한다.
  • checkpoint pending age: checkpoint_pending_reason IS NOT NULL인 session의 updated_at 중 가장 오래된 값을 쓴다. checkpoint와 무관한 같은 session 갱신이 이 시각을 움직일 수 있으므로 exact SLI가 아니라 하한 proxy다.
  • outcome_unknown: 최근 5분에 만들어진 turns.outcome_unknown = true row 수다.
  • stale lease: exited·lost가 아닌 attempt 가운데 lease_expires_at < clock_timestamp()인 수다.
  • provider 비용: 최근 1시간 cost_usd 합계와 priced_by = 'fallback' row 수다. werft.provider.unmetered_tokens_1h는 같은 1시간의 priced_by = 'unmetered' row 토큰 합계(input·output·cache write·cache read)다. billing: none profile은 비용이 0으로 남으므로, 유료 upstream을 잘못 선언했을 때 드러나는 곳은 이 토큰 수뿐이다. monitor는 정의하지 않는다. 정상값이 설치의 로컬 모델 사용량에 달려 있어서다.

어느 query도 session_id를 반환하지 않는다. log-metrics.json의 group-by도 service·env와 낮은 cardinality 상태만 쓴다. session_id는 incident에서 원본 로그나 DB를 좁힐 때만 쓴다.

정의 적용과 검증

정의는 자동으로 적용되지 않는다. 운영자는 JSON을 검토한 뒤 각 파일의 endpoint에 requests 항목을 하나씩 적용한다. log-facets.json은 UI에서 만들 facet과 만들지 않을 high-cardinality attribute 목록이다. dashboard.json은 dashboard create body다. 이미 적용한 monitor·dashboard·log pipeline은 다시 create하지 말고 기존 것의 이름과 query·filter를 새 정의에 맞춰 고친다. 다시 create하면 같은 정의가 둘이 된다.

서버 배포에서 다음을 확인한다.

  1. 기본 stack을 overlay 없이 render·기동했을 때 service 목록과 설정이 이전과 같다.
  2. overlay를 더한 docker compose config가 DD_ENV·DD_VERSION 누락을 거부하고, Agent의 Docker socket mount가 ro이며 /etc/werft/datadog.env만 읽는다.
  3. Agent status에서 Docker log, http_check:werft-readyz, PostgreSQL custom query가 모두 성공한다.
  4. /readyz를 잠깐 실패시켜 service check가 바뀌고 복구되는지 서버에서 확인한다. 실제 장기 stack 실행과 monitor notification 전달은 로컬 검증 증거로 대체하지 않는다.

이 페이지