배포 컨테이너 self-test API — CI 통과 != 배포 환경 정상 #73

Open
opened 2026-04-26 21:41:24 +09:00 by xhh · 0 comments
Owner

동기

현재 CI 는 PR / push 시점에 Forgejo Runner 위에서 pytest 를 돌려 코드의
정합성을 검증한다. 그러나 실제 배포된 컨테이너가 동일한 환경에서 통과
상태인지
는 별도로 보장되지 않는다.

차이가 생길 수 있는 지점:

  • 빌드 스테이지 의존성 픽스 vs 런타임 이미지 의존성 (uv sync --frozen)
  • 컨테이너 환경변수 (PYTHONPATH=/app/src, HOME=/home/app, chmod -R a+rX 등)
  • DB 스키마 자동 동기화 (Base.metadata.create_all lifespan 훅) 의 실제 거동
  • 호스트 네트워크 상의 의존 서비스 (Traefik, host.docker.internal 라우팅)
  • NAS 의 timezone / locale 설정에 의존하는 코드 (utc_now, KST cron)

가설: PR CI 가 초록불이어도 같은 코드를 배포 환경에서 다시 돌렸을 때
실패할 수 있는 케이스가 존재. 이 격차를 운영 도구에서 즉시 감지·검증할
수단이 필요.

제안

1. 배포 컨테이너 안에서 self-test 를 돌리는 API

POST /api/system/selftest      # write 스코프 + 내부망 게이트
GET  /api/system/selftest/last  # 공개? 또는 read-only — 검토
  • POST 는 컨테이너 내부에서 pytest -m smoke (또는 별도 작은 마커) 를
    subprocess 로 실행하고 결과를 메모리/DB 에 저장
  • GET 은 마지막 실행의 요약을 반환 (exit_code, passed, failed,
    duration_ms, started_at, commit)
  • 응답 스키마 예:
    {
      "commit": "abc1234",
      "started_at": "2026-04-26T13:00:00Z",
      "duration_ms": 4521,
      "passed": 122,
      "failed": 0,
      "skipped": 3,
      "exit_code": 0,
      "log_tail": "... pytest 출력 마지막 N 줄 ..."
    }
    

2. 실행 모드

  • on-demand: POST /api/system/selftest (write + private origin 게이트)
  • 자동: 배포 직후 deploy.yml 워크플로 마지막 단계에서 컨테이너 안의
    selftest 를 호출하고 실패 시 deploy 실패 처리. /api/version 검증 다음
    단계로 추가
  • 선택: APScheduler 가 매일 1회 self-test (운영 환경에서 누적 회귀 감지)

3. 보안·자원 고려

  • subprocess 로 pytest 를 띄우면 CPU spike 발생 — 동시 실행 1건 제한
    (lock 파일 또는 in-memory flag)
  • pytest 가 data/financial_data.db 를 건드리지 않도록 in-memory SQLite +
    API_AUTH_DISABLED 강제. 운영 DB 격리 필수
  • 외부 I/O 를 타지 않는 마커만 실행 (-m smoke). 외부 fetch/write 는 별도
    /api/system/selftest/integration 로 분리하고 그건 사용 안 함이 기본
  • POST 는 require_private_origin + require_scope("write") — 공개 인터넷
    차단

4. 마커 정의

pyproject.toml [tool.pytest.ini_options]markers = ["smoke: 빠른 self-test, 외부 I/O 없음"]. 기존 122 case 중 외부 I/O 없는 것 전부 또는
대표 핵심 (api 라우터 happy path + post_then_get 회귀) 만 선택. 1초 이내
완료 목표.

5. 선택지

  • A: 컨테이너 내 pytest 직접 실행 (위 설계). 정직하지만 운영 컨테이너에
    pytest 의존성 + 테스트 코드 동봉이 필요 (이미지 크기 ↑)
  • B: 별도 "test-runner" 사이드카 컨테이너에서 same image 를 돌림.
    운영 이미지 가벼움 유지. compose 복잡도 ↑
  • C: 핵심 invariant 만 검증하는 경량 self-check (DB 스키마, 환경변수,
    주요 라우트 200 OK). pytest 미실행 → 진짜 회귀 누수 가능

추천: A 부터 — 이미지 크기는 #13 에서 별도 다룸. self-test 코드와 운영
코드의 거리가 0 이라 가장 정직.

관련

  • #13 — Docker 이미지 크기 축소 (A 안 채택 시 같이 검토)
  • 배포 검증 엔드포인트 /api/version 과 동일 카테고리 — 배포 정합성 검증의 다음 단계

우선순위

