外から来た baggage をそのまま信じてしまった
한국어 원문으로 표시합니다.
목표
baggage 와 tracestate 를 직접 넣고 꺼내면서, baggage 가 저절로 스팬 속성이 되지 않는다는 것과 그 헤더가 모든 하류 요청에 실린다는 것을 숫자로 확인하고, 밖에서 들어온 문맥에 허용 목록 필터를 만들어 두 번째 서비스까지 같은 규칙으로 처리합니다.
왜 중요한가
트레이스가 이어져도 여섯 홉 아래 스팬에는 '어느 테넌트의 요청인가' 가 없다. baggage 는 그 값을 요청 전체에 나르라고 있는 자리인데, 넣어 두기만 해서는 아무 데도 나타나지 않는다 — 각 서비스가 꺼내서 자기 스팬에 옮겨 적어야 질의할 수 있는 자료가 된다. 반대로 값을 넉넉히 넣으면 그 바이트가 하류로 나가는 모든 요청에 붙어, 호출이 많은 경로에서만 지연이 늘고 프록시의 헤더 상한에 걸린다. 그리고 공개 API 앞에서 받은 baggage 는 남이 쓴 문자열이라, 그대로 속성으로 옮기면 지표의 카디널리티를 남이 정하게 된다. tracestate 는 같은 요청에 실리지만 업무 값을 넣는 자리가 아니라 추적 도구가 자기 위치를 적는 자리이고, 항목 수와 길이에 규격이 정한 규칙이 있다.
단계
/root/tp-baggage/carry.py를 만드세요. 덤프 경로는 환경변수TRACELAB_OUT에서 읽고 없으면/root/tp-baggage/carry.jsonl을 씁니다. 서비스 이름은shop-edge, 스팬 이름은checkout입니다. 그 스팬 안에서 baggage 에tenant=acme와checkout.tier=gold를 넣고, 빈 dict 에traceparent와baggage를 함께 주입한 뒤 그 dict 를 덤프와 같은 디렉터리의carry.json에 JSON 으로 적으세요. 그리고 프로그램을 한 번 돌리세요./root/tp-baggage/attrs.py를 만드세요. 덤프 기본 경로는/root/tp-baggage/attrs.jsonl입니다./opt/app/tracelab/tp_baggage/requests.json에서name이ok인 요청의 헤더를 꺼내 문맥을 추출하고, 그 문맥 아래 SERVER 스팬 두 개를 만듭니다. 첫 번째price.raw는 아무 속성도 붙이지 않고, 두 번째price.tagged에는 baggage 에서 꺼낸 값을 속성tenant와checkout.tier로 옮겨 적으세요. 서비스 이름은shop-pricing입니다. 프로그램을 돌린 뒤 두 스팬의attributes를 눈으로 비교하세요./root/tp-baggage/budget.py를 만들어/opt/app/tracelab/tp_baggage/candidates.json의 후보 네 묶음을 각각 baggage 헤더로 만들고 비용을 재세요. 덤프 기본 경로는/root/tp-baggage/budget.jsonl이고, 덤프와 같은 디렉터리의budget.tsv에 머리글 없이 네 줄을 탭으로 나눠 적습니다 —<id>,<넣은 항목 수>,<헤더에 실제로 실린 항목 수>,<헤더 바이트 수>,<keep|drop>. 마지막 칸은 W3C Baggage 규격이 전달을 보장하는 범위(항목 수와 바이트 수) 안이면keep, 벗어나면drop입니다. 후보마다budget이라는 스팬을 하나씩 남기고 속성baggage.id·baggage.entries·baggage.bytes를 붙이세요./root/tp-baggage/sanitize.py를 만드세요. 덤프 기본 경로는/root/tp-baggage/sanitize.jsonl입니다./opt/app/tracelab/tp_baggage/requests.json의hostile요청에서 baggage 를 추출한 뒤, 열쇠가tenant·checkout.tier·region중 하나이고 값이 정규식^[a-z0-9][a-z0-9._-]{0,31}$에 맞는 항목만 남기세요. 남은 것만 담은 새 문맥을 만들어 baggage 헤더로 주입하고 덤프와 같은 디렉터리의kept.json에 적습니다. SERVER 스팬POST /checkout을 그 요청의 부모 아래 만들고 속성baggage.in·baggage.kept·baggage.dropped에 개수를 넣고, 이벤트baggage.dropped의 속성keys에 버린 열쇠를 사전순으로 쉼표로 이어 적으세요. 서비스 이름은shop-edge입니다./root/tp-baggage/tsread.py를 만들어/opt/app/tracelab/tp_baggage/tracestates.json의 헤더 여섯 개를 판정하세요. 덤프 기본 경로는/root/tp-baggage/tsread.jsonl이고, 덤프와 같은 디렉터리의tsreport.tsv에<id>,<항목 수>,<헤더 글자 수>,<ok|bad>를 탭으로 나눠 여섯 줄 적습니다.ok는 항목 수가 규격의 상한 이하이고, 열쇠가 소문자·숫자로 시작해 소문자·숫자와_·-·*·/만 쓰며, 값이 규격의 글자 수 상한 이하이고 쉼표나 등호를 담지 않을 때입니다. 추가로/root/tp-baggage/05-limits.txt에 세 줄max_members=·max_value_chars=·propagate_min_chars=를 규격에서 읽은 숫자로 적으세요. 항목마다tracestate.read스팬을 하나씩 남기고 속성ts.id·ts.members·ts.ok를 붙입니다./root/tp-baggage/tsmutate.py를 만들어 같은 여섯 헤더를 나가는 헤더로 바꾸세요. 규칙은 넷입니다 — (1) 우리 항목은labhub=r1이고 언제나 맨 왼쪽에 온다, (2) 우리 열쇠가 이미 있으면 지우고 새로 앞에 넣는다(두 번 나오면 안 된다), (3) 나머지 항목의 순서는 그대로 둔다, (4) 항목 수가 규격의 상한을 넘으면 128자를 넘는 항목을 뒤에서부터 먼저 지우고, 그래도 넘으면 끝에서부터 지운다. 덤프 기본 경로는/root/tp-baggage/tsmutate.jsonl이고, 덤프와 같은 디렉터리의tsout.tsv에<id>와<나가는 헤더>를 탭으로 나눠 여섯 줄 적습니다. 항목마다tracestate.out스팬을 남기고 속성ts.id·ts.out을 붙이세요.- 앞 단계에서 손으로 넣었던 값을
/root/tp-baggage/policy.json한 곳으로 모으세요.baggage아래에allow(허용 열쇠 배열),value_pattern(값 정규식),max_entries,max_bytes를 두고,tracestate아래에key,value,max_members,drop_over_chars,position을 둡니다.max_entries와max_bytes는 규격이 보장하는 범위 안이어야 하고,max_members와drop_over_chars는 규격의 숫자와 같아야 하며position은left입니다.allow에는 사람을 식별하는 열쇠를 넣지 마세요. /root/tp-baggage/gateway.py를 만들어/root/tp-baggage/policy.json을 읽고/opt/app/tracelab/tp_baggage/requests.json의 요청 네 건 전부를 처리하세요. 요청마다 baggage 를 규칙대로 거르고 tracestate 를 6단계 규칙대로 바꾼 뒤, 그 요청의 부모 아래 SERVER 스팬gateway를 하나씩 남기고 속성req.name·baggage.kept·baggage.dropped·tracestate.members를 붙입니다. 덤프 기본 경로는/root/tp-baggage/gateway.jsonl이고, 덤프와 같은 디렉터리의gateway.tsv에<name>,<남긴 수>,<버린 수>,<나가는 tracestate 항목 수>를 탭으로 나눠 네 줄 적습니다. 서비스 이름은shop-gateway입니다.
참고
- 작업 디렉터리는
/root/tp-baggage입니다. 없으면 먼저 만드세요. - 계측 프로그램은 반드시
/opt/otel-lab/bin/python로 돌립니다. 시스템python3에는 OpenTelemetry 가 없습니다. - 덤프 경로는 언제나 환경변수
TRACELAB_OUT을 먼저 읽고, 없을 때만 과제에 적힌 기본 경로를 씁니다. 곁다리 산출물(carry.json·budget.tsv같은 것)도 덤프와 같은 디렉터리에 쓰세요. 채점기가 같은 프로그램을 자기 임시 디렉터리에서 한 번 더 돌려 대조하기 때문입니다. - 덤프 파일은 이어 쓰기라서, 프로그램을 여러 번 돌리면 스팬이 쌓입니다. 시작할 때
open(OUT, "w").close()로 비우세요. - 재료는
/opt/app/tracelab/tp_baggage/에 있습니다 —requests.json(들어오는 요청 네 건),candidates.json(baggage 후보 네 묶음),tracestates.json(tracestate 헤더 여섯 개). 이 파일들은 고치지 않습니다. - 덤프를 사람이 읽기 좋게 보려면
python3 /opt/lab/checks/_tplib.py summary <덤프>를 쓰세요. - 흔한 실수: baggage 를 넣은 뒤
set_baggage가 돌려준 문맥을 쓰지 않고 현재 문맥을 주입하는 것. 그러면 헤더가 비어서 나갑니다. - 흔한 실수: 들어온 baggage 를 추출할 때 빈
Context()를 기준으로 삼지 않아 우리 쪽 값이 섞이는 것. - W3C Baggage · W3C Trace Context — tracestate · OpenTelemetry — Baggage 개념 · OpenTelemetry Python — Propagation · OpenTelemetry — Context propagation
baggage 를 넣어 다음 서비스로 흘린다
/root/tp-baggage/carry.py 를 만드세요. 덤프 경로는 환경변수 TRACELAB_OUT 에서 읽고 없으면 /root/tp-baggage/carry.jsonl 을 씁니다. 서비스 이름은 shop-edge, 스팬 이름은 checkout 입니다. 그 스팬 안에서 baggage 에 tenant=acme 와 checkout.tier=gold 를 넣고, 빈 dict 에 traceparent 와 baggage 를 함께 주입한 뒤 그 dict 를 덤프와 같은 디렉터리의 carry.json 에 JSON 으로 적으세요. 그리고 프로그램을 한 번 돌리세요.
opentelemetry.baggage.set_baggage(key, value, context=...) 는 값을 얹은 새 문맥을 돌려줍니다. 그 문맥을 W3CBaggagePropagator().inject(carrier, context=...) 와 TraceContextTextMapPropagator().inject(...) 에 같이 넘기면 한 dict 에 두 헤더가 들어갑니다. 계측 프로그램은 /opt/otel-lab/bin/python 으로 돌립니다.
baggage 는 저절로 스팬 속성이 되지 않는다
/root/tp-baggage/attrs.py 를 만드세요. 덤프 기본 경로는 /root/tp-baggage/attrs.jsonl 입니다. /opt/app/tracelab/tp_baggage/requests.json 에서 name 이 ok 인 요청의 헤더를 꺼내 문맥을 추출하고, 그 문맥 아래 SERVER 스팬 두 개를 만듭니다. 첫 번째 price.raw 는 아무 속성도 붙이지 않고, 두 번째 price.tagged 에는 baggage 에서 꺼낸 값을 속성 tenant 와 checkout.tier 로 옮겨 적으세요. 서비스 이름은 shop-pricing 입니다. 프로그램을 돌린 뒤 두 스팬의 attributes 를 눈으로 비교하세요.
헤더에서 문맥을 꺼내는 일은 추출기 두 개가 나눠 합니다 — traceparent 는 TraceContextTextMapPropagator, baggage 는 W3CBaggagePropagator 입니다. 앞의 결과를 뒤의 context= 로 넘겨야 둘이 한 문맥에 모입니다. 꺼낸 baggage 전체는 baggage.get_all(ctx) 로 봅니다. start_as_current_span(..., context=ctx, kind=SpanKind.SERVER).
baggage 한 줄이 나가는 모든 요청에 실린다
/root/tp-baggage/budget.py 를 만들어 /opt/app/tracelab/tp_baggage/candidates.json 의 후보 네 묶음을 각각 baggage 헤더로 만들고 비용을 재세요. 덤프 기본 경로는 /root/tp-baggage/budget.jsonl 이고, 덤프와 같은 디렉터리의 budget.tsv 에 머리글 없이 네 줄을 탭으로 나눠 적습니다 — <id>, <넣은 항목 수>, <헤더에 실제로 실린 항목 수>, <헤더 바이트 수>, <keep|drop>. 마지막 칸은 W3C Baggage 규격이 전달을 보장하는 범위(항목 수와 바이트 수) 안이면 keep, 벗어나면 drop 입니다. 후보마다 budget 이라는 스팬을 하나씩 남기고 속성 baggage.id·baggage.entries·baggage.bytes 를 붙이세요.
빈 문맥은 opentelemetry.context.Context() 로 만듭니다. 주입한 dict 의 baggage 값이 곧 헤더 문자열이고, 항목 수는 쉼표로 나눈 개수, 바이트 수는 UTF-8 로 인코딩한 길이입니다. 보장 범위의 두 숫자는 규격의 Limits 절에 적혀 있습니다. 네 후보 중 하나는 넣은 항목 수와 실린 항목 수가 다릅니다 — 왜 그런지 헤더를 직접 보세요.
밖에서 들어온 baggage 에 허용 목록을 건다
/root/tp-baggage/sanitize.py 를 만드세요. 덤프 기본 경로는 /root/tp-baggage/sanitize.jsonl 입니다. /opt/app/tracelab/tp_baggage/requests.json 의 hostile 요청에서 baggage 를 추출한 뒤, 열쇠가 tenant·checkout.tier·region 중 하나이고 값이 정규식 ^[a-z0-9][a-z0-9._-]{0,31}$ 에 맞는 항목만 남기세요. 남은 것만 담은 새 문맥을 만들어 baggage 헤더로 주입하고 덤프와 같은 디렉터리의 kept.json 에 적습니다. SERVER 스팬 POST /checkout 을 그 요청의 부모 아래 만들고 속성 baggage.in·baggage.kept·baggage.dropped 에 개수를 넣고, 이벤트 baggage.dropped 의 속성 keys 에 버린 열쇠를 사전순으로 쉼표로 이어 적으세요. 서비스 이름은 shop-edge 입니다.
들어온 baggage 만 보려면 추출할 때 현재 문맥이 아니라 빈 Context() 를 기준으로 삼아야 합니다. 열쇠만 걸러서는 모자랍니다 — 허용된 열쇠에 이상한 값이 들어오는 경우가 이 요청에 섞여 있습니다. 이벤트는 span.add_event(이름, {속성}) 으로 남깁니다.
tracestate 의 규칙을 규격에서 읽어 적용한다
/root/tp-baggage/tsread.py 를 만들어 /opt/app/tracelab/tp_baggage/tracestates.json 의 헤더 여섯 개를 판정하세요. 덤프 기본 경로는 /root/tp-baggage/tsread.jsonl 이고, 덤프와 같은 디렉터리의 tsreport.tsv 에 <id>, <항목 수>, <헤더 글자 수>, <ok|bad> 를 탭으로 나눠 여섯 줄 적습니다. ok 는 항목 수가 규격의 상한 이하이고, 열쇠가 소문자·숫자로 시작해 소문자·숫자와 _·-·*·/ 만 쓰며, 값이 규격의 글자 수 상한 이하이고 쉼표나 등호를 담지 않을 때입니다. 추가로 /root/tp-baggage/05-limits.txt 에 세 줄 max_members=·max_value_chars=·propagate_min_chars= 를 규격에서 읽은 숫자로 적으세요. 항목마다 tracestate.read 스팬을 하나씩 남기고 속성 ts.id·ts.members·ts.ok 를 붙입니다.
세 숫자는 Trace Context 규격의 tracestate Limits 절과 Key·Value 절에 그대로 적혀 있습니다. propagate_min_chars 는 상한이 아니라 벤더가 최소한 전달해야 하는 길이입니다. 빈 헤더는 오류가 아닙니다 — 규격이 받아들이라고 적어 두었습니다.
들어온 tracestate 를 보존하며 우리 항목을 앞에 넣는다
/root/tp-baggage/tsmutate.py 를 만들어 같은 여섯 헤더를 나가는 헤더로 바꾸세요. 규칙은 넷입니다 — (1) 우리 항목은 labhub=r1 이고 언제나 맨 왼쪽에 온다, (2) 우리 열쇠가 이미 있으면 지우고 새로 앞에 넣는다(두 번 나오면 안 된다), (3) 나머지 항목의 순서는 그대로 둔다, (4) 항목 수가 규격의 상한을 넘으면 128자를 넘는 항목을 뒤에서부터 먼저 지우고, 그래도 넘으면 끝에서부터 지운다. 덤프 기본 경로는 /root/tp-baggage/tsmutate.jsonl 이고, 덤프와 같은 디렉터리의 tsout.tsv 에 <id> 와 <나가는 헤더> 를 탭으로 나눠 여섯 줄 적습니다. 항목마다 tracestate.out 스팬을 남기고 속성 ts.id·ts.out 을 붙이세요.
규격은 '고친 열쇠는 왼쪽으로 옮기고 건드리지 않은 항목의 순서는 보존하라' 고 적습니다. 그래서 우리 항목을 지우고 다시 앞에 붙이는 순서가 중요합니다. 자를 때는 항목을 통째로 버려야 하고, 규격이 먼저 버리라고 지목한 항목이 무엇인지 Limits 절에 적혀 있습니다. 여섯 입력 중 둘이 서로 다른 이유로 잘립니다.
규칙을 기계가 읽는 파일로 굳힌다
앞 단계에서 손으로 넣었던 값을 /root/tp-baggage/policy.json 한 곳으로 모으세요. baggage 아래에 allow(허용 열쇠 배열), value_pattern(값 정규식), max_entries, max_bytes 를 두고, tracestate 아래에 key, value, max_members, drop_over_chars, position 을 둡니다. max_entries 와 max_bytes 는 규격이 보장하는 범위 안이어야 하고, max_members 와 drop_over_chars 는 규격의 숫자와 같아야 하며 position 은 left 입니다. allow 에는 사람을 식별하는 열쇠를 넣지 마세요.
이 파일은 다음 단계의 프로그램이 읽습니다. 4단계에서 쓴 허용 목록과 정규식, 6단계에서 쓴 우리 열쇠와 값이 그대로 여기로 옮겨 오면 됩니다. max_entries·max_bytes 는 규격 상한보다 작게 잡아도 됩니다 — 상한은 '여기까지는 전달된다' 이지 '여기까지 채우라' 가 아닙니다.
두 번째 서비스에 규칙을 그대로 적용한다
/root/tp-baggage/gateway.py 를 만들어 /root/tp-baggage/policy.json 을 읽고 /opt/app/tracelab/tp_baggage/requests.json 의 요청 네 건 전부를 처리하세요. 요청마다 baggage 를 규칙대로 거르고 tracestate 를 6단계 규칙대로 바꾼 뒤, 그 요청의 부모 아래 SERVER 스팬 gateway 를 하나씩 남기고 속성 req.name·baggage.kept·baggage.dropped·tracestate.members 를 붙입니다. 덤프 기본 경로는 /root/tp-baggage/gateway.jsonl 이고, 덤프와 같은 디렉터리의 gateway.tsv 에 <name>, <남긴 수>, <버린 수>, <나가는 tracestate 항목 수> 를 탭으로 나눠 네 줄 적습니다. 서비스 이름은 shop-gateway 입니다.
규칙을 상수로 다시 적지 말고 policy.json 에서 읽으세요 — 이 단계의 요점이 그것입니다. 네 요청 중 하나는 baggage 도 tracestate 도 없고, 하나는 tracestate 가 이미 가득 차 있습니다. 둘 다 오류 없이 지나가야 합니다.