Web Operations 실전 훈련북
이 문서는 읽는 문서가 아니라 쓰면서 확인하는 훈련 문서입니다.
- 운영 상황에서 먼저 확인할 증거를 직접 고른다.
- 답안을 쓴 뒤 해설과 비교한다.
- 연결 문서로 돌아가 판단 기준을 다시 다진다.
이 문서의 사용법
이 문서는 기존 Web Operations 문서의 요약본이 아니다.
이미 만든 91. Web Operations 실전 가이드북이 “이 상황에서는 어떤 문서를 다시 열어야 하는가”를 알려주는 지도라면, 이 문서는 그 지도를 보고 직접 판단을 써보는 문제집이다.
사용 순서는 단순하다.
- 훈련 상황과 Evidence를 읽는다.
먼저 써보기에 자기 답안을 적는다.- 힌트를 보고 빠진 관점이 있는지 확인한다.
- 해설과 비교한다.
- 연결 문서를 다시 열어 기준을 보강한다.
- 반복 기록표에 놓친 관점과 다음 액션을 남긴다.
답안은 길 필요가 없다.
중요한 것은 “나는 무엇을 먼저 확인했고, 왜 그 순서가 안전한가”를 남기는 것이다.
훈련 방식
훈련은 세 가지 형식으로 나뉜다.
| 형식 | 목적 | 결과물 |
|---|---|---|
| Lab | 작은 운영 산출물을 직접 작성한다 | 표, 체크리스트, 확인 순서, 판단표 |
| Incident | 장애 상황에서 첫 판단과 복구 순서를 쓴다 | 5분 triage, 원인 후보, 복구 절차, 완료 검증 |
| Review | 그럴듯하지만 부족한 설계를 검토한다 | 숨은 리스크, 빠진 운영 계약, 승인 조건 |
각 훈련에는 일부러 완성되지 않은 정보가 들어 있다.
실제 운영도 처음부터 모든 정보가 정리되어 있지 않기 때문이다.
반복 학습 루프
한 번 풀고 끝내지 않는다.
같은 훈련을 다음 순서로 다시 푼다.
- 첫 번째 반복: 연결 문서를 보지 않고 답안을 쓴다.
- 두 번째 반복: 연결 문서를 열고 답안을 고친다.
- 세 번째 반복: 해설을 가리고 5분 안에 첫 대응 순서를 쓴다.
- 네 번째 반복: 비슷한 개인 프로젝트 상황으로 바꿔 다시 쓴다.
반복할수록 답안은 짧아져야 한다.
짧아진 답안이 근거를 잃지 않는다면 판단 기준이 몸에 들어온 것이다.
훈련 목록
Part 1. 실습 랩
Lab 1. 배포 전 운영 증거표 만들기
상황
주문 API order-api 새 버전을 오늘 17시에 운영 배포하려 한다.
개발자는 기능 테스트가 끝났다고 말하고, 배포 담당자는 “pipeline이 초록이면 바로 진행해도 되지 않나”라고 묻는다.
당신은 배포 전에 장애 대응자가 볼 수 있는 운영 증거표를 작성해야 한다.
제공된 Evidence
배포 정보는 다음과 같다.
service: order-api
env: prod
git_sha: 8f32ab1
image_tag: order-api:2026-07-01.3
image_digest: sha256:9be7...
active_profile: prod
changed_config:
ORDER_TIMEOUT_MS: 3000 -> 2000
PAYMENT_CLIENT_RETRY: 2 -> 1
migration:
V20260701_01__add_payment_attempt_column.sql
smoke_test:
GET /actuator/health -> 200
GET /orders/123 -> 200
POST /orders -> not tested
rollback_note:
"previous image exists"해야 할 일
다음 열을 가진 배포 전 운영 증거표를 작성한다.
| 항목 | 현재 값 | 확인 위치 | 기대 결과 | 실패 시 중단 조건 |
|---|---|---|---|---|
| artifact | ||||
| config/profile | ||||
| migration | ||||
| health/smoke | ||||
| rollback |
먼저 써보기
내 답안
여기에 운영 증거표를 직접 작성한다.
힌트
previous image exists만으로 rollback 가능하다고 말할 수 있는가?- 쓰기 API smoke test가 빠져도 배포를 진행할 수 있는가?
- migration이 있으면 이전 버전과 schema 호환성을 확인해야 한다.
- timeout과 retry 변경은 사용자가 느끼는 실패율과 latency를 바꿀 수 있다.
해설
좋은 답안은 “배포할 수 있다/없다”보다 “어떤 증거를 더 확인해야 배포를 진행할 수 있는가”를 먼저 적는다.
artifact는 tag뿐 아니라 digest와 Git SHA까지 맞아야 한다.
config는 active profile과 실제 environment source에서 확인해야 한다.
migration은 실행 여부보다 rollback 시 이전 버전이 새 schema를 읽을 수 있는지가 중요하다.
health는 process 생존 확인이고, smoke test는 핵심 사용자 경로 확인이다. 주문 생성 같은 쓰기 경로가 핵심이라면 사전에 정한 테스트 계정과 정리 방법이 있어야 한다.
연결 문서
- Release 단위: 배포 단위와 추적성을 확인한다.
- 호환성 원칙: migration이 rollback 판단에 끼치는 영향을 본다.
- 배포 전 기본 정보: 배포 전 중단 조건을 표로 만든다.
- 첫 5분: 배포 실패 직후 모을 증거를 역으로 점검한다.
- Case 1. 배포했는데 운영 서비스가 되지 않는다: 배포 실패와 데이터 호환성 판단 흐름을 다시 본다.
가져갈 판단 기준
배포 전 증거표는 배포를 빠르게 하기 위한 문서가 아니라, 실패했을 때 안전하게 멈추기 위한 문서다.
Lab 2. Linux 서비스 실행 점검표 만들기
상황
order-api가 VM에서 systemd 서비스로 실행된다.
지난주에는 log directory 권한 문제로 서비스가 시작되지 않았고, 누군가 임시로 권한을 넓혀 해결했다.
이번에는 같은 일이 반복되지 않도록 운영 서버 실행 점검표를 작성해야 한다.
제공된 Evidence
$ systemctl status order-api
Loaded: loaded (/etc/systemd/system/order-api.service; enabled)
Active: failed (Result: exit-code)
$ ls -l /opt/order-api/app.jar
-rw-r----- 1 deploy deploy 92818342 Jul 1 16:42 app.jar
$ ls -ld /var/log/order-api
drwxr-x--- 2 root root 4096 Jul 1 16:10 /var/log/order-api
$ grep -E 'User|EnvironmentFile|WorkingDirectory' /etc/systemd/system/order-api.service
User=app
WorkingDirectory=/opt/order-api
EnvironmentFile=/etc/order-api/order-api.env해야 할 일
다음 질문을 포함한 실행 점검표를 작성한다.
- 어떤 사용자가 서비스를 실행하는가?
- jar, env file, log directory를 그 사용자가 읽거나 쓸 수 있는가?
- 먼저 실행할 읽기 명령은 무엇인가?
- 변경 명령을 실행한다면 범위를 어디까지 좁힐 것인가?
- 변경 후 완료 검증은 무엇인가?
먼저 써보기
내 답안
여기에 Linux 서비스 실행 점검표를 직접 작성한다.
힌트
app사용자가 jar를 읽을 수 있는가?- log directory는
app사용자가 쓸 수 있는가? chmod -R 777은 문제를 빨리 숨기지만 다음 배포를 더 위험하게 만든다.- systemd 서비스는 process, 파일 권한, 환경 파일, restart 정책을 함께 봐야 한다.
해설
좋은 점검표는 변경 명령보다 읽기 명령이 먼저 나온다.
먼저 id app, ls -l, namei -l, journalctl -u order-api 같은 명령으로 현재 상태를 좁힌다.
그다음 필요한 권한만 고친다. 예를 들어 jar 소유자나 group을 service 실행 사용자 기준으로 맞추고, log directory는 app이 쓸 수 있게 제한적으로 변경한다.
완료 검증은 systemctl restart 성공만으로 끝내지 않는다. journal, application log, port listen, health, 핵심 API smoke test까지 확인한다.
연결 문서
- 파일 권한 확인: 실행 사용자와 파일 권한을 분리해 본다.
- 장애 확인 순서: systemd 장애 확인 순서를 따른다.
- 로그 확인: 로그 위치와 쓰기 실패를 확인한다.
- 읽기 명령과 변경 명령: 변경 전 읽기 명령을 고른다.
- Case 25. chmod 한 번으로 서비스는 살아났지만 다음 배포가 실패한다: 권한 문제를 다음 배포까지 연결해 본다.
가져갈 판단 기준
운영 서버 권한 문제는 넓게 열어 해결하는 것이 아니라 실행 주체와 필요한 접근 범위를 좁혀 해결한다.
Lab 3. Nginx와 Spring 요청 경로 확인표 만들기
상황
신규 API /api/orders가 배포됐다.
브라우저에서는 502가 보이지만, 개발자는 Spring 로그에 에러가 없다고 말한다.
Nginx, upstream, Spring port, DB까지 요청 경로를 따라 확인표를 만들어야 한다.
제공된 Evidence
# nginx access log
10.0.1.23 - - [01/Jul/2026:17:12:04 +0900] "POST /api/orders HTTP/1.1" 502 157 "-" "mobile" "req-7f3a"
# nginx error log
connect() failed (111: Connection refused) while connecting to upstream,
upstream: "http://127.0.0.1:8080/api/orders"
# server
$ ss -lntp | grep java
LISTEN 0 100 0.0.0.0:8081 0.0.0.0:* users:(("java",pid=4821))해야 할 일
요청 경로 확인표를 작성한다.
| 구간 | 확인할 증거 | 기대 상태 | 현재 단서 | 다음 행동 |
|---|---|---|---|---|
| Client → Nginx | ||||
| Nginx → upstream | ||||
| upstream → Spring | ||||
| Spring → dependency |
먼저 써보기
내 답안
여기에 요청 경로 확인표를 직접 작성한다.
힌트
- 502는 Spring 비즈니스 예외가 아닐 수 있다.
- error log의
Connection refused는 upstream port와 실제 listen port를 비교해야 한다. - Spring log가 비어 있는 것은 요청이 Spring까지 도달하지 않았다는 증거일 수 있다.
- Nginx 설정을 바꾸기 전에 현재 upstream과 process port를 먼저 확인한다.
해설
이 Evidence에서는 Nginx가 127.0.0.1:8080으로 보내고 있지만 Java process는 8081에서 listen 중이다.
따라서 첫 원인 후보는 Spring 코드가 아니라 upstream port 불일치다.
좋은 답안은 Nginx access log, error log, Spring application log를 같은 request id와 같은 시간대로 묶는다.
수정 후에는 단순히 200 한 번만 보지 말고 access log status, Spring request log, 핵심 API smoke test를 함께 확인한다.
연결 문서
- 요청 흐름: Nginx와 Spring 사이 요청 흐름을 확인한다.
- Nginx와 Spring Port: port listen과 연결 실패를 구분한다.
- 디버깅 순서: access/error log를 같은 시간대로 본다.
- Spring 확인 순서: Spring까지 도달한 요청인지 확인한다.
- Case 5. 502와 500을 구분하지 못해 시간이 흐른다: 502와 500의 경계를 다시 본다.
가져갈 판단 기준
Spring 로그가 조용한 502는 애플리케이션이 아니라 요청 경로의 앞단에서 멈춘 신호일 수 있다.
Lab 4. Restore Drill 체크리스트 만들기
상황
운영 DB는 매일 새벽 snapshot을 만들고 있다.
팀은 “백업이 있으니 괜찮다”고 말하지만, 실제로 복구를 해본 적은 없다.
당신은 주문 데이터와 업로드 영수증 파일까지 포함하는 Restore Drill 체크리스트를 작성해야 한다.
제공된 Evidence
backup:
db_snapshot: daily 03:00 KST
pitr: enabled, 7 days
object_storage_versioning: enabled
receipt_bucket_backup: weekly
target:
orders table
payment_attempts table
receipt object key stored in orders.receipt_key
known risk:
DB row and receipt object can be restored to different points in time.해야 할 일
다음을 포함한 Restore Drill 체크리스트를 작성한다.
- 복구 목표: RPO, RTO
- 복구 범위: DB, object storage, config
- 별도 환경 복구 절차
- 애플리케이션 검증
- 데이터 검증 SQL 또는 확인 기준
- 완료 선언 조건
먼저 써보기
내 답안
여기에 Restore Drill 체크리스트를 직접 작성한다.
힌트
- 백업 성공과 복구 가능성은 다르다.
- DB만 복구하면 object key가 가리키는 파일 시점이 맞지 않을 수 있다.
- drill은 운영에 직접 덮어쓰는 절차가 아니라 별도 환경에서 검증하는 절차다.
- 완료 검증에는 애플리케이션이 실제로 데이터를 읽을 수 있는지까지 들어가야 한다.
해설
좋은 체크리스트는 “snapshot이 존재한다”에서 멈추지 않는다.
복구 시점, 복구 대상, 검증 환경, 애플리케이션 기동, 핵심 API 조회, 파일 다운로드, count/sum 검증을 모두 포함한다.
특히 DB row와 object storage 파일은 다른 저장소이므로 같은 사고 시점으로 맞출 방법을 미리 적어야 한다.
Restore Drill의 성공 기준은 restore job 성공이 아니라 서비스가 복구된 데이터를 안전하게 읽는 것이다.
연결 문서
- 복구 목표: RPO/RTO를 먼저 정한다.
- Drill의 목표: drill이 증명해야 할 것을 확인한다.
- 복구 시나리오: 파일 데이터 복구 시나리오를 분리한다.
- 복구 방식 선택: 전체 restore와 보정 방식을 비교한다.
- Case 21. Rollback할지 Restore할지 회의가 길어진다: rollback과 restore의 차이를 다시 본다.
가져갈 판단 기준
백업은 존재로 증명되지 않고, 복구된 서비스를 검증할 때 증명된다.
Part 2. 장애 대응 훈련
Incident 1. 배포 직후 5xx와 migration 의심
Incident 상황
오늘 16:50에 주문 API 새 버전을 100% 배포했다.
17:05부터 주문 생성 5xx가 증가했고, 동시에 payment 관련 migration이 실행된 것으로 보인다.
팀은 rollback을 말하고 있지만, migration이 이미 실행되어 이전 버전 호환성이 불확실하다.
현재 보이는 증상
POST /orders5xx가 0.2%에서 8.5%로 증가GET /orders/{id}는 정상- 새 버전 Pod는 readiness 통과
- DB migration log에
payment_attemptcolumn 추가 이력 존재 - 고객센터에 주문 실패 문의가 들어오기 시작함
로그/지표/명령 단서
17:04:58 ERROR order-api PaymentAttemptRepository
SQLGrammarException: column payment_attempt_id does not exist
deploy:
previous_image: order-api:2026-06-30.8
current_image: order-api:2026-07-01.3
migration: V20260701_01__add_payment_attempt_column.sql
apm:
endpoint=/orders method=POST error_rate=8.5% p95=2300ms
endpoint=/orders/{id} method=GET error_rate=0.1% p95=180ms5분 안에 확인할 것
내 답안
영향 범위, migration 상태, rollback 가능성, 임시 완화 순서를 직접 작성한다.
원인 후보 분류
내 답안
application bug, schema migration, config drift, DB 권한, 특정 endpoint 문제로 나누어 본다.
하면 안 되는 조치
- migration 호환성 확인 전 전체 rollback
- 실패 로그와 migration 이력 보존 전 재배포
- 주문 쓰기 실패 범위 확인 전 장애 종료 선언
- DB column을 즉흥적으로 삭제하거나 이름 변경
복구 절차
- 추가 배포를 멈추고 현재 artifact, migration version, 장애 시작 시각을 기록한다.
- 쓰기 API 영향 범위와 정상 조회 API를 분리한다.
- migration이 실제 어떤 DB에 적용됐는지 확인한다.
- 이전 버전이 현재 schema와 호환되는지 확인한다.
- 불확실하면 feature flag, 쓰기 차단, forward fix 중 가장 피해가 작은 선택지를 고른다.
- 조치 후 주문 생성 성공률, error rate, DB error log를 확인한다.
완료 검증
POST /orderserror rate가 정상 범위로 회복- 실패 주문의 재시도 또는 보정 대상 범위 확정
- migration version과 실행 결과 기록
- 배포 실패 보고서에 rollback 가능 여부와 선택 이유 기록
회고 질문
- migration PR에 이전 버전 호환성 확인이 있었는가?
- smoke test에 쓰기 API가 포함되어 있었는가?
- 배포 전 중단 조건이 error rate만이 아니라 DB schema 호환성까지 포함했는가?
연결 문서
- 첫 5분: 배포 실패 직후 증거를 모은다.
- 호환성 원칙: migration과 rollback 가능성을 확인한다.
- Rollback 판단: 애플리케이션 rollback과 DB 변경을 함께 본다.
- 첫 질문: rollback, restore, forward fix를 분리한다.
- Case 1. 배포했는데 운영 서비스가 되지 않는다: 배포 직후 장애 판단 흐름을 다시 푼다.
가져갈 판단 기준
데이터 구조가 바뀐 배포에서는 rollback 버튼보다 schema 호환성 질문이 먼저다.
Incident 2. Nginx 502와 Spring 500 구분
Incident 상황
사용자는 결제 화면에서 간헐적으로 실패를 본다.
관제 화면에는 5xx가 증가한다고만 보이고, 팀 안에서는 Nginx 문제인지 Spring 문제인지 말이 갈린다.
현재 보이는 증상
/payments/confirm요청 중 일부가 502/orders조회는 정상- Spring error log에는 같은 시간대 500 stacktrace가 적다
- Nginx error log에도 upstream timeout이 있다
로그/지표/명령 단서
# nginx access log
17:22:01 "POST /payments/confirm" 502 request_id=req-a
17:22:03 "POST /payments/confirm" 500 request_id=req-b
# nginx error log
17:22:01 upstream timed out while reading response header from upstream, request: req-a
# spring log
17:22:03 ERROR request_id=req-b PaymentClientException: PG declined
# metric
payment_client_latency_p95=4800ms
nginx_upstream_response_time_p95=5100ms5분 안에 확인할 것
내 답안
request id별로 502와 500을 어떻게 분리할지 작성한다.
원인 후보 분류
내 답안
upstream timeout, Spring exception, 외부 PG 지연, Nginx timeout 설정, thread pool 포화로 나누어 본다.
하면 안 되는 조치
- 502와 500을 같은 원인으로 묶어 단정
- request id 없이 로그 샘플만 비교
- timeout만 크게 늘리고 외부 의존성 지연을 보지 않음
- Spring stacktrace가 있으니 Nginx는 무관하다고 결론
복구 절차
- 같은 시간대의 Nginx access log, error log, Spring log를 request id로 묶는다.
- 502 요청이 Spring까지 도달했는지 확인한다.
- 500 요청은 Spring 내부 예외인지 외부 PG 응답 오류인지 분리한다.
- 외부 PG latency와 timeout 설정을 비교한다.
- 임시 완화로 결제 재시도 제한, timeout 조정, 장애 공지, traffic 제한 중 필요한 조치를 고른다.
완료 검증
- 502와 500의 원인 후보가 분리됨
- 결제 API error rate와 p95 회복
- Nginx upstream timeout 감소
- Spring payment client 예외율 감소
- 장애 보고서에 request id 샘플과 시간대가 남음
회고 질문
- Nginx와 Spring log에 같은 request id가 항상 남는가?
- timeout 기준이 endpoint별로 분리되어 있는가?
- 외부 결제 의존성 지연 alert가 있는가?
연결 문서
- Status별 1차 분류: status별 1차 분류를 한다.
- 504 Timeout: timeout 설정이 장애를 어떻게 보이게 하는지 확인한다.
- 필드 설계: request id 연결을 확인한다.
- 영향 범위 확인: API 영향 범위를 나눈다.
- Case 5. 502와 500을 구분하지 못해 시간이 흐른다: 프록시와 애플리케이션 경계를 다시 본다.
가져갈 판단 기준
같은 5xx라도 request id와 시간대를 맞추기 전에는 같은 장애라고 부르지 않는다.
Incident 3. Health는 UP인데 주문 API timeout
Incident 상황
Load Balancer target은 healthy이고 /actuator/health도 UP이다.
하지만 주문 생성 API p95가 급격히 오르고 일부 요청은 timeout으로 실패한다.
DB 콘솔은 available로 표시된다.
현재 보이는 증상
/actuator/health200/ordersPOST p95 7.8s- DB connection pool active가 max에 가까움
- thread dump에 JDBC 대기 thread 증가
- error rate는 낮지만 timeout 문의 증가
로그/지표/명령 단서
HikariPool-1 - Connection is not available, request timed out after 3000ms.
metric:
http.server.requests{uri="/orders",status="200",quantile="0.95"} 7.8
hikaricp.connections.active 30
hikaricp.connections.max 30
jvm.threads.live 220
health:
status: UP5분 안에 확인할 것
내 답안
health, readiness, APM, DB pool, 사용자 영향 순서로 확인할 항목을 작성한다.
원인 후보 분류
내 답안
DB pool 포화, 느린 query, 외부 의존성 지연, readiness 설계 누락, traffic 증가로 분류한다.
하면 안 되는 조치
- health가 UP이라는 이유로 장애가 아니라고 판단
- connection pool만 크게 늘림
- DB available만 보고 애플리케이션 의존성을 정상으로 결론
- timeout 요청의 사용자 영향 범위를 확인하지 않음
복구 절차
- 핵심 API의 latency, timeout, error rate를 확인한다.
- connection pool active, pending, timeout을 본다.
- 느린 query 또는 외부 API 지연과 같은 하위 의존성을 확인한다.
- 필요하면 traffic 제한, 기능 제한, read-only 전환, pool 조정 중 피해가 작은 완화를 고른다.
- readiness가 실제 요청 가능 상태를 대표하는지 점검한다.
완료 검증
- 주문 생성 p95가 정상 범위로 회복
- connection pool pending과 timeout 감소
- 사용자 실패율 감소
- health check와 핵심 SLI의 차이를 보고서에 기록
회고 질문
- readiness에 어떤 의존성을 포함해야 하는가?
- pool 포화 alert와 API latency alert가 연결되어 있는가?
- health UP과 사용자 성공률을 구분해 보고했는가?
연결 문서
- Readiness: 살아 있음과 준비됨을 분리한다.
- 핵심 지표: latency, throughput, error, saturation을 본다.
- Prometheus Metric: Spring metric 노출 기준을 확인한다.
- 장애 분석: managed service 상태와 애플리케이션 의존성을 구분한다.
- Case 9. Health는 UP인데 주문이 실패한다: health와 사용자 영향의 차이를 다시 본다.
가져갈 판단 기준
Health UP은 장애가 없다는 증거가 아니라 다음 확인을 시작할 수 있다는 신호다.
Incident 4. Pod는 Running인데 Ingress 404
Incident 상황
Kubernetes로 옮긴 order-api 새 Deployment가 Running 상태다.
readiness도 통과하지만 외부 URL /api/orders는 404를 반환한다.
Pod log에는 요청이 거의 없고, Ingress controller log에는 backend를 찾지 못했다는 메시지가 있다.
현재 보이는 증상
kubectl get pod는 Running- readiness probe 통과
- 외부 URL은 404
- Service endpoint가 비어 있음
- Ingress rule은 새 host/path를 가리킴
로그/지표/명령 단서
$ kubectl get pod --show-labels
order-api-7f9c8d app=order-api,version=v2
$ kubectl describe svc order-api
Selector: app=orders-api
Endpoints: <none>
$ kubectl describe ingress order-api
Rules:
host: api.example.com
path: /api/orders -> service order-api:805분 안에 확인할 것
내 답안
Deployment label, Service selector, endpoint, Ingress path, Pod log 순서로 확인할 항목을 작성한다.
원인 후보 분류
내 답안
Service selector mismatch, Ingress path mismatch, readiness false positive, app route mismatch, LB health check 문제로 분류한다.
하면 안 되는 조치
- Pod가 Running이라는 이유로 애플리케이션 문제라고 단정
- 원인 확인 전 Deployment 전체 재시작
- selector 변경의 영향 범위 확인 없이 운영 Service 수정
- Ingress 404와 Spring 404를 구분하지 않음
복구 절차
- Ingress가 어떤 Service를 가리키는지 확인한다.
- Service selector와 Pod label을 비교한다.
- Endpoint가 비어 있는지 확인한다.
- selector 수정 또는 label 수정 중 더 안전한 조치를 고른다.
- 외부 요청이 Pod log에 도달하는지 확인한다.
- readiness와 실제 application route를 함께 검증한다.
완료 검증
- Service endpoint가 채워짐
- Ingress 요청이 Pod log에 남음
/api/orderssmoke test 성공- target health와 readiness가 모두 정상
- 잘못된 selector를 배포 전 검증하는 체크리스트 추가
회고 질문
- Deployment label과 Service selector를 배포 전 검증했는가?
- Running, Ready, Reachable을 분리해서 보고했는가?
- Ingress 404와 application 404를 로그로 구분할 수 있는가?
연결 문서
- 요청 흐름: Kubernetes 요청 흐름을 따라간다.
- Service와 Ingress 예시: selector와 Service 구성을 확인한다.
- Probe 구분: readiness가 무엇을 보장하는지 확인한다.
- 요청 흐름: 외부 LB와 내부 Service 경계를 본다.
- Case 29. Pod는 Running인데 Ingress로는 404가 난다: Pod 상태와 요청 도달 증거를 분리한다.
가져갈 판단 기준
Kubernetes에서 Running은 시작점이고, Service endpoint와 Ingress 도달이 운영 증거다.
Part 3. 설계 리뷰 훈련
Review 1. Canary 배포 설계안 리뷰
제안된 설계안
새 주문 할인 기능을 canary로 배포한다.
트래픽은 5%, 25%, 50%, 100% 순서로 10분마다 올린다.
각 단계에서 /actuator/health가 UP이면 다음 단계로 진행한다.
문제가 생기면 이전 image로 rollback한다.
겉보기 장점
- 한 번에 100% 배포하지 않는다.
- 단계별로 트래픽을 올린다.
- health check를 본다.
- rollback image가 있다.
숨은 리스크
- health UP만으로 주문 성공률을 대표하지 못한다.
- 할인 기능은 특정 사용자군과 특정 상품에서만 실패할 수 있다.
- DB schema나 cache key 변경이 있으면 image rollback만으로 복구되지 않을 수 있다.
- 각 단계의 중단 조건이 숫자로 정의되어 있지 않다.
- 관측 창 10분이 충분한지 근거가 없다.
리뷰 질문
- 어떤 SLI가 canary 중단 조건인가?
- 새 기능을 사용하는 사용자군과 endpoint를 따로 볼 수 있는가?
- rollback하면 DB와 cache가 이전 코드와 호환되는가?
- 5%에서 볼 sample 수가 충분한가?
- 누가 다음 단계 진행을 승인하는가?
빠진 운영 계약
- canary별 error rate, p95, saturation 중단 기준
- feature flag 또는 traffic 고정 방법
- DB migration 호환성 확인
- 단계별 관측 시간과 승인자
- rollback 후 검증 항목
개선된 설계 방향
단계별 트래픽 비율보다 중단 조건을 먼저 둔다.
health UP 외에 주문 생성 성공률, 할인 적용 오류율, latency, DB error, cache hit 변화 같은 사용자 영향 지표를 본다.
DB 변경이 있다면 Expand and Contract 방식과 rollback 가능성을 별도로 검토한다.
승인 조건
- 각 단계별 중단 조건이 숫자로 적혀 있다.
- rollback 또는 feature flag off 절차가 검증되어 있다.
- canary 대상 사용자군과 endpoint 지표가 분리되어 있다.
- 배포 후 검증 체크리스트가 owner와 함께 준비되어 있다.
연결 문서
- 관측 지표: canary에서 볼 지표를 정한다.
- 중단 조건: 중단 조건을 구체화한다.
- Rollback 판단: rollback 가능성을 확인한다.
- Case 7. 트래픽을 조금만 보냈는데 지표가 흔들린다: 지표가 흔들릴 때 진행/중단 판단을 다시 본다.
가져갈 판단 기준
Canary의 핵심은 천천히 올리는 것이 아니라 멈출 이유를 미리 합의하는 것이다.
Review 2. 관측 설계안 리뷰
제안된 설계안
새 결제 API를 출시하면서 다음 관측 계획을 세웠다.
로그는 기존처럼 text log로 남긴다.
metric은 전체 API 5xx 비율만 본다.
trace는 나중에 필요하면 붙인다.
alert는 5xx가 5분 이상 1%를 넘으면 Slack으로 보낸다.
겉보기 장점
- 최소한의 로그와 5xx alert가 있다.
- 기존 시스템을 크게 바꾸지 않는다.
- alert 기준이 숫자로 적혀 있다.
숨은 리스크
- 결제 요청 단위 correlation id가 없으면 Nginx, Spring, PG client 로그를 연결하기 어렵다.
- 전체 5xx 비율은 결제 API 장애를 늦게 보여줄 수 있다.
- latency와 timeout은 5xx보다 먼저 나빠질 수 있다.
- alert owner와 runbook이 없다.
- trace가 없으면 외부 PG, DB, 내부 로직 중 어디가 느린지 보기 어렵다.
리뷰 질문
- 결제 요청 하나를 로그, metric, trace에서 같은 id로 찾을 수 있는가?
- 5xx 외에 p95, timeout, PG latency, pool saturation을 보는가?
- alert를 받은 사람은 어떤 runbook을 열어야 하는가?
- 민감 정보가 로그에 남지 않게 필드 설계가 되어 있는가?
빠진 운영 계약
- request id/correlation id 필드
- endpoint별 error rate와 latency metric
- PG client span 또는 외부 의존성 metric
- alert owner, severity, runbook link
- 민감 정보 masking 기준
개선된 설계 방향
결제 API는 전체 API 평균이 아니라 endpoint별 SLI로 본다.
로그에는 request id, user id hash, endpoint, status, latency, error code를 구조화해 남긴다.
metric은 error rate, p95/p99, throughput, saturation을 분리한다.
alert는 “누가 무엇을 할지”와 runbook이 연결되어야 한다.
승인 조건
- Nginx와 Spring log에 같은 request id가 남는다.
- 결제 API 전용 dashboard가 있다.
- alert에 owner와 연결 runbook이 있다.
- 장애 보고서에 남길 수 있는 metric query와 log filter가 정해져 있다.
연결 문서
- 필드 설계: 요청 단위 로그 필드를 정한다.
- Histogram과 Timer: latency를 histogram으로 본다.
- 분석 순서: 관측 신호를 같은 사건으로 묶는다.
- 좋은 Alert의 조건: 행동 가능한 alert를 만든다.
- Case 8. 로그는 많은데 원인 설명이 안 된다: 많은 로그를 설명 가능한 증거로 바꾸는 흐름을 본다.
가져갈 판단 기준
관측 설계의 목표는 화면을 많이 만드는 것이 아니라 같은 사건을 여러 신호로 설명하게 만드는 것이다.
Review 3. Managed Service 도입 설계안 리뷰
제안된 설계안
팀은 VM의 MySQL을 Managed DB로 이전하려 한다.
설계안에는 “DB 운영 책임은 클라우드가 처리하므로 애플리케이션은 endpoint만 바꾸면 된다”라고 적혀 있다.
connection pool과 subnet, security group, backup restore drill은 별도 항목으로 빠져 있다.
겉보기 장점
- DB patch와 장애 조치 부담이 줄어든다.
- snapshot과 monitoring 기능을 사용할 수 있다.
- VM 디스크 관리 부담이 줄어든다.
숨은 리스크
- connection pool 합계가 DB max connection을 넘을 수 있다.
- private subnet, route, security group이 잘못되면 애플리케이션만 timeout을 본다.
- managed 상태가 available이어도 query latency나 pool 대기는 생길 수 있다.
- backup이 있어도 restore drill이 없으면 복구 가능성을 증명하지 못한다.
- 책임이 사라지는 것이 아니라 cloud metric과 app metric을 함께 봐야 하는 구조로 바뀐다.
리뷰 질문
- 모든 instance의 pool 합계가 DB limit 안에 있는가?
- DB 접근 경로의 source, target, port가 문서화되어 있는가?
- 장애 시 app log와 DB metric을 같은 시간대로 볼 수 있는가?
- restore drill과 애플리케이션 검증 절차가 있는가?
- 누가 cloud console metric과 application metric을 함께 볼 책임이 있는가?
빠진 운영 계약
- connection pool budget
- subnet/security group 변경 절차
- DB metric dashboard와 alert owner
- backup retention, PITR, restore drill
- 장애 시 임시 완화 방법
개선된 설계 방향
Managed DB 도입 설계에는 줄어드는 책임과 남는 책임을 따로 적는다.
애플리케이션은 endpoint만 바꾸는 것이 아니라 pool, timeout, retry, readiness, alert, restore drill을 함께 조정해야 한다.
네트워크 경로와 권한은 장애 중 가장 먼저 확인할 수 있도록 표로 남긴다.
승인 조건
- pool 합계와 DB max connection 비교표가 있다.
- VPC, subnet, security group 경로가 문서화되어 있다.
- backup restore drill이 별도 환경에서 검증되어 있다.
- DB available과 API success rate를 함께 보는 dashboard가 있다.
연결 문서
- 남는 책임: managed service 이후에도 남는 책임을 확인한다.
- 요청 경로: 네트워크 접근 경로를 문서화한다.
- 복구 목표: 복구 목표와 백업 방식을 연결한다.
- 격리 축: 장애 시 격리 축을 정한다.
- Case 30. 관리형 DB인데 장애 책임이 사라지지 않는다: managed service 장애 책임을 다시 검토한다.
가져갈 판단 기준
Managed Service 도입은 책임 삭제가 아니라 책임 위치와 관측 경계의 변경이다.
최종 종합 훈련
종합 시나리오
전날 18시에 할인 기능 canary 배포가 있었다.
오늘 오전 10시부터 주문 API p95가 상승했고, 일부 요청은 Nginx 502로 실패한다.
Load Balancer와 /actuator/health는 UP이다.
DB connection active는 평소보다 높고, 영수증 이미지 업로드 중 일부는 object storage 403을 반환한다.
팀은 canary를 중단할지, 전체 rollback을 할지, DB pool을 늘릴지, object storage 권한을 먼저 고칠지 의견이 갈린다.
제공된 Evidence
deploy:
yesterday 18:00 canary 10%
feature: discount-coupon-v2
image: order-api:2026-07-01.3
migration: none
nginx:
10:03 "POST /orders" 502 request_id=req-901 upstream_response_time=5.0
10:04 "POST /receipts" 403 request_id=req-914 upstream_response_time=0.2
spring:
req-901 HikariPool timeout after 3000ms
req-914 ObjectStorageAccessDenied bucket=receipt-prod key=2026/07/01/...
metric:
order_post_p95: 8200ms
order_post_error_rate: 3.2%
hikaricp_active: 30/30
receipt_upload_403_rate: 6.1%
canary_traffic: 10%
health:
actuator: UP
lb_target: healthy해야 할 일
다음을 직접 작성한다.
- 5분 triage
- 영향 범위
- 원인 후보 분류
- 확인할 로그/지표/명령
- 하면 안 되는 조치
- 복구 절차
- 완료 검증
- 회고 action
- 연결 문서
먼저 써보기
내 답안
여기에 종합 대응안을 직접 작성한다.
힌트
- 이 상황은 하나의 원인으로만 설명되지 않을 수 있다.
- Health UP은 핵심 API 성공을 보장하지 않는다.
- canary 배포, DB pool saturation, Nginx 502, object storage 403을 각각 분리해 본다.
- 전체 rollback 전에 데이터 변경 여부와 canary traffic 고정 가능성을 확인한다.
- object storage 403은 DB pool 문제와 별개 경로일 수 있다.
해설
좋은 대응은 먼저 canary traffic을 고정하고 추가 배포를 멈춘다.
그다음 주문 생성 장애와 영수증 업로드 장애를 같은 장애로 묶지 않고 request id, endpoint, metric 기준으로 나눈다.
주문 API는 DB pool saturation과 Nginx upstream timeout을 함께 본다. 502는 Spring이 느려져 Nginx timeout으로 보일 수도 있고, upstream 연결 문제일 수도 있으므로 access log, error log, Spring log를 같은 request id로 묶는다.
영수증 403은 object storage 권한, signed URL, bucket policy, key prefix를 본다.
Health가 UP이어도 readiness와 핵심 API SLI가 실패하면 사용자 영향 장애로 다룬다.
복구는 canary 중단 또는 traffic 고정, pool 임시 조정, feature flag off, object storage 권한 rollback 중 사용자 피해를 줄이는 순서로 고른다.
완료 검증은 p95, error rate, 403 rate, pool active, request log, 핵심 주문 smoke test, 영수증 업로드 확인까지 포함한다.
연결 문서
- 중단 조건: canary를 멈출 기준을 본다.
- 디버깅 순서: 502 요청 경로를 확인한다.
- 핵심 지표: latency와 saturation을 함께 본다.
- 권한 설계: object storage 403을 분리한다.
- 첫 5분: API 장애 첫 대응을 정리한다.
- 신호가 충돌할 때: 서로 다른 신호를 시간대로 묶는다.
- Case 7. 트래픽을 조금만 보냈는데 지표가 흔들린다: canary 중단 판단을 다시 본다.
- Case 9. Health는 UP인데 주문이 실패한다: health와 사용자 영향의 차이를 다시 본다.
- Case 12. 파일은 있는데 사용자에게 안 보인다: object storage와 cache/권한 경계를 다시 본다.
가져갈 판단 기준
복합 장애에서는 원인을 하나로 줄이기 전에 사용자 영향과 요청 경로를 먼저 나눠야 한다.
반복 기록표
| 날짜 | 훈련 | 처음 판단 | 놓친 관점 | 다시 볼 문서 | 다음 액션 |
|---|---|---|---|---|---|
| Lab 1 | |||||
| Lab 2 | |||||
| Lab 3 | |||||
| Lab 4 | |||||
| Incident 1 | |||||
| Incident 2 | |||||
| Incident 3 | |||||
| Incident 4 | |||||
| Review 1 | |||||
| Review 2 | |||||
| Review 3 | |||||
| 최종 종합 훈련 |