콘텐츠로 이동

컨테이너와 예측 API

1일차에서 Candidate B는 평가 조건을 통과했지만, 그 승인만으로 사용자 요청을 처리할 수는 없습니다. 모델 파일은 점수를 계산하는 규칙일 뿐이고, 요청을 받아 입력을 검사하고 같은 라이브러리로 실행하려면 API와 컨테이너가 함께 필요합니다.

이 페이지의 역할은 FastAPI나 Docker를 처음부터 만드는 것이 아닙니다. 제공된 실행 설정을 읽고, 같은 실행 환경이 왜 필요한지와 HTTP 200, 422, 413, 5xx, 요청 ID가 각각 무엇을 증명하는지를 구분합니다. 이어받는 근거는 Candidate B의 모델 승인과 모델 파일의 내용 지문이며, 여기서 남기는 판단은 요청이 어느 층에서 처리됐는지입니다.

어떤 모델 파일이 실제로 연결됐는지는 이 페이지의 200만으로 확정하지 않습니다. 그 확인은 실행 모델 확인에서 API 응답, 모델 묶음, 배포 설정을 대조할 때 이어갑니다. 수업용 공개 주소는 실습 환경 준비에서 만들고, 직접 호출하는 절차는 서빙 실습에 있습니다.

이 페이지에서 구분할 것

  • 컨테이너 이미지는 실행 환경을 고정하고, 컨테이너는 그 환경을 지금 실행한 프로세스입니다.
  • 모델 파일은 이미지와 별도로 연결되는 예측 규칙입니다.
  • HTTP 200은 이 요청이 처리됐다는 뜻이며, 모델 승인이나 대상 배포 완료가 아닙니다.

같은 실행 환경이 필요한 이유

승인된 모델 파일만으로는 같은 예측을 반복했다고 말할 수 없습니다. PC마다 설치된 Python과 라이브러리 버전이 다르면, 같은 파일을 읽어도 실행 도중 오류가 나거나 결과가 달라질 수 있습니다. 1일차에서 남긴 것은 어떤 모델 파일을 승인했는지이고, 그 파일을 어떤 실행 조건에서 돌릴지는 아직 열려 있습니다.

컨테이너 이미지(container image)는 애플리케이션 코드와 그 실행 조건을 한 꾸러미로 고정하는 실행 설계도입니다. 이미지를 같게 맞추면 담당자 PC, 로컬 연습 환경, 나중에 보는 대상 환경이 서로 다른 라이브러리를 쓰지 않게 할 수 있습니다. 이미지는 반복 가능한 실행 조건을 만드는 수단이지, 모델 품질을 보증하는 결과가 아닙니다.

실행 조건을 맞추는 기준은 사람마다 다른 “내 PC 설정”이 아니라 이미지에 넣은 파일입니다. 이 과정에서는 apps/risk_api/Dockerfile과 uv.lock으로 Risk API 이미지 내용을 고정합니다. 한 PC에는 최신 라이브러리가 있고 다른 PC에는 없는 상태라도, 같은 이미지로 실행하면 그 차이는 실행 조건 후보에서 빠집니다.

이미지 내용이 같다고 모델 파일과 예측 결과까지 항상 같지는 않습니다. 이미지는 실행 환경을 반복 가능하게 만들 뿐이고, 어떤 모델 파일을 연결했는지는 다음 절에서 따로 식별합니다.

이미지, 컨테이너, 모델 파일

실행 환경을 이미지로 고정해도, 지금 돌아가는 프로세스와 예측 규칙은 아직 같은 물건이 아닙니다. 이미지 이름만 보고 모델이 같다고 쓰면, 다른 모델 파일을 읽으면서도 같은 서비스를 확인했다고 보고하게 됩니다.

