fix: 하네스 제거 및 keycloak 문서 보강
This commit is contained in:
@@ -1 +0,0 @@
|
||||
../../vault/50-journal/daily-tasks/README.md
|
||||
@@ -0,0 +1,265 @@
|
||||
---
|
||||
title: daily-tasks / Hub
|
||||
source_type: meta
|
||||
status: stable
|
||||
tags: [meta, daily-task, hub]
|
||||
last_reviewed: 2026-05-28
|
||||
---
|
||||
|
||||
# daily-tasks / Hub
|
||||
|
||||
> Layer: `raw/daily-tasks/` — **매일 아침 학습용 실습 과제** 의 카테고리 진입점. 사수가 신입에게 주는 형식의 자율 학습 과제를 두 트랙으로 분리 누적.
|
||||
|
||||
## 0. 한 줄 요약
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| 사용 cadence | 매일 아침 |
|
||||
| 트랙 | `develop/` + `infra/` 두 가지 동시 진행 (각 ~2시간) |
|
||||
| 1과제 분량 | `duration_estimate: 120` 분 default (Pomodoro 4-5개) — *완료 신호* 까지의 자기 추정치 |
|
||||
| Template | `templates/daily-task-develop-template.md` / `templates/daily-task-infra-template.md` |
|
||||
| 산출물 | branch (`daily-task/<track>/<slug>`), commit/PR, manifest, dashboard/alert, 회고 |
|
||||
| Promotion 경로 | `verified` 항목만 `/ingest` 로 `wiki/concepts/` 또는 `wiki/projects/` (CLAUDE.md §15) |
|
||||
|
||||
## 1. 폴더 구조
|
||||
|
||||
```text
|
||||
raw/daily-tasks/
|
||||
├── README.md ← 이 파일 (hub)
|
||||
├── develop/
|
||||
│ └── YYYY-MM-DD-<implementation-slug>.md ← 매일 1개
|
||||
└── infra/
|
||||
└── YYYY-MM-DD-<implementation-slug>.md ← 매일 1개
|
||||
```
|
||||
|
||||
## 2. 명명 규칙
|
||||
|
||||
- 파일명: `YYYY-MM-DD-<implementation-slug>.md`
|
||||
- `YYYY-MM-DD` = `target_date` (수행 예정일). 미래 과제를 미리 작성해도 무방.
|
||||
- `<implementation-slug>` = **무엇을 배우고 구현하는지** 를 4~7 단어 영문 kebab-case 로. 슬러그만 보고도 과제 내용 파악 가능해야 함.
|
||||
- 좋은 예:
|
||||
- `develop/2026-05-29-archunit-controller-domain-return-rule.md`
|
||||
- `develop/2026-05-30-jackson-fail-on-unknown-properties-policy.md`
|
||||
- `infra/2026-05-29-actuator-readiness-probe-db-disconnect.md`
|
||||
- `infra/2026-05-30-prometheus-pod-restart-alert-rule.md`
|
||||
- 나쁜 예 (금지):
|
||||
- ❌ `develop/task-1.md` (의미 zero)
|
||||
- ❌ `infra/day-3-monitoring.md` (numbered hierarchy + 의미 부족)
|
||||
- ❌ `develop/오늘과제.md` (한글 파일명)
|
||||
- 자세한 규칙: [[rules/naming-conventions]] (§2.2.1 daily-task 명명)
|
||||
|
||||
## 3. 두 트랙의 차이
|
||||
|
||||
| 항목 | develop | infra |
|
||||
|---|---|---|
|
||||
| 주 산출물 | 코드 commit / PR / 테스트 / ArchUnit rule | manifest / config / probe / alert rule / dashboard |
|
||||
| 검증 채널 | unit test, contract test, build pipeline | kubectl + promql + log query + smoke test (≥2 채널 교차) |
|
||||
| §5 흐름 | 코드 작성 → 테스트 작성 → 빌드 → PR | manifest 작성 → apply → 관측 → 롤백 drill |
|
||||
| 시간 분포 | CPU bound (Pomodoro 직접) | apply / 수렴 *대기* 시간 포함 |
|
||||
| 회복력 anchor (§11) | 없음 | **있음** — fail-fast vs degrade, 롤백 트리거 |
|
||||
| Template | [[templates/daily-task-develop-template]] | [[templates/daily-task-infra-template]] |
|
||||
|
||||
## 4. 트랙별 6-month 커리큘럼
|
||||
|
||||
> 매일 1과제 × 2트랙을 6개월 (약 130 영업일) 진행했을 때 도달 목표를 *시니어 초반급 문제해결력* 으로 설정. 단순 지식 누적이 아닌 *trade-off articulation / system thinking / failure-mode awareness / root-cause tracing* 4역량의 동시 향상.
|
||||
>
|
||||
> **목표 정의 근거**: `[[raw/company-tech-blogs/senior-engineer-competency-mubin-shaikh]]#SR-MUBIN-C1` (system thinking — latency/throughput/failure-mode 까지), `#SR-MUBIN-C3` (trade-off 명시 — "best practice" 인용은 senior 미달), `#SR-MUBIN-C4` (증상 아닌 근본 원인 + 재발 방지까지), `#SR-MUBIN-C5` (커리어 초반=무엇을 만드는가, 후반=어떤 결정을 주도하는가).
|
||||
>
|
||||
> **격상 위험 주의** (raw 의 ELEV-1, ELEV-2): 본 anchor 는 *personal-blog 단독* 근거. 커리큘럼 본문에서 인용할 때는 "Mubin Shaikh 관점에서" 또는 "참고 기준으로" 한정. *공식 industry standard* 처럼 표현 금지.
|
||||
|
||||
### 4.0 4역량 anchor — *시니어 초반급* 의 조작적 정의
|
||||
|
||||
| 역량 | 의미 | 측정 신호 (도달 시) | 인용 |
|
||||
|---|---|---|---|
|
||||
| **System thinking** | 코드 한 함수가 아닌 시스템 전체 (request 진입~응답 반환 + 의존성 + 실패 전파) 로 사고 | 새 feature 를 *requirements → deployment → 운영* 까지 혼자 설계 가능 | `#SR-MUBIN-C1` |
|
||||
| **Trade-off articulation** | 모든 결정에 "왜 이걸 골랐고 왜 다른 걸 안 골랐는가" 를 *최소 2-3개* 댈 수 있음. "best practice 이니까" 거부 | 자기 PR 의 design choice 를 1분 안에 3개 trade-off 와 함께 설명 | `#SR-MUBIN-C3` |
|
||||
| **Failure-mode awareness** | 정상 path 가 아니라 *어떻게 깨지는가* 부터 설계. 새 기능 도입 시 새 실패 모드를 함께 명시 | 새 코드 / manifest 의 §11 운영 회복력 anchor 가 빈칸이 아님 | `#SR-MUBIN-C1`, `#SR-MUBIN-C4` |
|
||||
| **Root-cause tracing** | production issue 를 증상 (retry 실패) 이 아닌 근본 원인 (idempotency 누락) 까지 추적. 재발 방지 (alert / contract test) 까지 책임 | issue 1건당 fix + alert + contract test 의 3-pack 결과 | `#SR-MUBIN-C4` |
|
||||
|
||||
매 phase 끝에 위 4역량을 0~5 self-rate. 6개월 끝에서 모두 ≥ 3 이 목표 (`참고 기준`, Mubin Shaikh 관점).
|
||||
|
||||
### 4.1 develop 트랙 — 6 phase (각 4주)
|
||||
|
||||
| Phase | 핵심 anchor | 시니어 사고 강제 (trade-off) | 산출물 |
|
||||
|---|---|---|---|
|
||||
| **D-P1** Boundary Contract Enforcement | ArchUnit, Spring MVC exception, Bean Validation 4-layer, mapper boundary | 정적 분석 vs runtime 검증 trade-off / false-positive vs leak coverage | 5-8 ArchUnit rule, mapping exception classifier, contract test 묶음 |
|
||||
| **D-P2** Mapper & Serialization Safety | record + canonical constructor, MapStruct optional, Jackson polymorphic 보안 (CVE-2019-14379 패턴), PATCH semantics (RFC 7396 미채택) | 수기 mapper vs generated trade-off / `enableDefaultTyping` 보안 vs 편의 / null=deletion vs absent 의미 | mapper 패턴 카탈로그 + polymorphic deserialization 보안 test + PATCH endpoint 3-상태 contract |
|
||||
| **D-P3** Data & Transaction Contract | JPA, `TransactionPort` 추상화, optimistic / pessimistic lock, idempotency key, repository capability | tx 경계 위치 (controller/service/UC) trade-off / lock 종류 선택 / idempotency table vs request-key cache | TransactionPort 구현 + idempotency 처리 + capability 테스트 |
|
||||
| **D-P4** Domain Event & Async Boundary | outbox pattern, transactional event publish, async executor, virtual thread (Loom) 호환성 | 동기 vs 비동기 trade-off / outbox 폴링 주기 vs latency / virtual thread + ThreadLocal MDC | outbox publisher + async boundary test + virtual thread compatibility test |
|
||||
| **D-P5** API Surface & Schema Evolution | OpenAPI spec-first, contract test, API versioning, breaking change 분류 | spec-first vs code-first trade-off / version 전략 (header/path) / unknown field 허용 시점 | OpenAPI v1 + spec drift detection + deprecation policy |
|
||||
| **D-P6** Performance & Concurrency | JMH micro-bench, async profiler, jstack 분석, concurrency primitives (ReentrantLock vs synchronized vs StampedLock) | latency vs throughput trade-off / bench reliability (warmup, GC noise) / lock 선택 | JMH report + bottleneck analysis + lock comparison |
|
||||
|
||||
#### D-P1 상세 — *시작 phase, 모든 후속 phase 의 baseline*
|
||||
|
||||
- **진입 조건**: ca-tmpl 빌드 통과, ArchUnit 의존성 추가 가능
|
||||
- **학습 anchor**:
|
||||
- [[raw/branch-notes/feature-boundary-validation-mapping-contract]] D1~D14
|
||||
- [[raw/official-docs/spring-mvc-rest-exception-handling]]
|
||||
- [[raw/official-docs/validation-jakarta-bean-validation-3.0-spec]]
|
||||
- [[raw/official-docs/schema-jackson-unknown-field-handling]]
|
||||
- **변수 / 상황 anchor** (매 과제 §8 회고에 답할 것):
|
||||
- rule 이 잡지 *못하는* 우회 패턴 (reflection / generic Object 반환 / dynamic proxy) — 어디까지 ArchUnit 으로 가는 게 합리적인가?
|
||||
- false positive 1건 vs leak 1건의 비대칭 비용
|
||||
- generated code (MapStruct, Lombok) exemption 의 위치
|
||||
- **시니어 초반급 도달 신호** (이 phase 끝났을 때):
|
||||
- controller / service / DTO 의 boundary leak 시나리오 *5개* 를 trade-off 와 함께 설명 가능
|
||||
- 새 rule 추가 시 *false positive 측정* 부터 시작하는 절차가 몸에 익음
|
||||
- **예상 과제 흐름** (영업일 기준):
|
||||
- W1: controller return type rule + JSON leak integration test (오늘 작성된 첫 과제로 시작)
|
||||
- W2: request DTO → application 직접 전달 금지 rule + Bean Validation group sequence
|
||||
- W3: Mapping exception classifier + ResponseEntityExceptionHandler 확장
|
||||
- W4: Jackson deserialization 정책 강제 + integration cross-check
|
||||
|
||||
#### D-P2 상세
|
||||
|
||||
- **진입 조건**: D-P1 의 boundary contract 가 코드로 강제됨
|
||||
- **학습 anchor**:
|
||||
- [[raw/official-docs/schema-jackson-polymorphic-deserialization]] (CVE-2019-14379 포함)
|
||||
- [[raw/official-docs/patch-json-merge-rfc7396]]
|
||||
- **변수 / 상황 anchor**:
|
||||
- MapStruct generated code 가 build 마다 stale 가능 → CI 검증
|
||||
- sealed `Command` interface 의 Jackson 2.15+ 자동 인식 vs 명시 `@JsonTypeInfo` trade-off
|
||||
- PATCH `null` 의 의미 (3-상태) 를 OpenAPI 에 어떻게 노출하는가
|
||||
- **시니어 초반급 도달 신호**:
|
||||
- polymorphic deserialization gadget chain 의 *공격 시나리오* 를 1개 그릴 수 있음
|
||||
- PATCH 의 silent overwrite 버그 패턴을 코드 리뷰에서 즉시 잡아냄
|
||||
|
||||
#### D-P3 ~ D-P6 (요약, 상세는 phase 진입 시 README 갱신)
|
||||
|
||||
각 phase 진입 시 *그 phase 의 첫 주차에* 본 README 의 해당 sub-section 을 D-P1/D-P2 와 동일 깊이로 채운다 — *phase 진입은 README 갱신부터*. 이게 진행 추적 anchor.
|
||||
|
||||
### 4.2 infra 트랙 — 6 phase (각 4주)
|
||||
|
||||
| Phase | 핵심 anchor | 시니어 사고 강제 (trade-off) | 산출물 |
|
||||
|---|---|---|---|
|
||||
| **I-P1** Health & Lifecycle | actuator probe (readiness/liveness 분리), graceful shutdown, startup validation, JVM/container 자원 한계 | probe period vs detection latency / liveness 에 DB 포함의 *치명적 함정* / fail-fast vs degrade | probe contract + chaos drill + startup validation matrix |
|
||||
| **I-P2** Observability Fundamentals | structured JSON log, MDC propagation, OpenTelemetry trace context (virtual thread 호환), baseline metric (RED + USE), SLO 정의 | observability cost vs coverage / sampling rate / cardinality 폭발 위험 | dashboard 묶음 + alert rule + SLO 문서 |
|
||||
| **I-P3** Resilience Pattern | circuit breaker (Resilience4j), retry, rate limit, backpressure, bulkhead | retry vs idempotency / breaker threshold / queue size 의 latency 영향 | resilience 통합 + chaos test (지연/단절/burst) |
|
||||
| **I-P4** Cluster Operation | k8s manifest, helm chart, rollout/rollback drill, secret 관리 (sealed secret / external secret operator) | gitops vs imperative / blue-green vs canary / secret rotation 자동화 trade-off | helm chart + rollback runbook + secret rotation drill |
|
||||
| **I-P5** Capacity & Cost | HPA (CPU/memory/custom metric), resource limits, profile-driven sizing, cost reporting | over-provision (cost) vs under-provision (SLO 위험) / HPA 스파이크 vs 비용 / right-sizing 의 측정 노이즈 | sizing report + HPA policy + cost dashboard |
|
||||
| **I-P6** Security & Supply Chain | RBAC, network policy, image scan (Trivy), SBOM 생성, secret rotation, supply chain attestation | security vs DX trade-off / scan blocking vs warning / sbom 검증 강도 | SBOM pipeline + image scan gate + rotation drill |
|
||||
|
||||
#### I-P1 상세 — *시작 phase, 모든 infra 작업의 baseline*
|
||||
|
||||
- **진입 조건**: 로컬 cluster (kind/k3d/minikube) + Prometheus/Grafana 가 동작
|
||||
- **학습 anchor**:
|
||||
- [[raw/project-notes/ca-skeleton-operational-contract]] §15 (Runtime/Lifecycle), §18 (Metrics/Alerting)
|
||||
- [[raw/official-docs/runtime-health-spring-actuator-groups]]
|
||||
- [[raw/official-docs/actuator-endpoint-exposure-spring-official]]
|
||||
- [[raw/official-docs/actuator-management-port-spring-official]]
|
||||
- **변수 / 상황 anchor** (매 과제 §8 회고에 답할 것):
|
||||
- probe 가 *측정하려는 것* (트래픽 받을 준비) 과 *실제로 측정되는 것* (HTTP 200) 사이의 갭
|
||||
- 측정값 간 시간차 (actuator vs kubectl vs prometheus) — scrape interval 영향
|
||||
- liveness/readiness 혼동 시 발생하는 cascade failure (재기동 폭주)
|
||||
- probe 자체의 timeout (actuator hang) — DB 가 죽었는데 readinessProbe 도 timeout
|
||||
- **시니어 초반급 도달 신호**:
|
||||
- readiness/liveness 의 운영적 차이를 *1분* 안에 설명 + 잘못 설정한 시스템의 cascade failure 시나리오 *2개* 묘사 가능
|
||||
- 새 운영 변경 도입 시 *측정값 baseline → 변경 → 측정값 after → 차이 분석* 흐름이 자동
|
||||
- **예상 과제 흐름**:
|
||||
- W1: actuator readiness probe 분리 + DB 단절 시 측정 (오늘 작성된 첫 과제)
|
||||
- W2: graceful shutdown + in-flight 요청 처리 (terminationGracePeriodSeconds 와 actuator 의 관계)
|
||||
- W3: startup validation + 의도적 잘못된 env 로 fail-fast 시간 측정
|
||||
- W4: JVM/container 자원 한계 시뮬레이션 + OOM 시 cleanup
|
||||
|
||||
#### I-P2 ~ I-P6 (요약)
|
||||
|
||||
D-P3~D-P6 와 동일 — phase 진입 시 본 README 의 해당 sub-section 을 채우는 것이 phase 시작.
|
||||
|
||||
### 4.3 변수 / 상황 anchor — 공통 메타 패턴
|
||||
|
||||
매 phase, 매 과제 §8 회고에 답해야 하는 메타 질문 (시니어 사고 강제):
|
||||
|
||||
1. **베이스라인 측정 없이 시작했는가?** — *없으면 변경 후의 "좋아졌다" 가 측정 불가*. 매 과제 §5 Step 1 은 항상 baseline.
|
||||
2. **예상 결과 vs 실측의 차이는 몇 %인가?** — 일치하면 학습 0, 차이 클수록 학습 ↑. 차이가 0% 면 과제 너무 쉬움 (`difficulty` 조정 신호).
|
||||
3. **이 결정의 *우회 가능 경로* 는 무엇인가?** — 정적 분석은 reflection 우회, alert 는 silent failure 우회, contract test 는 misconfig 우회. 우회 1개를 매번 명시.
|
||||
4. **이 결정이 *추가하는* 실패 모드는 무엇인가?** — 새 rule 은 false positive, 새 probe 는 toggle 폭주, 새 alert 는 fatigue. 추가 실패 1개를 매번 명시.
|
||||
5. ***되돌릴* 명령은 무엇인가?** — 롤백 명령을 *작성하기 전에* 코드/manifest 작성 금지. 매 infra 과제는 snapshot first.
|
||||
|
||||
이 5개 질문이 4역량 anchor (§4.0) 의 일상 운영판.
|
||||
|
||||
### 4.4 cross-track integration
|
||||
|
||||
매 phase 끝에 *두 트랙이 같은 도메인을 다르게 보는* cross-check 1개:
|
||||
|
||||
| 시점 | develop ↔ infra cross-check |
|
||||
|---|---|
|
||||
| P1 끝 | D-P1 의 ArchUnit rule 이 I-P1 의 probe-on-startup 검증과 일관: rule 위반 build 가 *startup validation* 단계에서도 잡히는가? |
|
||||
| P2 끝 | D-P2 의 mapper masking 정책 ↔ I-P6 의 image scan 의 PII pattern. 둘이 동일 PII set 을 cover? |
|
||||
| P3 끝 | D-P3 의 idempotency key ↔ I-P3 의 retry policy. retry 가 idempotency 없이 발동 시 contract test 가 잡는가? |
|
||||
| P4 끝 | D-P4 의 outbox + virtual thread ↔ I-P2 의 trace propagation. virtual thread 경계에서 trace 가 끊기는가? |
|
||||
| P5 끝 | D-P5 의 OpenAPI spec drift ↔ I-P4 의 helm rollout. spec drift 가 rollout 차단으로 이어지는가? |
|
||||
| P6 끝 | D-P6 의 bottleneck profiling ↔ I-P5 의 HPA policy. 측정된 bottleneck 이 HPA metric 으로 연결되는가? |
|
||||
|
||||
### 4.5 진행 추적 / 자가평가
|
||||
|
||||
- 매 phase 끝 (4주차 금요일 권장): §4.0 4역량 표를 0-5 self-rate
|
||||
- phase 가 4주를 넘으면 *진척이 안 나는 신호* → 학습 anchor 분할 (예: D-P2 를 mapper + Jackson 보안 2개로 쪼개기)
|
||||
- 6개월 끝: 6회 self-rate 누적 → 역량별 성장 곡선 그리기
|
||||
|
||||
### 4.6 커리큘럼이 *틀어졌을 때*
|
||||
|
||||
- production / 회사 일정으로 1주 이상 멈추면: 멈춘 시점의 phase 마지막 과제 §8 회고를 다시 읽고 *그 phase 의 학습 anchor* 만 5분 재정리. *연속성* 회복 후 재개.
|
||||
- 한 phase 가 *너무 쉬워서* 2주 만에 끝나면: 다음 phase 진입 *전* 에 cross-track integration 과제 1개 (§4.4) 를 끼워 깊이 보강.
|
||||
- 한 phase 가 *너무 어려워서* 6주 넘어가면: 학습 anchor 를 *반으로* 자르고 새 phase 추가. 6 phase → 7 phase 로 확장 허용.
|
||||
|
||||
## 5. 하루 흐름 권장
|
||||
|
||||
```text
|
||||
07:00 - 09:00 develop 과제 1개 (~2h)
|
||||
09:00 - 09:15 회고 (§8) + commit/PR
|
||||
09:15 - 11:15 infra 과제 1개 (~2h)
|
||||
11:15 - 11:30 회고 (§8) + apply 결과 정리
|
||||
```
|
||||
|
||||
총 4시간 (이동시간 / 휴식 미포함). 각 트랙 회고 5분은 *반드시* — 회고 없는 과제 = 학습 손실 (`raw/company-tech-blogs/deliberate-practice-software-developers-redgreencode#DP-RGC-C4`).
|
||||
|
||||
## 5. 과제 시작 / 종료 절차
|
||||
|
||||
### 시작 시
|
||||
|
||||
1. 어제의 §7 "다음 과제 thread" 를 본다 → 오늘 과제 후보 선정
|
||||
2. 해당 template 복사 → `raw/daily-tasks/<track>/YYYY-MM-DD-<slug>.md`
|
||||
3. frontmatter 채움 (`target_date`, `difficulty`, `duration_estimate`, `parent_project`, `prerequisites`)
|
||||
4. §1~§4 채움 (목표 / 스토리라인 / 환경 / 사전 지식) — *과제 시작 전* 완료
|
||||
5. `status_label: in-progress` 로 변경
|
||||
|
||||
### 종료 시
|
||||
|
||||
1. §5 단계 모두 체크
|
||||
2. §6 자동 검증 명령 모두 통과
|
||||
3. §7 결과물 + §8 회고 채움
|
||||
4. §10 Closure — `status_label: done`, 소요 시간 실측, promotable 후보
|
||||
5. (infra) §11 운영 회복력 anchor 채움
|
||||
6. commit / PR 푸시
|
||||
|
||||
## 6. Promotion / Ingest
|
||||
|
||||
- `done` + `actually-implemented` 또는 `locally-verified` 등급 항목만 `/ingest` 대상
|
||||
- 절대 `wiki/interview/` 나 `wiki/portfolio/` 로 **직접** 이동 금지 (CLAUDE.md §15) — 반드시 `wiki/concepts/` 또는 `wiki/projects/` canonical 경유
|
||||
- `documented-only` / `planned` 항목은 raw 영구 보관, wiki 추출 대상 아님
|
||||
|
||||
## 7. Sources / 근거 자료
|
||||
|
||||
본 hub 와 두 template 의 구조 근거:
|
||||
|
||||
| Source | 정당화 |
|
||||
|---|---|
|
||||
| [[raw/company-tech-blogs/skillable-hands-on-lab-structure]] | 9-section anchor (Learning Objectives / Storyline / Environment / Exercises / Assessments / Outcomes / Sources / Closure / Reflection) 의 vendor-normative 근거. **공식 best practice 격상 금지** — company-case-study 강도. |
|
||||
| [[raw/company-tech-blogs/deliberate-practice-software-developers-redgreencode]] | §5 단계 분할 (slightly higher than current), §6 objective 평가, §8 reflection 의 deliberate-practice 원리. **personal-blog 강도** — Ericsson 연구 2차 인용이므로 "Ericsson 연구 기반" 표현 금지, "경험 기반 권고" 로만 인용. |
|
||||
| [[raw/company-tech-blogs/senior-engineer-competency-mubin-shaikh]] | 커리큘럼 "시니어 초반급 문제해결력" 목표의 외부 anchor — mid→senior 갭(trade-off articulation, system thinking, failure-mode awareness). **personal-blog 강도** — 공식 best practice 격상 금지. |
|
||||
|
||||
## 8. 누적 인덱스 (수동 또는 Dataview)
|
||||
|
||||
> 현재는 비어 있음. 과제가 쌓이면 트랙별로 최신 N개를 본 섹션에 손으로 적거나 Obsidian Dataview 쿼리로 자동화.
|
||||
|
||||
### develop (최신 순)
|
||||
|
||||
| 날짜 | 슬러그 | Phase | difficulty | status | 검증 결과 |
|
||||
|---|---|---|---|---|---|
|
||||
| 2026-05-29 | [[raw/daily-tasks/develop/2026-05-29-archunit-controller-domain-return-rule\|archunit-controller-domain-return-rule]] | D-P1 W1 | intermediate | not-started | — |
|
||||
|
||||
### infra (최신 순)
|
||||
|
||||
| 날짜 | 슬러그 | Phase | difficulty | status | 측정값 / 검증 |
|
||||
|---|---|---|---|---|---|
|
||||
| 2026-05-29 | [[raw/daily-tasks/infra/2026-05-29-actuator-readiness-probe-db-disconnect-detection\|actuator-readiness-probe-db-disconnect-detection]] | I-P1 W1 | intermediate | not-started | — |
|
||||
@@ -1 +0,0 @@
|
||||
../../../vault/50-journal/daily-tasks/develop/2026-05-29-archunit-controller-domain-return-rule.md
|
||||
@@ -0,0 +1,236 @@
|
||||
---
|
||||
title: daily-task / develop / archunit-controller-domain-return-rule
|
||||
source_type: daily-task
|
||||
track: develop
|
||||
status: raw
|
||||
status_label: not-started
|
||||
difficulty: intermediate
|
||||
duration_estimate: 120
|
||||
prerequisites:
|
||||
- "[[raw/branch-notes/feature-boundary-validation-mapping-contract]]"
|
||||
- "[[raw/project-notes/ca-skeleton-operational-contract]]"
|
||||
parent_project: ca-skeleton-operational-contract
|
||||
parent_branch: feature-boundary-validation-mapping-contract
|
||||
target_date: 2026-05-29
|
||||
created: 2026-05-28
|
||||
tags: [daily-task, validation, mapper, testing]
|
||||
---
|
||||
|
||||
# daily-task / develop / archunit-controller-domain-return-rule
|
||||
|
||||
> Layer: `raw/daily-tasks/develop/` — **개발 트랙 일일 실습 과제**.
|
||||
> `status_label`: `not-started` → 시작 시 `in-progress` → 종료 시 `done`
|
||||
> `difficulty`: `intermediate` (ArchUnit 기본 사용 경험 가정, predicate 합성은 새로움)
|
||||
> `duration_estimate`: 120 (Pomodoro 4-5개)
|
||||
>
|
||||
> **이 과제의 위치**: develop 트랙 1일차. [[raw/branch-notes/feature-boundary-validation-mapping-contract]] 의 첫 Claims To Verify ("controller 가 domain object 를 직접 반환하지 않는지") 를 *코드에서 강제* 하는 ArchUnit rule 을 작성한다.
|
||||
|
||||
## Parent / 부모 (필수)
|
||||
|
||||
- **Parent project**: [[raw/project-notes/ca-skeleton-operational-contract]] (§4 Boundary Validation & Mapper Contract)
|
||||
- **연관 branch**: [[raw/branch-notes/feature-boundary-validation-mapping-contract]] — D1 (모든 경계에 validation/mapping 책임), D8 (domain object → response DTO 직접 노출 금지)
|
||||
|
||||
## 1. 학습 목표 / Learning Objectives
|
||||
|
||||
- [ ] **L1**: ArchUnit 의 `ArchRuleDefinition.classes().that()...should()` 체인으로 controller class 의 method return type 제약 rule 을 작성할 수 있다
|
||||
- [ ] **L2**: 의도적 위반 코드 추가 시 build 가 *정확히* 위반된 rule 이름 + violating method signature 메시지로 깨짐을 확인할 수 있다
|
||||
- [ ] **L3**: rule 이 `@Controller`, `@RestController` 양쪽 모두 cover 하고, `ResponseEntity<T>` wrapper 의 generic 인자도 검사하는지 직접 검증할 수 있다
|
||||
- [ ] **L4 (optional, 시간 남으면)**: integration test 로 actual JSON response payload 에 domain entity field (e.g., `version`, `createdBy`) 가 leak 되지 않음을 검증할 수 있다
|
||||
|
||||
## 2. 스토리라인 / WHY (Storyline)
|
||||
|
||||
어제 보강한 `feature-boundary-validation-mapping-contract` 의 D8 결정 — *domain object 를 response DTO 로 직접 노출 금지* — 은 *문서상 합의* 일 뿐, 실제 코드는 Jackson 의 implicit reflective serialization 으로 controller method 가 `return entity` 라고 적어도 build 가 통과한다.
|
||||
|
||||
다음 신입이 이 결정을 모르고 `return ticket` 으로 적어도 컴파일러는 침묵하고, JSON response 에는 `passwordHash` 와 `version` 이 그대로 흘러간다. PR 리뷰어가 매번 *손으로* 잡아내야 하는 것은 contract 가 아니라 사회적 합의일 뿐. **사회적 합의는 컴파일러를 이기지 못한다.**
|
||||
|
||||
오늘은 *그 단 한 가지* rule — controller method return type 은 DTO record 또는 `ResponseEntity<DTO record>` 만 허용 — 을 작성하고, 의도적으로 위반된 코드를 추가해 build 가 깨지는 것을 *눈으로* 확인한다. 이 단 한 줄의 rule 이 다음 1년의 boundary leak 50건을 막을 것이다.
|
||||
|
||||
## 3. 환경 / Environment
|
||||
|
||||
**개발 도구**:
|
||||
|
||||
- Java: 21 (LTS)
|
||||
- Build: Gradle 8.x
|
||||
- IDE 권장: IntelliJ IDEA 2025.x
|
||||
- 라이브러리: `com.tngtech.archunit:archunit-junit5:1.3.0`, Spring Boot 3.3.x, JUnit 5.10+
|
||||
|
||||
**사전 셋업**:
|
||||
|
||||
```bash
|
||||
cd ~/workspace/ca-tmpl
|
||||
git checkout main && git pull
|
||||
git checkout -b daily-task/develop/archunit-controller-domain-return-rule
|
||||
|
||||
# 현재 ArchUnit 의존성 확인
|
||||
./gradlew :adapter-web:dependencies | grep archunit
|
||||
|
||||
# 기존 ArchUnit test 위치 확인
|
||||
find . -name 'CleanArchitectureTest.java' -path '*/test/*'
|
||||
|
||||
# 빌드 정상 확인
|
||||
./gradlew :adapter-web:test --tests '*CleanArchitectureTest'
|
||||
```
|
||||
|
||||
**예상 변경 파일**:
|
||||
|
||||
- `adapter-web/src/test/java/<base>/architecture/ControllerReturnTypeRuleTest.java` (신규)
|
||||
- 또는 기존 `CleanArchitectureTest.java` 에 메서드 추가
|
||||
|
||||
## 4. 사전 지식 / Prerequisites
|
||||
|
||||
- [[raw/branch-notes/feature-boundary-validation-mapping-contract]] — D1, D8, Claims To Verify 첫 항목 정독
|
||||
- [[raw/project-notes/ca-skeleton-operational-contract]] §4 — Boundary Validation & Mapper Contract
|
||||
- ArchUnit 핵심 API (모르면 5분만 보고 시작):
|
||||
- `JavaClasses` 로딩 (`new ClassFileImporter().importPackages(...)`)
|
||||
- `ArchRuleDefinition.methods()` chain
|
||||
- `DescribedPredicate` 합성 (`and`, `or`, `not`)
|
||||
|
||||
## 5. 단계별 과제 / Exercises
|
||||
|
||||
### Step 1: 베이스라인 — 현재 위반 grep (~20min)
|
||||
|
||||
- **What**: 현재 ca-tmpl 의 controller code 에 이미 `return entity` 또는 `return domainObject` 패턴이 있는지 확인. 사전 측정.
|
||||
- **How (hint)**: `grep -r "return.*Entity\b" adapter-web/src/main/java` / IDE에서 `@RestController` annotated class 들의 method return type 한 줄로 정렬해서 listing
|
||||
- **Done when**:
|
||||
- 현재 위반 카운트 N개 명시 (0이어도 무방 — 기준선만 확보)
|
||||
- §7 결과물 섹션에 "baseline violation: N" 기록
|
||||
|
||||
### Step 2: ArchUnit rule 작성 (~30min)
|
||||
|
||||
- **What**: `ControllerReturnTypeRuleTest.java` 에 단일 `@ArchTest` rule 작성. controller class 의 모든 public method 의 return type 이 *허용 set* (DTO record / `ResponseEntity<DTO>` / `void`) 안에 있는지 검사.
|
||||
- **How (hint)**:
|
||||
- `classes().that().areAnnotatedWith(RestController.class)` 로 controller selection
|
||||
- `.should()` 뒤에 custom `ArchCondition<JavaClass>` 작성 — class 내부 method 순회
|
||||
- 허용 set 정의: 해당 패키지 (e.g., `<base>.web.dto.*`) 아래 record 인지, 또는 `ResponseEntity` 의 raw type 인지
|
||||
- `ResponseEntity<T>` 의 generic 인자 추출은 `JavaParameterizedType` 사용
|
||||
- **함정** (의도적 노출):
|
||||
- `ResponseEntity<DomainEntity>` 처럼 wrapper 안에 domain 이 숨는 경우 — generic 인자도 검사해야 함
|
||||
- record 가 *DTO 패키지가 아닌 domain 패키지에 있는* 경우 — 패키지 위치도 검사
|
||||
- **Done when**:
|
||||
- `./gradlew :adapter-web:test --tests '*ControllerReturnType*'` 통과
|
||||
- rule 코드 30줄 이내 (복잡하면 분리)
|
||||
|
||||
### Step 3: 의도적 위반 → build 깨짐 확인 (~25min)
|
||||
|
||||
- **What**: 임의의 controller method return type 을 domain entity 로 *임시* 변경 → build 실행 → 에러 메시지 *정확히 읽고* 확인 → rule 이름이 메시지에 포함되는지 검증 → 위반 복구
|
||||
- **How (hint)**:
|
||||
- 가장 단순한 GET controller method 선택
|
||||
- return type 만 변경 (구현은 그대로 두고 `(DomainType) (Object) responseDto` cast 같은 hack 사용)
|
||||
- build 실패 시 stack trace 가 아니라 **violation 메시지** 의 첫 줄을 읽을 것
|
||||
- **Done when**:
|
||||
- 실패 메시지에 rule description (예: `controllers should return only DTO record or ResponseEntity<DTO record>`) 포함
|
||||
- 실패 메시지에 정확한 violating method signature 포함
|
||||
- 변경 복구 후 build 다시 통과
|
||||
- **공통 실수**:
|
||||
- rule 자체에 typo 가 있어 *항상* 실패 — 의도된 위반인지 unintended 위반인지 구분 필요
|
||||
|
||||
### Step 4: `ResponseEntity<DomainEntity>` 위반 잡기 (심화) (~25min)
|
||||
|
||||
- **What**: Step 3 의 위반을 `ResponseEntity<DomainEntity>` 형태로 변경. 현재 rule 이 이 패턴도 잡는가? 못 잡으면 rule 보강.
|
||||
- **How (hint)**:
|
||||
- ArchUnit 의 `JavaMethod.getReturnType()` 은 raw type만 반환 — generic 인자는 `getRawReturnType()` 외 `getReturnType()` 의 `JavaParameterizedType` cast 필요
|
||||
- 또는 더 간단한 우회: `ResponseEntity` 인 경우에만 별도 검사 분기
|
||||
- **트레이드오프 의식** (시니어 사고):
|
||||
- rule 을 정교하게 만들수록 false positive 줄지만 rule 복잡도 ↑
|
||||
- 대안: ArchUnit 대신 lightweight `@JsonView` 정책 + DTO 패키지 격리 → 다른 trade-off
|
||||
- *이 결정은 본 과제 범위 밖이지만 §8 회고에 기록할 것*
|
||||
- **Done when**:
|
||||
- `ResponseEntity<DomainEntity>` 패턴이 build 실패로 검출됨
|
||||
- rule 코드가 여전히 50줄 이내
|
||||
|
||||
### Step 5 (선택): integration test 로 JSON leak 검증 (~20min)
|
||||
|
||||
- **What**: 정상 endpoint 호출 → response JSON 을 deserialize → domain entity 의 internal field (e.g., `passwordHash`, `version`, `auditingFields.createdBy`) 가 *없음* 을 assert
|
||||
- **How (hint)**:
|
||||
- `@SpringBootTest(webEnvironment = RANDOM_PORT)` + `TestRestTemplate`
|
||||
- JSON path assertion 또는 `Map<String, Object>` deserialize 후 keyset 검사
|
||||
- 금지 field set 을 명시적으로 정의 (whitelist 아닌 blacklist — 추가 field 는 허용)
|
||||
- **Done when**:
|
||||
- test 통과 + 의도적으로 controller 가 entity 반환하도록 변경 시 test 실패
|
||||
- 변경 복구
|
||||
|
||||
## 6. 검증 / Assessment
|
||||
|
||||
**자동 검증**:
|
||||
|
||||
```bash
|
||||
# 1) 빌드 + 단위 테스트
|
||||
./gradlew clean :adapter-web:test
|
||||
# 합격 기준: exit 0
|
||||
|
||||
# 2) 본 과제의 ArchUnit rule
|
||||
./gradlew :adapter-web:test --tests '*ControllerReturnType*'
|
||||
# 합격 기준: PASS 로그 + rule 1개 이상 executed
|
||||
|
||||
# 3) 의도적 위반 시 빌드 깨기 (수동)
|
||||
# - controller method return type 임시 변경
|
||||
# - ./gradlew :adapter-web:test → FAILED
|
||||
# - 메시지 확인 → 복구
|
||||
|
||||
# 4) (Step 5) integration test
|
||||
./gradlew :adapter-web:test --tests '*JsonLeakIntegrationTest'
|
||||
# 합격 기준: exit 0
|
||||
```
|
||||
|
||||
**수동 self-check**:
|
||||
|
||||
- [ ] rule description 이 한 줄로 명확 (남이 봐도 무엇을 검사하는지 알 수 있음)
|
||||
- [ ] 의도적 위반 메시지가 rule description + violating method signature 둘 다 포함
|
||||
- [ ] rule 이 controller 패키지 *외부* class 는 검사하지 않음 (false positive 없음)
|
||||
- [ ] commit 메시지가 "왜" 를 답함 (예: "Enforce controller→DTO return type to prevent domain leak in JSON response")
|
||||
- [ ] **시니어 사고 체크** — 본 rule 의 trade-off 1-2개 (예: false positive 가능 시나리오, rule 우회 방법 — generic Object 반환 등) 를 §8 회고에 기록
|
||||
|
||||
## 7. 결과물 / Outcomes
|
||||
|
||||
- **commit / PR**:
|
||||
- 브랜치: `daily-task/develop/archunit-controller-domain-return-rule`
|
||||
- commits: <해시 + 1줄 메시지>
|
||||
- PR URL (있다면):
|
||||
- **신규/변경 파일**:
|
||||
- `adapter-web/src/test/java/<base>/architecture/ControllerReturnTypeRuleTest.java` — controller return type rule
|
||||
- (Step 5 했다면) `adapter-web/src/test/java/<base>/architecture/JsonLeakIntegrationTest.java`
|
||||
- **베이스라인 측정값** (Step 1):
|
||||
- Pre-rule violation count: <N>
|
||||
- 위반 패턴: <패턴 목록>
|
||||
- **학습한 개념** (wiki/concepts 로 ingest 후보):
|
||||
- ArchUnit predicate 합성 (`and`/`or`/`not`)
|
||||
- `JavaParameterizedType` 으로 generic 인자 검사
|
||||
- `ResponseEntity<T>` 와 ArchUnit 의 generic erasure 다루기
|
||||
- **다음 과제 thread**:
|
||||
- request DTO 가 application service signature 에 직접 나타나는지 검사 (`feature-boundary-validation-mapping-contract` Claims To Verify 2번째 항목)
|
||||
- MapStruct generated code 의 architecture exemption 검증
|
||||
- `@JsonView` 또는 DTO 패키지 격리 대안의 trade-off 비교
|
||||
|
||||
## 8. 회고 / Reflection (~5min)
|
||||
|
||||
- **막혔던 곳** (몇 분 / 어디서):
|
||||
- **예상과 다른 점**:
|
||||
- 예: ArchUnit 의 generic type 처리 방식이 예상과 달랐다 / `ResponseEntity` 의 raw type 만 가능한 줄 알았는데 generic 도 가능했다 / 의도적 위반 메시지가 stack trace 안에 묻혀 있었다
|
||||
- **다음 반복에서 개선할 점**:
|
||||
- 베이스라인 측정 자동화? IDE 단축키? grep alias?
|
||||
- rule 작성 전 *제일 단순한 1개 메서드* 부터 잡고 정교화하는 순서?
|
||||
- **부수 효과로 발견한 것**:
|
||||
- 예: 현재 코드베이스의 다른 패턴 위반 발견
|
||||
- **이 과제의 난이도가 적정했는가**: `너무 쉬움` / `적정` / `너무 어려움`
|
||||
- **시니어 사고 체크 항목** (필수):
|
||||
- 본 rule 의 trade-off 1-2개를 명시했는가?
|
||||
- 우회 가능 시나리오를 예측했는가?
|
||||
- 본 rule 이 잡지 *못하는* 경계 leak 패턴은? (예: `Object` 반환, raw `Map`, exception body)
|
||||
|
||||
## 9. 출처 / Sources
|
||||
|
||||
| Source | 정당화 영역 |
|
||||
|---|---|
|
||||
| [[raw/company-tech-blogs/skillable-hands-on-lab-structure]] | template 9-section 구조 |
|
||||
| [[raw/company-tech-blogs/deliberate-practice-software-developers-redgreencode]] | §5 단계 분할 + §8 reflection |
|
||||
| [[raw/branch-notes/feature-boundary-validation-mapping-contract]] | D1, D8, Claims To Verify 1번째 항목 (본 과제가 검증하는 결정) |
|
||||
| [[raw/project-notes/ca-skeleton-operational-contract]] | §4 Boundary Validation & Mapper Contract |
|
||||
|
||||
## 10. 완료 후 정리 / Closure
|
||||
|
||||
- **최종 status_label**: `done` | `abandoned`
|
||||
- **소요 시간 실측**: <분> (vs duration_estimate 120) — 차이는 §8 회고에
|
||||
- **promotable 후보**:
|
||||
- `actually-implemented` → `feature-boundary-validation-mapping-contract` Claims To Verify 1번째 항목 status 를 `planned` → `actually-implemented` 로 갱신
|
||||
- `locally-verified` → build pass + 의도적 위반 build fail 양쪽 확인
|
||||
- **추출하지 않을 항목** (단순 학습):
|
||||
@@ -1 +0,0 @@
|
||||
../../../vault/50-journal/daily-tasks/infra/2026-05-29-actuator-readiness-probe-db-disconnect-detection.md
|
||||
@@ -0,0 +1,360 @@
|
||||
---
|
||||
title: daily-task / infra / actuator-readiness-probe-db-disconnect-detection
|
||||
source_type: daily-task
|
||||
track: infra
|
||||
status: raw
|
||||
status_label: not-started
|
||||
difficulty: intermediate
|
||||
duration_estimate: 120
|
||||
prerequisites:
|
||||
- "[[raw/project-notes/ca-skeleton-operational-contract]]"
|
||||
- "[[raw/official-docs/runtime-health-spring-actuator-groups]]"
|
||||
parent_project: ca-skeleton-operational-contract
|
||||
parent_branch:
|
||||
target_date: 2026-05-29
|
||||
created: 2026-05-28
|
||||
tags: [daily-task, infra, observability, runtime]
|
||||
---
|
||||
|
||||
# daily-task / infra / actuator-readiness-probe-db-disconnect-detection
|
||||
|
||||
> Layer: `raw/daily-tasks/infra/` — **인프라/운영 트랙 일일 실습 과제**.
|
||||
> `status_label`: `not-started` → `in-progress` → `done`
|
||||
> `difficulty`: `intermediate` (Spring Boot actuator 기본 사용 + k8s probe 개념 가정)
|
||||
> `duration_estimate`: 120 (Apply / 측정 대기 시간 포함)
|
||||
>
|
||||
> **이 과제의 위치**: infra 트랙 1일차. [[raw/project-notes/ca-skeleton-operational-contract]] §15 (Runtime/Lifecycle) — actuator health/readiness/liveness 기준 — 의 *측정 가능한 1차 검증*. develop 첫 과제 (`archunit-controller-domain-return-rule`) 와 같은 날 진행해 코드 contract + 운영 contract 가 한 사이클에 검증되는 경험을 만든다.
|
||||
|
||||
## Parent / 부모 (필수)
|
||||
|
||||
- **Parent project**: [[raw/project-notes/ca-skeleton-operational-contract]] (§15 Runtime/Lifecycle, §18 Metrics/Alerting)
|
||||
- **연관 branch**: (없음 — operational contract 직접 검증)
|
||||
|
||||
## 1. 학습 목표 / Learning Objectives
|
||||
|
||||
- [ ] **L1**: Spring Boot `health/readiness` 와 `health/liveness` 의 의미 차이 — *내 서비스가 트래픽 받을 준비됐는가* (readiness) vs *프로세스를 죽여야 하는가* (liveness) — 를 1분 안에 누군가에게 설명할 수 있다
|
||||
- [ ] **L2**: `application.yaml` 에 actuator health group 을 명시 설정하고 `/actuator/health/readiness` 에 DB indicator 가 포함됨을 검증할 수 있다
|
||||
- [ ] **L3**: DB 단절 시 readiness 가 `OUT_OF_SERVICE` 로 전환되고 이 변화가 *몇 초 만에* (kubectl + prometheus 양 채널) 표면화되는지 *측정값으로* 제시할 수 있다
|
||||
- [ ] **L4 (필수, advanced)**: liveness 는 *동일 상황에서 전환되지 않음* (pod kill ≠ DB 단절) 을 확인하고, 왜 그래야 하는지 trade-off 로 설명할 수 있다 — 이 한 줄이 mid 와 senior 의 차이
|
||||
|
||||
## 2. 스토리라인 / WHY (Storyline)
|
||||
|
||||
[[raw/project-notes/ca-skeleton-operational-contract]] §15 는 "actuator health/readiness/liveness 기준" 을 요구하지만, 많은 프로젝트가 default `/actuator/health` 만 보는 readinessProbe 로 만족한다. 이 default 의 의미는 **"프로세스가 살아있다"** 이지 **"트래픽 받을 준비됐다"** 가 아니다.
|
||||
|
||||
DB가 죽어도 Spring Boot 프로세스는 잘 살아있으니 `/actuator/health` 는 200을 반환하고, k8s readinessProbe 는 *ready* 라고 판정하고, 트래픽이 흘러오고, 5xx 가 양산된다. 알림이 울리고 사람이 새벽에 깨고, root cause 는 "왜 우리는 DB 단절을 readiness 에 반영하지 않았는가" 가 된다.
|
||||
|
||||
오늘은 *그 한 가지* — readiness 를 명시적으로 분리하고 DB indicator 를 포함 — 를 설정하고, 의도적으로 DB 를 *끊었을 때* 몇 초 후 not-ready 가 어디서 어떻게 표면화되는지 *측정값으로* 답할 수 있게 만든다.
|
||||
|
||||
심화 (L4): liveness 는 같은 상황에서 *전환되지 않아야* 한다. 왜냐하면 DB 단절은 *프로세스를 죽일 이유* 가 아니라 *트래픽을 잠시 차단할 이유* 이기 때문. 이걸 헷갈리면 pod 이 재기동 폭주에 들어가서 DB 가 살아나도 cluster 가 회복 불능. 이 trade-off 가 시니어 초반급 사고의 핵심.
|
||||
|
||||
## 3. 환경 / Environment
|
||||
|
||||
**작업 호스트**: 로컬 Linux/macOS/WSL2 (사용자 환경에 맞게)
|
||||
|
||||
**대상 환경**:
|
||||
|
||||
- Cluster: 로컬 `kind` 또는 `k3d` (cluster 없으면 시작 절차에 포함)
|
||||
- Namespace: `ca-tmpl-dev`
|
||||
- Kubeconfig context: `kind-ca-tmpl-dev` (예시)
|
||||
|
||||
**도구 버전**:
|
||||
|
||||
- `kubectl`: 1.30+
|
||||
- `kind`: 0.23+ (또는 `k3d` 5.6+, 또는 minikube)
|
||||
- `docker`: 24.x
|
||||
- Spring Boot: 3.3.x (ca-tmpl 기존)
|
||||
- 관측: Prometheus 2.50+ + Grafana 11.x (kube-prometheus-stack helm chart 권장)
|
||||
|
||||
**사전 셋업**:
|
||||
|
||||
```bash
|
||||
# 1) 작업 디렉토리 + 브랜치
|
||||
cd ~/workspace/ca-tmpl-infra # (또는 ca-tmpl 의 deploy/ 디렉토리)
|
||||
git checkout -b daily-task/infra/actuator-readiness-probe-db-disconnect-detection
|
||||
|
||||
# 2) cluster 확인
|
||||
kubectl config current-context
|
||||
kubectl get ns ca-tmpl-dev || kubectl create ns ca-tmpl-dev
|
||||
|
||||
# 3) 현재 상태 스냅샷 (롤백 reference)
|
||||
kubectl get all -n ca-tmpl-dev -o yaml > /tmp/snapshot-pre-readiness-probe.yaml
|
||||
|
||||
# 4) Prometheus / Grafana 준비 (없으면 설치)
|
||||
helm list -n monitoring | grep prometheus || echo "kube-prometheus-stack 설치 필요"
|
||||
|
||||
# 5) 현재 ca-tmpl 의 application.yaml 확인
|
||||
grep -A 10 'management:' ca-tmpl/src/main/resources/application.yaml || echo "actuator 설정 없음"
|
||||
```
|
||||
|
||||
**변경 예정 리소스**:
|
||||
|
||||
- `ca-tmpl/src/main/resources/application.yaml` — `management.endpoint.health.probes.enabled=true`, group readiness/liveness 명시
|
||||
- `deploy/k8s/ca-tmpl-deployment.yaml` — readinessProbe path 분리, livenessProbe 의 thresholds 명시
|
||||
- (선택) `deploy/k8s/alerts/db-disconnect.yaml` — PrometheusRule 신규
|
||||
|
||||
## 4. 사전 지식 / Prerequisites
|
||||
|
||||
- [[raw/project-notes/ca-skeleton-operational-contract]] §15 (Runtime/Lifecycle) + §18 (Metrics/Alerting) 정독
|
||||
- [[raw/official-docs/runtime-health-spring-actuator-groups]] — actuator health group 공식 spec
|
||||
- (있으면) Kubernetes liveness vs readiness 공식 정의 — `kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/`
|
||||
- Spring Boot `DataSourceHealthIndicator` 의 default 동작 (connection validation query)
|
||||
|
||||
## 5. 단계별 과제 / Exercises
|
||||
|
||||
### Step 1: 베이스라인 측정 (~20min)
|
||||
|
||||
- **What**: 현재 상태를 *수치* 로 기록. 변경 후 비교 가능해야 함.
|
||||
- **How (hint)**:
|
||||
- 현재 `/actuator/health` 응답 body (DB indicator 가 *있는지* 없는지)
|
||||
- `kubectl describe pod <ca-tmpl-pod>` → readinessProbe / livenessProbe 설정 (path, initialDelay, period, threshold)
|
||||
- `kubectl get pod -w` 로 ready 상태 watch
|
||||
- DB container 가 살아있는 동안의 readiness 응답 시간 (curl -w 로 측정)
|
||||
- **Done when**: §7 결과물 섹션에 baseline 표 3행 이상 (`/actuator/health` 응답 type / readinessProbe path / readiness latency)
|
||||
|
||||
### Step 2: actuator group 설정 + manifest 변경 (~30min)
|
||||
|
||||
- **What**: `application.yaml` 에 health group 명시, k8s manifest 의 probe path 분리.
|
||||
- **How (hint)**:
|
||||
|
||||
```yaml
|
||||
# application.yaml
|
||||
management:
|
||||
endpoint:
|
||||
health:
|
||||
probes:
|
||||
enabled: true
|
||||
group:
|
||||
readiness:
|
||||
include: readinessState,db,diskSpace
|
||||
liveness:
|
||||
include: livenessState
|
||||
show-details: never # PII / secret leak 방지 (CLAUDE.md §11)
|
||||
```
|
||||
|
||||
```yaml
|
||||
# k8s deployment.yaml (발췌)
|
||||
spec:
|
||||
containers:
|
||||
- name: ca-tmpl
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/readiness
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
failureThreshold: 3 # = 15초 후 NotReady
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/liveness
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
failureThreshold: 6 # = 60초 후 kill (보수적)
|
||||
```
|
||||
|
||||
- **함정 / 의도적 노출**:
|
||||
- readiness 에 `db` 를 *너무 빨리* 포함시키면 부팅 시점에 DB 가 천천히 ready 되는 동안 pod 도 NotReady → 부팅 지연
|
||||
- liveness 에 `db` 를 포함시키면 *DB 죽었다고 pod kill* — **이게 가장 큰 함정. 의도적으로 절대 안 한다.**
|
||||
- **Done when**:
|
||||
- `kubectl apply --dry-run=server -f <manifest>` 통과
|
||||
- probe path / period / threshold 가 baseline 과 어떻게 다른지 diff 검토 완료
|
||||
|
||||
### Step 3: Apply + 정상 readiness 확인 (~25min)
|
||||
|
||||
- **What**: 실제 apply, rollout 대기, 정상 상태 측정.
|
||||
- **How (hint)**:
|
||||
|
||||
```bash
|
||||
kubectl apply -f deploy/k8s/ca-tmpl-deployment.yaml
|
||||
kubectl rollout status deployment/ca-tmpl -n ca-tmpl-dev --timeout=120s
|
||||
|
||||
# 1) HTTP 응답 직접 확인
|
||||
kubectl port-forward svc/ca-tmpl 8080:8080 -n ca-tmpl-dev &
|
||||
curl -sS http://localhost:8080/actuator/health/readiness | jq .
|
||||
curl -sS http://localhost:8080/actuator/health/liveness | jq .
|
||||
|
||||
# 2) k8s pod 상태
|
||||
kubectl get pod -n ca-tmpl-dev -l app=ca-tmpl
|
||||
|
||||
# 3) prometheus query (kube-state-metrics)
|
||||
# promql: kube_pod_container_status_ready{namespace="ca-tmpl-dev",container="ca-tmpl"}
|
||||
```
|
||||
|
||||
- **Done when**:
|
||||
- readiness 응답 = `{"status":"UP"}` (show-details=never 로 detail 미노출 — §11 정합)
|
||||
- kubectl `READY 1/1`
|
||||
- prometheus 의 `kube_pod_container_status_ready` = 1
|
||||
|
||||
### Step 4: 의도적 DB 단절 → not-ready 전환 시간 측정 (~25min, **본 과제의 핵심**)
|
||||
|
||||
- **What**: DB 를 *끊고* 몇 초 후 readiness 가 false 로 전환되는지 4-5 채널 교차 측정. liveness 는 전환되지 *않음* 을 확인.
|
||||
- **How (hint)**:
|
||||
|
||||
```bash
|
||||
# 1) 측정 시작 시각 기록
|
||||
TS_START=$(date +%s)
|
||||
echo "DB cut at $TS_START"
|
||||
|
||||
# 2) DB 단절 (postgres container stop 또는 service block)
|
||||
kubectl delete pod -n ca-tmpl-dev -l app=postgres
|
||||
# (또는) docker stop ca-tmpl-postgres
|
||||
|
||||
# 3) 즉시 watch 시작 — 별 터미널에서:
|
||||
watch -n 1 "kubectl get pod -n ca-tmpl-dev -l app=ca-tmpl -o wide && curl -sS http://localhost:8080/actuator/health/readiness; echo; curl -sS http://localhost:8080/actuator/health/liveness"
|
||||
|
||||
# 4) NotReady 표면화 시각 측정
|
||||
# - readiness 응답이 503 또는 OUT_OF_SERVICE 로 바뀌는 순간
|
||||
# - kubectl 의 READY 가 0/1 로 바뀌는 순간
|
||||
# - prometheus 의 metric 이 0 으로 바뀌는 순간
|
||||
# 세 값의 차이 자체가 학습 포인트
|
||||
|
||||
# 5) liveness 가 *전환되지 않는지* 확인 (UP 유지)
|
||||
```
|
||||
|
||||
- **측정해야 할 값들**:
|
||||
- T_actuator: actuator readiness 가 OUT_OF_SERVICE 로 전환된 시각 (DB 단절 후 N초)
|
||||
- T_kubectl: `kubectl get pod` 의 READY 가 0/1 로 표시되는 시각
|
||||
- T_prometheus: prometheus metric 이 0 으로 바뀌는 시각 (kube-state-metrics scrape interval 의 영향)
|
||||
- liveness 응답 상태: *반드시* UP 유지
|
||||
|
||||
- **함정 / 트레이드오프 의식** (시니어 사고):
|
||||
- `failureThreshold=3`, `periodSeconds=5` 이면 *최대* 15초 후 표면화 — 더 빨리 잡으려면 period↓ 인데 false positive ↑
|
||||
- HikariCP 의 `connection-timeout` 과 actuator probe timeout 의 상호작용 — actuator가 DB indicator 평가 시 30초 hang 하면 readinessProbe 자체도 timeout
|
||||
- **prometheus scrape interval (예: 30초) 이 alert 표면화의 lower bound** — 5초마다 NotReady 가 토글되면 prometheus 는 못 봄. 이걸 모르면 "왜 alert 가 안 울리지" 미스터리 발생.
|
||||
|
||||
- **Done when**:
|
||||
- 세 측정값 (T_actuator, T_kubectl, T_prometheus) 표로 기록
|
||||
- liveness 가 *전환되지 않음* 명시적으로 확인
|
||||
- **§8 회고에 "왜 세 값이 다른가" 한 문장 답변**
|
||||
|
||||
### Step 5 (선택, advanced): DB 복원 → readiness 자동 복귀 측정 (~20min)
|
||||
|
||||
- **What**: DB 다시 살리고 readiness 가 자동으로 UP 으로 돌아오는 시간 측정 + 그 사이 traffic 처리 동작 확인.
|
||||
- **How (hint)**:
|
||||
- DB pod 재시작
|
||||
- HikariCP 의 connection pool 이 자동 복구되는지 (`hikari.minimum-idle` 영향)
|
||||
- 복귀 시간 = HikariCP retry interval + actuator probe period
|
||||
- **트레이드오프** (시니어 사고):
|
||||
- 자동 복구가 *너무 빠르면* DB 가 flaky 할 때 readiness 가 토글 — load balancer 도 토글
|
||||
- 의도적 hysteresis 권장 (예: 30초 연속 UP 일 때만 ready)
|
||||
- **Done when**: 복귀 시간 측정값 + 그 사이 in-flight 요청의 운명 (drop / 502 / queue) 기록
|
||||
|
||||
## 6. 검증 / Assessment
|
||||
|
||||
**자동 검증** (4-5 채널 중 ≥2개 교차):
|
||||
|
||||
```bash
|
||||
# 1) Probe / health (정상 상태)
|
||||
curl -fsS http://localhost:8080/actuator/health/readiness | jq -e '.status == "UP"'
|
||||
curl -fsS http://localhost:8080/actuator/health/liveness | jq -e '.status == "UP"'
|
||||
# 합격 기준: 두 명령 모두 exit 0
|
||||
|
||||
# 2) k8s 리소스 상태 (rollout 후)
|
||||
kubectl rollout status deployment/ca-tmpl -n ca-tmpl-dev --timeout=60s
|
||||
# 합격 기준: successfully rolled out
|
||||
|
||||
# 3) PromQL — readiness 가 metric 으로 노출
|
||||
# 권장 query: kube_pod_container_status_ready{namespace="ca-tmpl-dev",container="ca-tmpl"}
|
||||
# 합격 기준: 정상 시 = 1
|
||||
|
||||
# 4) DB 단절 시뮬레이션 시 readiness 전환
|
||||
# (Step 4 의 측정 결과를 contract test 로 만들 수 있다면 가산점)
|
||||
|
||||
# 5) Smoke test — 정상 endpoint 가 200 응답
|
||||
curl -fsS http://localhost:8080/api/v1/<sample-endpoint>
|
||||
# 합격 기준: exit 0 (정상 상태에서)
|
||||
```
|
||||
|
||||
**수동 self-check**:
|
||||
|
||||
- [ ] 위 4-5채널 중 ≥2 가 *교차* 확인됨 (단일 채널 의존 금지)
|
||||
- [ ] DB 단절 시 readiness 전환 시간이 measurable (Step 4 측정값 표 존재)
|
||||
- [ ] liveness 가 DB 단절 상황에서 *UP 유지* — 측정으로 확인
|
||||
- [ ] 롤백 명령 (`kubectl apply -f /tmp/snapshot-pre-readiness-probe.yaml`) 이 *완전히* 베이스라인으로 복귀 가능
|
||||
- [ ] L1~L4 학습 목표 모두 *수행 가능* — 특히 L4 (liveness/readiness trade-off) 를 *한 줄로* 설명 가능
|
||||
- [ ] manifest commit 메시지가 "왜" 를 답함
|
||||
|
||||
## 7. 결과물 / Outcomes
|
||||
|
||||
- **commit / PR**:
|
||||
- 브랜치: `daily-task/infra/actuator-readiness-probe-db-disconnect-detection`
|
||||
- commits: <해시 + 1줄>
|
||||
- **변경된 manifest / 설정**:
|
||||
- `ca-tmpl/src/main/resources/application.yaml` — actuator health group 명시
|
||||
- `deploy/k8s/ca-tmpl-deployment.yaml` — probe path 분리, threshold 명시
|
||||
- **측정값 표** (Step 1 baseline vs Step 4 적용 후):
|
||||
|
||||
| 측정 항목 | Baseline | DB 단절 후 |
|
||||
|---|---|---|
|
||||
| `/actuator/health/readiness` 응답 | UP / 200 | OUT_OF_SERVICE / 503 (T초 후) |
|
||||
| `kubectl get pod` READY | 1/1 | 0/1 (T초 후) |
|
||||
| prometheus `kube_pod_container_status_ready` | 1 | 0 (T초 후) |
|
||||
| liveness 응답 | UP | **UP 유지** (의도) |
|
||||
|
||||
- **Dashboard / Alert**:
|
||||
- Grafana panel: `ca-tmpl readiness` (kube_pod_container_status_ready over time)
|
||||
- Alert rule (작성 시): readiness=0 이 60초 지속 시 P2 alert
|
||||
- **Runbook stub**:
|
||||
- 알람 발생 시 1차 확인: `kubectl describe pod -l app=ca-tmpl` + `curl /actuator/health/readiness`
|
||||
- 즉시 fail-fast vs degrade: DB 단절 = readiness 차단 (fail-fast), pod kill 아님 (degrade with traffic block)
|
||||
- **학습한 개념** (wiki/concepts 후보):
|
||||
- readiness vs liveness 의 운영적 차이
|
||||
- HikariCP connection timeout 과 probe timeout 의 상호작용
|
||||
- prometheus scrape interval 이 alert detection 의 lower bound
|
||||
- **다음 과제 thread**:
|
||||
- HikariCP `connection-timeout` 의 적정값 측정
|
||||
- readinessProbe failure 후 traffic drain (Kubernetes service endpoint 갱신 시간)
|
||||
- chaos test 자동화 (chaos-mesh)
|
||||
- circuit breaker (Resilience4j) 와 readiness 의 관계
|
||||
|
||||
## 8. 회고 / Reflection (~5min)
|
||||
|
||||
- **막혔던 곳** (몇 분 / 어디서):
|
||||
- **예상과 다른 점** (특히 측정값 vs 예측):
|
||||
- 예: `failureThreshold=3` 인데 readiness 가 *15초보다 늦게* 표면화 — 왜? (probe timeout? actuator hang?)
|
||||
- prometheus metric 이 *훨씬 늦게* 변함 — scrape interval 영향
|
||||
- **다음 반복에서 개선할 점**:
|
||||
- **부수 효과로 발견한 것**:
|
||||
- **이 과제의 난이도가 적정했는가**: `너무 쉬움` / `적정` / `너무 어려움`
|
||||
- **시니어 사고 체크** (필수 1줄 답변):
|
||||
- "왜 liveness 에 DB 를 포함하면 안 되는가?" — <답>
|
||||
- "T_actuator, T_kubectl, T_prometheus 세 값이 다른 이유는 무엇인가?" — <답>
|
||||
- "readiness 토글 (UP→OUT_OF_SERVICE→UP) 이 잦으면 어떤 운영 문제를 일으키는가?" — <답>
|
||||
|
||||
## 9. 출처 / Sources
|
||||
|
||||
| Source | 정당화 영역 |
|
||||
|---|---|
|
||||
| [[raw/company-tech-blogs/skillable-hands-on-lab-structure]] | template 9-section 구조 |
|
||||
| [[raw/company-tech-blogs/deliberate-practice-software-developers-redgreencode]] | §5 단계 분할 + §8 reflection |
|
||||
| [[raw/project-notes/ca-skeleton-operational-contract]] | §15 Runtime/Lifecycle (probe 기준) + §18 Metrics/Alerting |
|
||||
| [[raw/official-docs/runtime-health-spring-actuator-groups]] | actuator health group 공식 spec — readiness/liveness 분리 근거 |
|
||||
|
||||
## 10. 완료 후 정리 / Closure
|
||||
|
||||
- **최종 status_label**: `done` | `abandoned`
|
||||
- **소요 시간 실측**: <분> (vs 120) — 차이는 §8 회고에
|
||||
- **promotable 후보**:
|
||||
- `actually-implemented` → ca-skeleton-operational-contract §15 의 actuator probe 분리 결정의 *실 구현* 증거
|
||||
- `locally-verified` → DB 단절 → readiness 전환 측정값 4채널 교차 확인
|
||||
- `prod-verified` → (해당 없음 — 로컬 cluster)
|
||||
- **추출하지 않을 항목**:
|
||||
- chaos-mesh 자동화 / circuit breaker 통합 — 별도 daily-task 로 분할
|
||||
|
||||
## 11. 운영 회복력 / Operational Resilience (infra 전용 anchor)
|
||||
|
||||
- **본 변경이 도입하는 새 실패 모드**:
|
||||
- DB indicator 가 *시간이 오래 걸리는 query* 면 readinessProbe 자체가 timeout → false NotReady
|
||||
- probe period 가 *너무 짧으면* DB 가 잠시 hiccup 할 때 ready 토글 → load balancer 토글 → 502 spike
|
||||
- **새 실패 모드의 fail-fast vs degrade 분류**:
|
||||
- DB 단절 = fail-fast (트래픽 차단)
|
||||
- DB 응답 지연 = degrade (slow 응답이지만 트래픽 유지) — readiness 에 포함시킬지 결정 필요
|
||||
- **모니터링 누락 위험**:
|
||||
- prometheus scrape interval 보다 *짧은* not-ready 윈도우는 못 봄 (false success)
|
||||
- alert quiet hours 가 없으면 readiness toggle 시 alert 폭주
|
||||
- **롤백 트리거 조건**:
|
||||
- readiness false 가 5분 지속 + DB 자체는 정상 → 본 변경 자체의 false positive 가능성 → 즉시 롤백
|
||||
- `kubectl apply -f /tmp/snapshot-pre-readiness-probe.yaml`
|
||||
- **연관 alert / runbook**:
|
||||
- [[raw/project-notes/ca-skeleton-operational-contract]] §28 Operational Runbook 의 "DB unavailable" 시나리오와 정합
|
||||
- 본 과제의 PrometheusRule 이 §28 의 1차 alert 항목으로 등록되어야 함
|
||||
Reference in New Issue
Block a user