40장. 20일 완주와 출간 판정
| 기간 | 결과물 |
|---|---|
| 1~3일 | terminal·jq·Git 증거 폴더 |
| 4~7일 | DevTools DOM·Console·Network·Storage 재현 |
| 8~10일 | curl timing·API client·OpenAPI contract |
| 11~13일 | Docker Compose·DB·Redis 진단 |
| 14~15일 | DNS·TCP·TLS evidence |
| 16~17일 | Playwright trace·접근성 |
| 18일 | k6 threshold·bounded security scan 계획 |
| 19일 | kubectl event·previous log·debug 승인 |
| 20일 | trace·metric·log·audit 연결과 인계 |
독자는 새 도구를 설치할 때 source, license, update, data upload, credential storage, team export를 검토하고, 같은 문제를 CLI 또는 open format으로 재현할 수 있어야 한다. 자동 QA는 환경 적합성 보증이 아니다. 상업 출간 전 web frontend/backend, DBA, network/TLS, SRE, security, accessibility 전문가가 clean-room 실습을 감수해야 한다.
저작권·상표·개인정보
CheckoutLab, ToolRoute, 모든 그림·fixture·명령 output은 이 책을 위해 새로 만들었다. 공식 문서는 사실 확인과 추가 학습 링크로만 사용하고 제품 screenshot과 logo를 복제하지 않았다. Chrome, curl, Docker, PostgreSQL, Redis, Playwright, Grafana k6, Kubernetes, OpenTelemetry 및 기타 제품명은 각 권리자의 상표일 수 있으며 제휴를 암시하지 않는다. 실제 token, private URL, 고객 데이터는 넣지 않는다.
공식 자료 확인표
- Chrome DevTools Network
- Chrome DevTools Performance
- curl man page
- curl HTTP scripting
- OpenAPI Specification
- Docker Compose
- Docker Compose quickstart
- PostgreSQL EXPLAIN
- Redis CLI
- Playwright
- Playwright Trace Viewer
- Grafana k6 Thresholds
- Kubernetes kubectl introduction
- Kubernetes kubectl debug
- OpenTelemetry
부록 A. 증거 인계서
INCIDENT/BUG:
영향 사용자·기능·시작 시각:
expected / actual:
minimal reproduction:
client release / server release:
request_id / trace_id / decision_id:
경계별 확인:
browser / DNS / connect / TLS / gateway / app / DB / external
확인한 가설과 반증:
변경한 상태와 원복:
민감정보 제거 확인:
다음 담당자와 다음 한 가지 질문:
스크린샷 20장보다 다음 담당자가 같은 request를 재현할 input과 ID가 중요하다. “DB가 느린 듯” 대신 어떤 query, parameter class, plan, wait event인지 쓴다. 모르는 것은 추측으로 채우지 않고 unknown으로 남긴다.
부록 B. 설치 전 도구 평가표
| 질문 | 확인 |
|---|---|
| 공식 배포 source와 checksum/signature가 있는가 | |
| license가 개인·팀·상업 용도에 맞는가 | |
| 입력 data를 local에서만 처리하는가, cloud로 보내는가 | |
| token·cookie·history 저장 위치와 삭제법은 무엇인가 | |
| plugin/extension 권한과 network egress는 무엇인가 | |
| export가 open format이고 team handoff가 가능한가 | |
| 자동 update와 rollback 정책은 무엇인가 | |
| 기존 CLI·표준으로 대체 가능한가 | |
| owner와 사용 종료 절차가 있는가 |
무료라는 이유로 production data를 업로드하지 않는다. 유료라는 이유로 검증됐다고 가정하지 않는다. tool adoption PR에 이 표와 작은 synthetic evaluation을 붙인다.
부록 C. 90초 명령 카드
# HTTP
curl -fsS -D - -o /dev/null https://example.test/health
# DNS
dig example.test A
# TLS (SNI 포함)
openssl s_client -connect example.test:443 -servername example.test </dev/null
# Compose
docker compose config && docker compose ps
# Kubernetes
kubectl config current-context
kubectl -n checkout get pod,deploy
kubectl -n checkout get events --sort-by=.lastTimestamp
명령은 출발점이다. target과 context를 확인하고 출력에 credential·개인정보가 없는지 본다. 실패를 해결할 다음 명령보다 먼저 “이 출력이 어느 가설을 지지하거나 반박하는가”를 한 문장으로 쓴다. 그 습관이 프로그램을 많이 아는 사람과 문제를 안전하게 푸는 엔지니어를 가른다.
부록 D. CheckoutLab 장애 훈련 12개
훈련 1 — 클릭해도 아무 일도 없다. Elements에서 button 존재·disabled·overlay를 확인하고 Console 최초 오류, Network initiator를 잇는다. handler가 호출됐지만 request가 없다면 form validation과 state branch에 breakpoint를 건다. 임시 DOM 수정으로 원인을 좁힌 뒤 source test를 추가한다.
expected: click -> POST /api/checkouts
actual : click -> no request
evidence: button enabled / handler throws TypeError / checkout.ts:84
fix gate: Playwright test + Console error zero
훈련 2 — 브라우저만 403이다. DevTools request에서 method, Origin, Cookie, CSRF header를 보고 Copy as cURL을 synthetic token 없이 재작성한다. curl은 성공하고 browser는 실패하면 CORS·CSRF·cookie 전달을 분리한다. 403을 token refresh loop로 고치지 않는다.
훈련 3 — 두 번 결제됐다. Network에서 같은 click의 두 POST와 initiator, idempotency key를 비교한다. frontend button disable은 UX 통제이고 서버 idempotency가 무결성 통제다. API client로 같은 key·같은 body는 같은 결과, 같은 key·다른 body는 409인지 contract test한다.
for n in 1 2; do
sh example/http/checkout.sh
done
실제 결제 adapter가 아니라 test fixture에서 실행한다.
훈련 4 — 첫 화면만 5초다. curl timing의 첫 연결과 재사용 연결을 비교한다. DNS, TLS, TTFB 중 어느 구간인지 본다. DevTools waterfall에서 render-blocking asset과 API를 나눈다. “CDN을 붙인다” 전에 느린 자원이 cache 가능한지, server wait인지 확인한다.
훈련 5 — 내 PC에서만 도메인이 안 열린다. dig, OS resolver cache, VPN, proxy, IPv4/IPv6를 비교한다. curl --resolve로 host와 certificate SNI를 유지한 채 특정 IP를 시험한다. hosts file 변경은 실험 기록과 원복 시간을 남긴다.
curl --resolve api.example.test:443:203.0.113.10
https://api.example.test/health
문서용 IP이며 실제 서비스가 아니다.
훈련 6 — 인증서 교체 뒤 일부 기기만 실패한다. browser Security, openssl s_client -showcerts, 서버별 endpoint를 비교한다. intermediate 누락, 오래된 trust store, load balancer node별 다른 certificate, SNI default certificate를 본다. -k 성공은 해결이 아니라 검증 실패를 확인한 것뿐이다.
훈련 7 — Compose API가 DB를 못 찾는다. docker compose config, ps, postgres health, api log, service DNS를 본다. host에서 쓰는 localhost:5432와 container 내부 postgres:5432를 구분한다. depends_on만으로 DB schema 준비가 끝난다고 가정하지 않는다.
훈련 8 — query가 어제부터 느리다. release annotation, data volume, parameter 분포, plan과 statistics를 비교한다. 동일 SQL 문자열이라도 tenant별 row 수가 다를 수 있다. slow query log의 실제 parameter를 개인정보 없는 class로 재현한다. index 생성 전 write lock과 rollback을 계획한다.
훈련 9 — CI Playwright만 실패한다. first retry trace에서 action timeout 앞 DOM snapshot, network와 console을 본다. CI viewport·timezone·locale·CPU와 local을 비교한다. waitForTimeout(5000)을 추가하지 않고 user-visible condition을 기다린다. flaky pass 비율을 별 지표로 남긴다.
훈련 10 — k6에서만 429가 난다. rate limit이 기대된 보호인지, generator가 같은 test 계정·IP를 과도 사용한 것인지 본다. scenario arrival rate와 실제 traffic model을 맞추고 test identifier를 server metric에 안전하게 구분한다. 429를 성공으로 볼지 실패로 볼지 목적에 따라 check를 정한다.
훈련 11 — Pod가 CrashLoopBackOff다. describe event, current/previous log, exit code, last state, config/secret mount를 본다. OOMKilled와 application exception을 구분한다. pod 삭제·rollout restart 전에 실패 container의 release digest와 evidence를 보존한다.
훈련 12 — 모든 지표는 정상인데 주문이 사라진다. HTTP 201과 CPU가 정상이어도 order row·outbox·payment authorization의 업무 invariant가 깨질 수 있다. trace에서 transaction과 publish span, DB audit와 queue lag를 연결한다. technical observability에 accepted checkout eventually reaches terminal state exactly once 합성 검증을 추가한다.
부록 E. macOS·Linux·Windows에서 같은 질문을 하는 법
명령이 같지 않아도 질문은 같다. executable은 어디 있는가, 어느 process가 port를 듣는가, resolver와 route는 무엇인가, certificate chain은 어떻게 보이는가를 대응시킨다.
| 질문 | macOS/Linux | Windows PowerShell |
|---|---|---|
| executable | type -a curl / which -a |
Get-Command curl |
| port listener | lsof -nP -iTCP:8080 -sTCP:LISTEN 또는 ss -lntp |
Get-NetTCPConnection -LocalPort 8080 |
| DNS | dig, resolvectl, scutil --dns |
Resolve-DnsName |
| route | ip route, route -n get |
Get-NetRoute |
| process | ps, pgrep |
Get-Process |
| hash | shasum -a 256 / sha256sum |
Get-FileHash -Algorithm SHA256 |
PowerShell에서 curl alias 역사는 version마다 다를 수 있어 curl.exe와 Invoke-WebRequest를 구분한다. WSL, Docker Desktop VM, host Windows의 localhost와 filesystem은 같은 경계가 아니다. IDE terminal이 WSL인지 PowerShell인지 output에 남긴다.
Get-Command curl.exe
Resolve-DnsName api.example.test
Test-NetConnection api.example.test -Port 443
Get-NetTCPConnection -State Listen | Where-Object LocalPort -eq 8080
사내 PC에서 network·certificate store를 바꾸려면 정책과 관리자 승인을 따른다. OS 차이를 해결하려 container로 모든 것을 숨기지 않는다. file permission, line ending, case sensitivity, clock과 trust store 차이를 test matrix로 유지한다.
부록 F. API Collection을 팀 자산으로 만드는 규칙
collection 구조를 인증 방식이 아니라 업무 흐름으로 나눈다: 건강 확인, 장바구니 생성, checkout, idempotency, 주문 조회, 취소, 실패 fixture. 각 request에는 목적, 전제, 안전한 example, assertions, side effect와 cleanup을 쓴다.
CheckoutLab/
00-health/
10-cart/
20-checkout/
create-success
duplicate-same-input
duplicate-different-input
payment-timeout
30-order/
90-admin-test-only/
base URL은 environment variable, token은 local secret store, fixture ID는 pre-request generation으로 둔다. export 후 placeholder가 실제 값으로 치환되지 않았는지 secret scan한다. production environment는 read-only collection과 별 profile로 분리한다.
assertion은 status만 보지 않는다. content type, schema, problem type, idempotency response, header와 최대 시간의 목적을 적는다. 시간이 performance gate가 아니라 smoke timeout이면 이름을 다르게 한다. collection runner 결과를 CI에서 쓸 때 외부 API 수집 scheduler로 변질시키지 않고 repository의 정상 contract validation에 한정한다.
부록 G. Local HTTPS를 운영과 혼동하지 않는 법
OAuth callback, secure cookie, WebAuthn은 HTTPS와 실제 host 의미를 필요로 한다. local CA tool이나 조직 개발 certificate를 사용할 수 있지만 설치한 root CA는 높은 신뢰 자산이다. source와 fingerprint, 저장 위치, 만료, 제거법을 기록한다.
dev host: checkout.local.example
certificate SAN: checkout.local.example
private key: local untracked keychain
trust: 개발 기기만, 종료 시 제거
self-signed leaf를 browser에서 매번 예외 처리하기보다 승인된 local CA 흐름을 쓴다. 실제 production domain private key를 개발자 PC에 복사하지 않는다. container가 host trust store와 다른 점을 확인한다.
reverse proxy를 local에서 쓸 때 forwarded proto/host와 application redirect를 시험한다. certificate가 맞아도 cookie domain과 OAuth registered redirect가 다르면 로그인 loop가 난다. openssl s_client와 browser Security, application callback log를 같은 host 기준으로 본다.
부록 H. DB GUI를 안전하게 쓰는 운영 프로필
DBeaver, DataGrip, pgAdmin 같은 GUI는 schema, relation, plan, result grid를 빠르게 읽을 수 있다. 그러나 auto-commit, table edit, export가 production data 변경·유출을 쉽게 만든다. 연결 이름과 색상만 믿지 않고 server, database, user, transaction mode를 query로 확인한다.
SELECT current_database(), current_user, inet_server_addr(), now();
SHOW transaction_read_only;
SHOW statement_timeout;
production profile은 read-only credential, manual commit 또는 read-only transaction, 짧은 timeout, row limit, DDL confirmation, export 제한을 쓴다. SSH tunnel과 cloud auth token의 수명·owner를 관리한다. 저장된 password와 driver download source를 검토한다.
결과 grid screenshot에 이름·이메일·주소가 보이지 않게 synthetic tenant에서 재현한다. query history와 local cache의 삭제·encryption 정책을 확인한다. GUI가 만든 SQL을 review하고 explain은 별 console에서 명시적으로 실행한다.
부록 I. Proxy와 Packet Capture의 경계
browser DevTools와 app log로 부족할 때 Charles, Fiddler, mitmproxy 같은 intercepting proxy 또는 tcpdump/Wireshark를 고려할 수 있다. TLS interception은 local root CA 설치와 민감 payload 복호화를 동반한다. 소유한 test environment, 승인된 계정·host·시간 범위에서만 사용한다.
목적: mobile test app의 redirect header 확인
범위: test device -> api.sandbox.example / 15분
제외: banking, personal browsing, production customer
저장: encrypted evidence / 24시간 / incident owner
종료: proxy off, CA remove, capture delete verification
packet capture는 payload보다 connection·retransmission·TLS handshake timing을 먼저 본다. capture filter와 display filter를 구분한다. 공유 파일에는 다른 사용자의 traffic이 섞였는지 확인한다. “디버그를 위해” 회사 전체 proxy certificate를 개인이 배포하지 않는다.
부록 J. 도구 출력의 개인정보 Redaction 테스트
금지 field를 이름만 가리면 nested header, query, URL-encoded body, trace event에 남을 수 있다. synthetic canary secret을 fixture에 넣고 export 결과 전체에서 검출되지 않는지 test한다.
SENSITIVE-CANARY-DO-NOT-SHIP-1042
실제 secret이 아니다. HAR, Playwright trace, curl verbose, API collection, application log, OpenTelemetry span, DB result, screenshot에 canary가 남는지 확인한다. redaction이 실패하면 export/upload를 중단한다.
rg -n 'SENSITIVE-CANARY-DO-NOT-SHIP-1042' evidence/ test-results/
redaction 뒤에도 context로 개인을 재식별할 수 있다. tenant·order·IP·user agent 조합을 최소화한다. incident evidence는 access, retention, deletion을 가진다. debugging SaaS에 올리기 전 조직의 data processing 정책을 확인한다.
부록 K. 팀의 최소 도구 표준
모든 도구를 표준화할 필요는 없다. 다음 인터페이스만 합의한다.
run local stack : docker compose up --wait 또는 동등한 documented command
health : machine-readable endpoint
API contract : versioned OpenAPI + examples
quality : one CLI command with exit code
browser failure : Playwright trace retention policy
performance : approved scenario + SLO thresholds
production read : read-only kubectl/DB roles
evidence : request/trace/release IDs and redaction
개인은 선호 API GUI와 DB viewer를 쓸 수 있지만 collection·query·result는 open format과 CLI로 인계한다. 새 도구가 team bus factor를 낮추는지, 특정 cloud workspace에 lock-in하는지 본다. unsupported extension과 local binary는 inventory와 update owner를 둔다.
분기마다 하나의 장애를 다른 사람이 도구 지도만 보고 재현·인계한다. 도구 교육을 메뉴 tour가 아니라 “90초 안에 첫 증거 찾기”, “10분 안에 다음 경계 정하기”, “비밀 없이 evidence 공유하기”로 평가한다.
부록 L. 용어를 정확히 쓰는 짧은 사전
- HAR: HTTP Archive. browser network 기록이며 header·body의 민감정보를 포함할 수 있다.
- TTFB: request 시작부터 response 첫 byte까지 시간. server 처리만이 아니라 앞선 network 구간 영향을 받을 수 있다.
- SNI: TLS handshake에서 client가 의도한 server name을 전달하는 확장. virtual host certificate 선택에 쓰인다.
- SAN: certificate가 유효한 DNS/IP 이름 목록. 표시용 subject 이름만 보지 않는다.
- CORS: browser cross-origin response 접근 정책. server 인증이나 firewall이 아니다.
- CSRF: browser가 자동 전송하는 credential을 이용한 의도하지 않은 state change 공격.
- idempotency: 같은 의미의 요청을 반복해도 업무 효과가 중복되지 않게 하는 성질과 계약.
- trace: 한 요청이 여러 service 경계를 통과한 span의 연결.
- metric cardinality: label 조합 수. 사용자·order ID를 넣으면 비용과 개인정보가 폭발한다.
- readiness: workload가 지금 traffic을 받을 준비가 됐는지 나타내는 신호.
- ephemeral container: 실행 중 Pod 조사에 추가하는 임시 container. 높은 접근 권한으로 관리한다.
- fixture: 테스트와 설명을 위해 통제된 입력·상태·예상 결과 묶음.
정확한 용어는 tool handoff를 짧게 한다. “네트워크가 느림” 대신 “DNS 12ms, connect 18ms, TLS 35ms, TTFB 1.8s이며 trace의 payment span이 1.6s”라고 말하면 다음 담당자가 같은 경계를 바로 조사한다.