이미지는 실행 재료이고, 컨테이너(container)는 그 재료로 지금 돌아가는 프로세스이며, 모델 파일(model file)은 예측에 쓰는 학습 결과입니다. 세 대상은 포함 관계가 아니라 연결 관계입니다. 컨테이너는 이미지를 실행해 만들어지고, 모델 파일은 그 컨테이너가 읽도록 따로 붙입니다.

세 대상이 보여 주는 사실이 다르므로, 한 대상의 정상으로 다른 대상까지 통과했다고 쓰지 않습니다.

대상 고정하는 것 이 대상만으로 아직 모르는 것
이미지 코드, 라이브러리, 기본 실행 명령 지금 어떤 모델 파일을 읽는지
컨테이너 그 이미지를 실행한 프로세스와 포트, 연결 경로 대상 환경에도 같은 연결이 있는지
모델 파일 점수 계산에 쓰는 예측 규칙 그 파일이 승인된 후보와 같은지

이 과정에서는 model.joblib을 이미지에 넣지 않고 읽기 전용으로 연결합니다. 같은 Risk API 이미지라도 연결한 모델 파일이 다르면 예측 규칙이 달라집니다. 실행 환경과 모델 내용은 따로 식별해야 하며, 파일 이름만 같다고 같은 모델이라고 단정하지 않습니다.

컨테이너 실행 과정

세 대상의 이름만으로는 API가 어떤 모델 파일을 읽는지 알 수 없습니다. 실행 명령이 이미지, 포트, 모델 경로를 어떻게 연결하는지를 봐야 로컬에서 확인한 범위를 말할 수 있습니다.

로컬 Compose는 Dockerfile로 만든 이미지, 읽기 전용 모델 연결, 127.0.0.1:8000 포트를 한 실행으로 묶습니다. 이미지를 만드는 일과 컨테이너를 시작해 포트와 모델 저장소를 연결하는 일은 이어지지만 같은 증거가 아닙니다. 수강생은 이미지를 새로 빌드하지 않고, 강사가 안내한 이미지로 기존 운영 모델 API가 뜨는지만 확인합니다.

이 실행이 만드는 근거는 로컬에서 Risk API에 접근할 수 있는지와, 고정 경로의 모델 파일을 읽도록 선언했는지입니다. 명령은 tta-aiqa 저장소 루트에서 실행합니다.

docker compose -f deploy/compose.yaml up -d --no-build risk-api
curl http://127.0.0.1:8000/health/ready

준비 상태 응답은 로컬 프로세스가 요청을 받을 준비가 됐다는 뜻입니다. Compose의 포트는 기본으로 127.0.0.1에만 붙고, 모델 경로는 /runtime/model에 읽기 전용으로 연결됩니다. --no-build는 이미지를 새로 만들지 않고 안내된 이미지로 컨테이너를 시작한다는 뜻입니다. 공개 주소와 로컬 루프백은 서로 다른 확인 범위이므로 한 환경의 근거로 섞지 않습니다.

로컬 Compose가 떠 있다고 Kubernetes에도 같은 컨테이너와 모델이 적용된 것은 아닙니다. Docker를 사용할 수 없으면 컨테이너 성공을 추정하지 않고, 이미지 설정과 모델 패키지 연결만 확인한 뒤 시작하지 못한 이유를 남깁니다. 직접 실행하는 절차와 막혔을 때의 기록 방법은 서빙 실습을 따릅니다.

모델 서빙 구조

컨테이너가 실행 중이어도 요청 검사와 점수 계산이 한 덩어리는 아닙니다. 입력 오류와 모델 계산 실패를 같은 문제로 묶으면, 다음에 누구의 어떤 자료를 봐야 하는지 고를 수 없습니다.

모델 서빙(model serving)은 공개 요청을 받아 점수를 계산하고 예측을 돌려주는 실행 구조입니다. 이 과정에서 Risk API는 공개 요청 계약을 확인하고, 점수 계산은 가상 컴퓨터 안 모델 파일 또는 KServe 예측 서비스 중 하나가 맡습니다. Risk API가 모델 파일을 직접 계산한다고 보거나, KServe가 입력 계약을 대신 검사한다고 보면 실패 위치를 잘못 적게 됩니다.