낮음 — Phase 1 게이트(#7) 통과 후 운영 안정화 단계에서 검토.

## 동기 현재 CI 는 PR / push 시점에 Forgejo Runner 위에서 `pytest` 를 돌려 코드의 정합성을 검증한다. 그러나 **실제 배포된 컨테이너가 동일한 환경에서 통과 상태인지** 는 별도로 보장되지 않는다. 차이가 생길 수 있는 지점: - 빌드 스테이지 의존성 픽스 vs 런타임 이미지 의존성 (`uv sync --frozen`) - 컨테이너 환경변수 (`PYTHONPATH=/app/src`, `HOME=/home/app`, `chmod -R a+rX` 등) - DB 스키마 자동 동기화 (`Base.metadata.create_all` lifespan 훅) 의 실제 거동 - 호스트 네트워크 상의 의존 서비스 (Traefik, host.docker.internal 라우팅) - NAS 의 timezone / locale 설정에 의존하는 코드 (utc_now, KST cron) **가설**: PR CI 가 초록불이어도 같은 코드를 배포 환경에서 다시 돌렸을 때 실패할 수 있는 케이스가 존재. 이 격차를 운영 도구에서 즉시 감지·검증할 수단이 필요. ## 제안 ### 1. 배포 컨테이너 안에서 self-test 를 돌리는 API ``` POST /api/system/selftest # write 스코프 + 내부망 게이트 GET /api/system/selftest/last # 공개? 또는 read-only — 검토 ``` - POST 는 컨테이너 내부에서 `pytest -m smoke` (또는 별도 작은 마커) 를 subprocess 로 실행하고 결과를 메모리/DB 에 저장 - GET 은 마지막 실행의 요약을 반환 (`exit_code`, `passed`, `failed`, `duration_ms`, `started_at`, `commit`) - 응답 스키마 예: ```json { "commit": "abc1234", "started_at": "2026-04-26T13:00:00Z", "duration_ms": 4521, "passed": 122, "failed": 0, "skipped": 3, "exit_code": 0, "log_tail": "... pytest 출력 마지막 N 줄 ..." } ``` ### 2. 실행 모드 - **on-demand**: `POST /api/system/selftest` (write + private origin 게이트) - **자동**: 배포 직후 `deploy.yml` 워크플로 마지막 단계에서 컨테이너 안의 selftest 를 호출하고 실패 시 deploy 실패 처리. `/api/version` 검증 다음 단계로 추가 - **선택**: APScheduler 가 매일 1회 self-test (운영 환경에서 누적 회귀 감지) ### 3. 보안·자원 고려 - subprocess 로 pytest 를 띄우면 **CPU spike** 발생 — 동시 실행 1건 제한 (lock 파일 또는 in-memory flag) - pytest 가 `data/financial_data.db` 를 건드리지 않도록 in-memory SQLite + `API_AUTH_DISABLED` 강제. **운영 DB 격리 필수** - 외부 I/O 를 타지 않는 마커만 실행 (`-m smoke`). 외부 fetch/write 는 별도 `/api/system/selftest/integration` 로 분리하고 그건 사용 안 함이 기본 - POST 는 `require_private_origin` + `require_scope("write")` — 공개 인터넷 차단 ### 4. 마커 정의 `pyproject.toml` `[tool.pytest.ini_options]` 에 `markers = ["smoke: 빠른 self-test, 외부 I/O 없음"]`. 기존 122 case 중 외부 I/O 없는 것 전부 또는 대표 핵심 (api 라우터 happy path + post_then_get 회귀) 만 선택. 1초 이내 완료 목표. ### 5. 선택지 - **A**: 컨테이너 내 pytest 직접 실행 (위 설계). 정직하지만 운영 컨테이너에 pytest 의존성 + 테스트 코드 동봉이 필요 (이미지 크기 ↑) - **B**: 별도 "test-runner" 사이드카 컨테이너에서 same image 를 돌림. 운영 이미지 가벼움 유지. compose 복잡도 ↑ - **C**: 핵심 invariant 만 검증하는 경량 self-check (DB 스키마, 환경변수, 주요 라우트 200 OK). pytest 미실행 → 진짜 회귀 누수 가능 추천: **A** 부터 — 이미지 크기는 #13 에서 별도 다룸. self-test 코드와 운영 코드의 거리가 0 이라 가장 정직. ## 관련 - #13 — Docker 이미지 크기 축소 (A 안 채택 시 같이 검토) - 배포 검증 엔드포인트 `/api/version` 과 동일 카테고리 — 배포 정합성 검증의 다음 단계 ## 우선순위 낮음 — Phase 1 게이트(#7) 통과 후 운영 안정화 단계에서 검토.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
xhh/financial-data-platform#73
No description provided.