LabHub
배우기 러닝패스 코스

OTCA — OpenTelemetry Certified Associate

Auto-Instrumentation Gives You One Thing: the Network Boundary

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

자동 계측을 켜면 얻는 것은 정확히 하나, 네트워크 경계입니다. HTTP 서버와 클라이언트, gRPC, DB 드라이버, 레디스, 메시지 큐 클라이언트에 스팬이 생깁니다. 프로세스 안에서 일어나는 일은 어떤 스팬으로도 나타나지 않고, 부모 스팬의 self time 이라는 공백으로만 드러납니다.

Concept map: 네트워크 경계 · self time · 3번이 4번보다 앞 · 어디가 문제가 아닌지

왜 이게 필요했나

계측에 실패하는 팀은 거의 같은 방식으로 실패합니다. 코드에 수동 스팬부터 심기 시작해서, 2주 뒤에 스팬은 300개인데 트레이스는 여전히 서비스 경계에서 끊겨 있는 상태가 됩니다. 작동하는 순서는 정해져 있습니다.

순서 하는 일 건너뛰면
1 자동 계측을 켜고 데이터가 도착하는지 확인 이후 모든 디버깅이 추측이 된다
2 리소스 속성 확정 나중에 바꾸면 과거 데이터와 끊긴다
3 서비스 경계를 넘는 전파 검증 스팬을 늘려도 트레이스가 조각난다
4 self time 이 큰 구간에만 수동 스팬 자동 계측의 빈칸이 영원히 남는다
5 컬렉터로 가공·샘플링 이관 정책 바꿀 때마다 전 서비스 재배포

3번이 4번보다 앞이라는 점이 핵심입니다. 전파가 끊긴 상태에서 수동 스팬을 추가하는 것은 조각난 트레이스를 더 잘게 조각내는 일입니다.

어떻게 동작하나

자동 계측을 켜면 이런 트레이스가 나옵니다.

SERVER    checkout-api   POST /v1/orders                       1421ms
├─ CLIENT    GET http://auth.internal/verify                      31ms
├─ CLIENT    SELECT carts WHERE id = ?                             6ms
├─ CLIENT    redis GET promo:rules:t-8871                          2ms
├─ CLIENT    POST http://payment.internal/charge                  74ms
└─ (나머지 1308ms 는 어떤 스팬에도 속하지 않음)

마지막 줄이 전부입니다. 자동 계측은 어디가 문제가 아닌지를 1,308ms 의 공백으로 알려 줍니다. 그 공백이 self time 이고, 수동 스팬은 여기에만 넣습니다.

자동 계측이 절대 보지 못하는 것은 다음과 같습니다. 프로세스 안의 CPU 작업(직렬화·압축·템플릿 렌더링·암호화), 락 대기와 커넥션 풀 대기, GIL 경합과 이벤트 루프 지연, 계측 패키지가 없는 서드파티 SDK 호출, 그리고 비즈니스 로직의 분기.

수동 스팬을 넣을 자리는 다섯 곳입니다.

  1. 루프와 배치 경계 — 반복 횟수를 속성으로 남깁니다
  2. 캐시 조회 — 히트 여부를 속성으로 남기면 캐시 효율이 트레이스에서 바로 보입니다
  3. 계측 패키지가 없는 서드파티 SDK 호출
  4. 락, 큐, 커넥션 풀 대기
  5. CPU 를 오래 쓰는 구간 — 직렬화, 압축, 리포트 생성

넣고 나면 아까의 공백이 이렇게 채워집니다.

├─ INTERNAL  checkout.apply_promotions                          1298ms
│  ├─ INTERNAL  promotion.load_rules   cache.hit=false           1241ms  <-- 여기
│  └─ INTERNAL  promotion.evaluate     evaluated=812               54ms

스팬 이름 규칙 하나만 지키면 됩니다. 이름은 저카디널리티여야 합니다. GET /v1/orders/A-99183 이 아니라 GET /v1/orders/:id 이고, 구체적인 값은 전부 속성으로 갑니다. 백엔드는 스팬 이름으로 그룹핑해서 지연 통계와 서비스 그래프를 만들기 때문에, 이름에 ID 가 들어가면 그 집계 뷰 전체가 무너집니다.

리소스 속성은 나중에 못 고칩니다. 스팬 속성과 달리 리소스 속성은 그 프로세스가 내보내는 모든 신호에 붙고, service.name 을 바꾸는 순간 대시보드·알림·서비스 그래프·과거 데이터와의 연결이 전부 끊깁니다.

속성 바꿀 수 있는가
service.name checkout-api 사실상 불가
service.namespace commerce 어렵다
service.version 2.7.1 매 배포마다 바뀜
deployment.environment.name prod 불가
service.instance.id 파드 이름 매 재시작마다 바뀜

여기서 자주 틀리는 것 두 가지. 첫째, 환경 속성의 이름은 deployment.environment.name 입니다. 예전 이름 deployment.environment 는 더 이상 쓰지 않고, 이름이 다르면 두 개의 별개 속성이 되어 대시보드 변수는 그중 하나만 읽습니다. 둘째, service.name 은 배포 단위가 아니라 서비스 단위입니다. 카나리를 checkout-api-canary 로 부르는 순간 서비스 그래프에 유령 노드가 생깁니다.

샘플러는 처음에 parentbased_always_on 으로 시작하는 것을 권합니다. 처음부터 비율 샘플링을 켜면 트레이스가 안 보일 때 계측 문제인지 샘플링 때문인지 구분할 수 없습니다. 비율로 넘어갈 때도 반드시 parentbased_traceidratio 를 씁니다. 부모 결정을 따르지 않고 서비스마다 독립적으로 확률 판정을 하면 트레이스가 중간에서 잘려 나갑니다.

현장에서 만나는 모습

계측이 앱을 망가뜨리는 방식은 몇 가지로 반복됩니다.

증상 원인 대응
배포 후 메모리가 계속 증가 백엔드 지연으로 익스포터 큐가 참 큐 상한 명시, 컬렉터 경유로 전환
스팬이 일부만 도착 종료 시 flush 없이 죽음 shutdown 호출, 종료 유예 시간 확대
스팬 하나가 수백 KB 요청 본문 전체를 속성에 넣음 속성 값 길이 상한 설정
서비스 그래프에 유령 노드 카나리를 별도 service.name 으로 배포 service.name 은 서비스 단위 고정

속성 값 길이에는 SDK 기본 상한이 없습니다. 속성 개수는 기본 128개로 제한되지만 값 길이는 무제한이라, 요청 본문이 통째로 들어가면 전송과 저장 비용이 그대로 따라옵니다. 명시적으로 걸어 두는 편이 안전합니다.

다음 실습에서 할 것

/root/otca-sdk/ 아래에 SDK 환경변수 파일을 쓰고, 리소스 속성을 규약에 맞게 정하고, 쿠버네티스 Downward API 로 파드 이름과 네임스페이스를 주입하는 Deployment 를 만들어 실제로 적용합니다. 마지막에는 스팬 이름 목록을 정리하고, ID 가 박힌 이름을 잡아내는 린터를 직접 작성합니다.