요청을 받는 층과 점수를 계산하는 층을 나누면, 오류가 난 뒤에 먼저 볼 자료가 달라집니다.

층 맡는 역할 정상일 때 보이는 것 문제가 있을 때 먼저 볼 것
Risk API 공개 경로로 요청을 받고 입력을 검사하며 응답을 만듦 규약에 맞는 요청이 예측 응답으로 바뀜 요청 본문, HTTP 상태, 오류 코드
점수 계산 검사를 통과한 입력으로 점수를 계산 점수와 임계값으로 예측이 만들어짐 모델 파일 연결, 예측 서비스 준비 상태
공개 입구 수업용 HTTPS 또는 로컬 루프백으로 요청을 받음 /v1/predict로 예측을 보낼 수 있음 공개 입구가 아닌 내부 추론 경로인지

KServe의 내부 추론 경로는 공개 입구에 없습니다. 공개로 보내는 예측은 /v1/predict입니다. 입력 오류는 API 경계에서 끝나고, 모델 계산 층까지 내려가지 않습니다. 요청 처리와 모델 계산의 역할을 나눠 봐야 어느 층 문제인지 말할 수 있습니다.

예측 요청 처리 흐름

역할을 나눈 뒤에는 한 예측 요청이 어떤 순서로 지나가는지를 고정해야 합니다. 순서를 모르면 422를 모델이 잘못된 입력을 스스로 고친 결과로 읽거나, 검사가 끝나기 전에 점수가 나왔다고 오해합니다.

처리 순서는 HTTP 수신, JSON 해석, 입력 검사, 점수 계산, 응답 직렬화입니다. FastAPI는 Python 함수와 입력 형식을 HTTP 경로에 연결하는 웹 프레임워크이며, 이 과정의 Risk API는 이미 이 순서로 /v1/predict를 제공합니다. 수강생은 FastAPI 서비스를 처음부터 작성하지 않고, 제공된 경로가 모델 실행 전에 입력을 검사하는지만 확인합니다.

한 경로의 성공으로 모든 단계가 통과됐다고 쓰지 않습니다. /v1/predict를 읽을 때는 각 단계가 남기는 근거를 따로 봅니다.

단계 이 단계에서 하는 일 통과하지 못하면
HTTP 수신 공개 경로로 POST 요청을 받음 요청이 API에 도달하지 않음
JSON 해석 본문을 JSON 객체로 읽음 본문을 읽기 전에 크기 제한으로 거절될 수 있음
입력 검사 빠진 항목, 추가 항목, 자료형을 확인 모델을 호출하지 않고 422로 끝남
점수 계산 검사를 통과한 입력으로 점수를 만듦 서버 처리 실패면 5xx 후보
응답 점수, 예측, 모델 정보, 요청 ID를 JSON으로 돌려줌 200은 이 요청의 처리 성공만 뜻함

입력 검사를 통과한 뒤에만 모델을 호출합니다. 빠진 항목, 추가 항목, 자료형 오류는 모델 실행 전에 걸러지므로, 모든 422를 모델 품질 문제로 보지 않습니다. 정상 응답의 필드 뜻은 다음 절에서, 입력 오류와 본문 크기 오류는 그 다음 절에서 나눕니다.

정상 응답 200

입력 검사를 통과한 요청은 점수 계산까지 가서 정상 응답을 남깁니다. 이 응답을 모델 승인이나 대상 배포 완료로 읽으면, 1일차 평가 결과와 지금 확인한 한 요청을 같은 근거로 합치게 됩니다.

