생존과 요청 준비 상태
90분 안팎
학습 목표
프로세스 실행과 요청 처리 가능 상태를 구분합니다.
개념
프로세스가 살아 있어도 요청은 실패합니다
Java 프로세스의 시작 로그는 안내판 배포의 마지막 증거가 아닙니다. 저장 파일을 읽지 못하거나 m06에서 연결한 인증 파일이 사라지면 사용자 조회는 실패합니다. 생존 상태는 실행 중인 프로세스가 응답할 수 있는지를 묻고 준비 상태는 이 인스턴스가 지금 사용자 요청을 받아도 되는지를 묻습니다. 두 질문을 다른 주소로 확인하면 어느 구간을 조사할지 정하기 쉬워집니다. 신입의 배포 리뷰에서는 실행 성공을 보여 주는 대신 요청이 사용하는 의존성까지 확인하게 합니다.
이 모듈의 실행 환경
로컬 zip은 압축을 푼 루트에서 안내한 명령을 실행합니다. Java는 JDK 17과 Spring Boot 3.1.5를 쓰며 ./mvnw test로 검사합니다. 파일 실습은 Python 3와 bash check.sh를 사용합니다. Docker 실습은 개인 daemon에서만 수행하며 실제 실행 검증은 external 대기로 표시합니다. 실습 토큰은 m06의 외부 current/token을 이어받고 화면에 출력하지 않습니다. 프로젝트 자원과 이전 CI archive를 보존합니다. Java wrapper와 테스트는 zip에 있으며 캐시를 이용하는 작성 검사는 오프라인으로 실행합니다.
health와 ready의 서로 다른 계약
기존 /health는 status가 UP인 응답을 반환합니다. 이 주소에 저장소 검사를 한꺼번에 넣으면 저장소 문제와 프로세스 문제를 구분하기 어렵습니다. 이번 /ready는 준비 조건을 만족하면 HTTP 200과 READY, 실패하면 HTTP 503과 NOT_READY를 반환합니다. 비밀 값과 자세한 호스트 경로는 응답에 넣지 않습니다. 이 주소는 교육용 컨트롤러가 직접 구현하며 Spring Boot Actuator가 자동 제공하는 주소가 아닙니다. 의존성을 확인하지 않은 채 Actuator 설정을 찾는 실수를 피합니다.
사용자 작업에서 준비 조건을 고릅니다
사용자는 /notices에서 안내판 제목을 읽으므로 readiness도 NOTICE_DATA의 파일을 실제로 읽습니다. 파일은 일반 파일이어야 하고 65536바이트 이하이며 UTF-8로 읽어 공백을 제거한 내용이 비어 있지 않아야 합니다. 크기 제한은 교육용 계약이며 큰 파일을 반복해서 읽는 비용을 제한합니다. 경로가 있다는 조건만 검사하면 디렉터리나 읽을 수 없는 파일도 준비 완료로 잘못 판정할 수 있습니다. 읽기 가능 비트만 보고 끝내지 않고 실제 파일 읽기의 성공을 확인합니다.
인증 파일도 요청의 의존성입니다
m06 서비스는 X-Lab-Token을 검사합니다. 비밀 경로가 설정된 인스턴스에서는 해당 파일이 일반 파일이고 4096바이트 이하이며 공백 제거 후 16자 이상이어야 합니다. 이 레슨은 토큰 강도 평가가 아니라 앞 모듈의 주입 계약 확인입니다. 비밀 경로가 빈 값이면 앞 단계와 호환하는 공개 모드를 유지합니다. 실제 배포 정책에서는 그 모드를 허용할지 따로 결정합니다. 비밀 파일 읽기가 가능하다는 사실과 인증한 사용자 요청이 성공한다는 사실도 후속 검사에서 각각 확인합니다.
검사가 데이터를 만들면 안 됩니다
기존 /notices는 파일이 없으면 기본 안내문을 만듭니다. readiness에서 그 메서드를 호출하면 누락을 감지하는 대신 파일을 생성하여 문제를 숨길 수 있습니다. ReadyProbe는 디렉터리 생성과 기본값 쓰기를 하지 않고 읽기만 수행합니다. missingDataDoesNotCreateIt 테스트는 준비 실패와 파일 미생성을 함께 검사합니다. 반복 검사 전후의 바이트가 같다는 테스트도 있습니다. 읽기 확인의 부수 효과를 찾아 제거하는 습관은 이후 원본 데이터 보존 조건을 구현할 때도 필요합니다.
경로를 생성자로 전달합니다
ReadyProbe는 데이터와 비밀 경로를 생성자로 받습니다. 실행 시 Spring의 Value가 설정을 연결하고 테스트에서는 임시 경로를 직접 전달합니다. 특정 노트북의 workspace나 실제 비밀 파일에 의존하지 않는 검사를 만들 수 있습니다. Component와 하나의 생성자를 사용해 런타임 연결을 분명히 합니다. 파일 준비 판단은 작은 객체에 두고 HTTP 상태와 JSON 표현은 컨트롤러가 맡습니다. 두 부분을 나누면 실패가 파일 조건을 가리키는지 응답 계약을 가리키는지 빠르게 좁힐 수 있습니다.
읽기 실패는 준비 실패로 변환합니다
Files.readString은 파일 누락·읽기 권한 부족·잘못된 UTF-8 등에서 IOException을 낼 수 있습니다. 이런 실패를 false로 반환하여 /ready가 503을 내도록 합니다. 의존성의 실패를 확인했는데 예외를 잡고 true를 반환하면 보호 장치가 반대로 동작합니다. 개발 환경에서 권한 거부를 재현하기 어렵더라도 누락과 디렉터리·손상 내용은 임시 파일로 검사할 수 있습니다. 오류를 조사할 때 비밀 내용을 출력하지 않고 실패한 검사 종류와 주입 경로 설정부터 확인합니다.
성공과 실패를 함께 요구하는 테스트
starter는 항상 true를 반환하여 정상 파일 사례는 통과하지만 누락·빈 내용·짧은 토큰·디렉터리·큰 파일 사례는 실패합니다. 학습자는 테스트 기대값을 바꾸지 않고 준비 조건을 구현합니다. solution은 11개 ReadyProbeTest와 상속 회귀 검사를 통과해야 합니다. 준비 실패의 컨트롤러 응답은 503이면서 health는 UP인지 확인합니다. 모든 요청을 거부하는 구현도 정상 파일 사례에서 잡힙니다. 사례의 개수보다 각각이 어떤 잘못된 구현을 배제하는지 설명하는 것이 중요합니다.
테스트 메시지에서 계약을 읽습니다
missingDataDoesNotCreateIt의 expected false but was true는 파일이 없는데 준비 완료라고 판단했다는 뜻입니다. validDependencies의 expected true but was false는 준비된 조건을 너무 엄격하게 막은 것입니다. Maven 의존성 해결 오류와 JUnit assertion 실패는 다릅니다. 캐시 문제를 해결하려고 테스트를 삭제하지 않습니다. target/surefire-reports에서 메서드 이름과 기대값을 읽고 임시 파일 조건을 확인합니다. 객체와 임시 파일만 사용하는 기본 검사는 서버를 띄우지 않아 종료 후 프로세스를 남기지 않습니다.
준비 검사와 실제 요청을 연결합니다
파일 검사가 성공한 직후 파일이 바뀔 수도 있어 readiness는 미래 요청의 성공 보장이 아닙니다. 후보 컨테이너에서는 /ready 다음에 인증한 /notices를 보내 상태 코드와 id·title을 비교합니다. 이 순서는 다음 레슨에서 프록시 전환 조건이 됩니다. 실제 Docker UID와 마운트는 객체 테스트만으로 증명하지 않습니다. 생존 UP, 준비 NOT_READY, 사용자 실패가 동시에 나타나는 예를 설명하고 README에 검사 주소와 실패 시 전환 금지를 기록합니다. JUnit 구조의 세부 내용은 더 읽기로 보냅니다.
probe 구분의 참고: Spring Boot 3.1.5 공식 문서. 이번 /ready 구현은 실제 파일 의존성을 포함한 교육 모델입니다.
따라하기
준비 조건을 테스트에서 찾기
starter.zip을 푼 루트에서 11개 준비 조건 테스트를 읽습니다. missingDataDoesNotCreateIt과 validDependencies의 기대값을 먼저 비교합니다. 실행 서버는 필요하지 않습니다.
cat src/test/java/lab/ReadyProbeTest.java
./mvnw test누락 파일에서 데이터 생성 금지 확인
터미널에서는 아래 코드를 python3로 실행합니다. 임시 파일 존재를 읽기만 하면 검사 뒤에도 누락이 유지됩니다. 이는 Java 구현의 부수 효과 금지 계약을 작은 파일 실험으로 확인합니다.
from pathlib import Path
import tempfile
with tempfile.TemporaryDirectory() as d:
p=Path(d)/'notice.txt'
print('before:', p.exists())
ready=p.is_file() and bool(p.read_text().strip())
print('ready:', ready)
print('after:', p.exists())실행 결과
before: False ready: False after: False
유효한 데이터의 읽기 전후 비교
UTF-8 제목을 읽어 공백을 제거하되 원본 바이트는 바꾸지 않습니다. 실제 readiness는 비밀 파일도 검사해야 합니다.
from pathlib import Path
import tempfile
with tempfile.TemporaryDirectory() as d:
p=Path(d)/'notice.txt';p.write_text('배포 연습 안내\n',encoding='utf-8')
before=p.read_bytes()
print(p.read_text(encoding='utf-8').strip())
print('unchanged:', before==p.read_bytes())실행 결과
배포 연습 안내 unchanged: True
ReadyProbe 완성과 회귀 검사
ReadyProbe.ready의 항상 true를 파일 읽기와 준비 조건으로 바꿉니다. solution과 비교한 뒤 JUnit 실패·오류가 0인지 확인합니다. /ready의 200·503 응답과 health UP도 테스트합니다.
./mvnw test확인 문제
실습
ReadyProbe.ready를 구현합니다. 데이터는 일반 파일·65536바이트 이하·읽을 수 있는 UTF-8·공백 제거 후 비어 있지 않은 내용입니다. 설정된 비밀 경로는 일반 파일·4096바이트 이하·공백 제거 후 16자 이상이어야 합니다. 빈 비밀 경로는 앞 모듈 호환 모드입니다. 누락과 읽기 오류는 false이고 파일 생성·수정은 하지 않습니다. 테스트 기대값을 유지하고 /ready 200·503과 독립 health UP을 확인합니다.
실행 명령
./mvnw test
기대 결과
ReadyProbeTest 11개와 상속 회귀 테스트가 실패·오류 없이 통과합니다.
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 환경마다 달라지는 설정을 주입하는 방법을 설명합니다.