HTTP 200은 확인한 요청 하나가 API 규약에 맞게 처리됐다는 뜻입니다. 올바른 입력이 들어오면 점수, 예측, 모델 식별 정보, 교육용 사용 경계가 함께 반환됩니다. 점수를 임계값과 비교해 high_risk 또는 low_risk를 읽고, education_only가 참인지 함께 봅니다. 이 과정의 기준 모델은 임계값 0.5를 사용합니다.

응답 필드는 각각 다른 질문에 답하므로, 점수 하나만 복사해 품질 결론을 내지 않습니다.

필드 뜻 보고서에 쓰는 방식
request_id 이 요청을 나중에 찾기 위한 연결값 확인한 요청의 ID를 그대로 적음
model_profile, model_version 응답이 밝힌 모델 정보 확인한 URL과 함께 적고, 배포 완료로 바꾸지 않음
score 고위험으로 분류될 가능성 실제 응답에서 본 값만 적음. 문서 예시를 복사하지 않음
threshold 점수를 예측으로 바꾸는 기준 평가 때 사용한 값과 같은지 다음에 대조
prediction high_risk 또는 low_risk 이 요청의 예측이며 임상 판단이 아님
education_only 교육용 분류 경계 true이면 의료 판단으로 쓰지 않음

정상 요청을 직접 보내는 절차는 서빙 실습에 있습니다. 아래 JSON은 응답 형식의 예시이며, score와 prediction은 입력마다 달라지므로 문서의 숫자를 보고서에 복사하지 않습니다.

{
  "request_id": "...",
  "model_profile": "baseline",
  "model_version": "baseline-f2576f12512a",
  "score": 0.029262,
  "threshold": 0.5,
  "prediction": "low_risk",
  "education_only": true
}

200은 요청 처리 성공입니다. 예측 품질 승인, Candidate B 대상 배포 완료, 임상 사용 허가가 아닙니다. high_risk가 나왔다는 사실만으로 품질이 높다고 쓰지 않습니다. 지금 실행 중인 모델이 승인 후보와 같은지는 실행 모델 확인에서 대조합니다.

입력 오류 422

200을 본 뒤에도 잘못된 입력이 모델까지 조용히 들어가는지 따로 봐야 합니다. 오류 응답이 없으면 입력 검사가 동작한다고 말할 수 없고, 422를 성능 저하로 읽으면 원인 후보를 잘못 고르게 됩니다.

입력이 공개 계약과 다르면 모델을 호출하지 않습니다. 누락, 추가, 자료형 오류는 같은 입력 검사에서 멈추고, 응답은 HTTP 422와 MODEL_INPUT_INVALID입니다. 이름만 다른 키나 이전 과정에서 쓰던 다른 입력 묶음은 이 계약이 아니므로 422가 맞습니다.

세 요청이 같은 층에서 거절되는지 보는 것이 이 절의 확인입니다.

잘못된 입력 검사에서 멈추는 이유 확인하는 것
필수 항목 누락 계약에 필요한 값이 없음 모델 실행 전에 거절되는가
계약에 없는 항목 추가 모르는 키를 조용히 무시하지 않음 추가 값이 점수로 통과하지 않는가
자료형 오류 숫자여야 할 값이 문자이거나 유한하지 않음 형식 오류가 계산으로 이어지지 않는가

422는 입력 규약 오류이지 모델 계산 실패, 서버 장애, 모델 성능 저하가 아닙니다. 오류 응답은 원본 입력을 그대로 돌려주지 않으며, 제한된 오류 코드와 분류만 남깁니다. 의도한 잘못된 요청으로 422를 확인했다면 “입력 검사가 동작한다”고 적을 수 있고, 그 결과를 모델이 나빠진 근거로 바꾸지 않습니다.

요청 크기 413과 서버 오류 5xx

422만 보면 본문 크기 초과와 서버 실패를 같은 실패로 묶기 쉽습니다. 상태 코드를 합치면, 입력 계약을 고칠 일과 서버 층을 조사할 일을 구분하지 못합니다.

상태 코드는 어느 층에서 멈췄는지를 알려 줍니다. /v1/predict 본문이 configs/serving/api.yaml의 기본 제한 65,536바이트를 넘으면 본문을 파싱하거나 기록하지 않고 HTTP 413과 REQUEST_BODY_TOO_LARGE로 거절합니다. 입력 계약이 깨지면 422이고, 서버 처리가 실패하면 5xx입니다. 셋은 같은 실패가 아닙니다. 이 실습에서 413과 5xx를 직접 만들지 않을 수 있으며, 두 값은 설정과 처리 층의 경계입니다.

상태 코드를 한 종류의 실패로 합치면 다음에 볼 자료가 섞입니다. 실패 위치를 나누면 응답을 실제로 본 뒤에 쓸 문장이 달라집니다.

HTTP 상태 멈춘 층 응답을 본 뒤에 쓸 수 있는 판단 아직 쓰지 않는 판단
413 본문을 읽기 전 크기 제한 요청이 너무 커서 파싱하지 않았다 입력 필드 이름 오류와 같은 422다
422 입력 검사 계약과 다른 입력을 모델 실행 전에 거절했다 모델 성능이 떨어졌다
5xx 서버 처리 서버 층에서 처리가 실패했다 입력 오류나 모델 결함을 바로 증명한다

413과 5xx는 조사 시작점이지, 이 페이지에서 반드시 재현하는 결과가 아닙니다. 413을 서버 장애로 합치지 않고, 5xx를 모델 품질 문제로 바로 바꾸지 않습니다. 해당 응답을 보지 않았다면 관측했다고 쓰지 않고, 한 번의 실패로 대상 환경 전체 장애를 단정하지 않습니다.

요청 식별값

상태 코드만으로는 같은 요청을 로그와 처리 경로에서 다시 찾기 어렵습니다. 200, 422, 413을 구분해도 ID가 없으면, 나중에 본 기록이 지금 확인한 요청인지 연결할 수 없습니다.

요청 ID(request ID)는 한 요청을 응답, 로그, 처리 경로에서 찾기 위한 연결값입니다. 클라이언트가 X-Request-ID를 보내면 응답의 request_id와 운영 기록에 같은 값이 반복됩니다. 같은 ID로 여러 자료를 이을 수 있지만, 사용자 신원을 확인하는 인증 정보는 아니며 ID를 찾았다고 원인까지 밝혀진 것도 아닙니다.

외부에서 온 요청 ID를 신원처럼 신뢰하지 않습니다. X-Request-ID는 영문자나 숫자로 시작하는 최대 64자의 허용 문자만 받고, 유효하지 않은 값은 서버가 만든 UUID로 바꿉니다. 이 제한은 인증을 대신하지 않으므로, 로컬 Compose 포트는 기본 localhost 바인딩을 유지합니다.

요청 ID는 연결값이지 원인 증명이 아니므로, 하는 일과 하지 않는 일을 나누어 기록합니다.

확인한 자료 요청 ID로 할 수 있는 일 요청 ID만으로 할 수 없는 일
응답 본문과 X-Request-ID 헤더 확인한 요청의 연결값을 보고서에 남김 호출자가 허가된 사용자인지 증명
구조화 로그 같은 ID의 오류 코드와 처리 시각을 찾음 모델 파일이 승인 후보와 같다고 확정
처리 경로 그 요청이 어느 층을 지났는지 따라감 원인 후보 하나를 자동으로 확정

요청 ID는 조사 연결값이며 인증 정보나 원인 증명이 아닙니다. 로컬에서 확인한 기준 모델의 200, 422, 요청 ID를 적었다면 이 페이지의 판단은 충분하고, Candidate B가 대상 환경에서 실행 중인지는 아직 모릅니다. 다음 확인은 실행 모델 확인에서 지금 돌아가는 모델 정보를 대조하는 일입니다.