[둥지] FastAPI + GitHub Actions: Pydantic 환경 변수(ValidationError) 트러블슈팅
FastAPI 프로젝트에 GitHub Actions CI를 도입하며 겪은 환경 변수 주입 문제를 해결합니다. Pydantic의 엄격한 타입 검증(ValidationError)으로 인한 CI 빌드 실패 원인과, 더미 파일(.env.example) 및 GitHub Secrets를 활용한 해결 과정을 공유합니다.
FastAPI와 pydantic-settings 모듈의 조합은 애플리케이션 시작 시점에 필수 환경 변수가 정확한 타입으로 존재하는지 검증해 준다. 하지만 이 ‘엄격함’ 때문에, 로컬 PC에서는 완벽하게 돌아가던 테스트 코드가 CI 서버(GitHub Actions)에만 올라가면 실패하는 현상을 자주 겪게 된다.
이번 포스트에서는 ‘둥지’ 프로젝트의 통합 테스트 및 CI/CD 파이프라인 구축 과정에서 마주한 환경 변수 누락 및 Pydantic 검증 에러(ValidationError)의 연쇄적인 트러블슈팅 과정을 기록한다.
1. 로컬 환경 - 통합 테스트 Skip 현상
발생 로그: 로컬 환경에서
make test(uv run pytest) 실행 시, 외부 API(행정안전부 주소 검색 등)를 호출하는 통합 테스트(test_juso_client_integration.py)가 실행되지 않고s(Skip) 처리되었다.1 2 3 4 5 6
# 발생 로그 collected 25 items tests\clients\test_juso_client.py ........... [ 44%] tests\clients\test_juso_client_integration.py sssssssssss [ 88%] tests\test_sample.py ... [100%]
- 원인: 통합 테스트 코드 내부에
JUSO_API_KEY를 찾지 못할 경우 실패(F)가 아닌 스킵(s)하도록 방어 로직(pytest.skip("JUSO_API_KEY not found"))이 작성되어 있었다. 기존 환경 변수 파일명의 불일치로 API 키가 로드되지 않았던 것이다. - 해결: 로컬 테스트 프레임워크가 정상적으로 읽을 수 있도록 파일명을
.env에서.env.local로 수정하여 API 키가 올바르게 주입되도록 조치했다.
2. CI 환경 (GitHub Actions) - 환경 변수 누락 에러
현상: CI 파이프라인에서 테스트 실행 시 Pydantic의
ValidationError가 대량으로 발생하며 빌드 실패(Exit code 2).1 2 3 4 5 6 7
# 발생 로그 (12개의 필수 환경 변수 누락) E pydantic_core._pydantic_core.ValidationError: 12 validation errors for Settings E SECRET_KEY E Field required [type=missing, input_value={}, input_type=dict] ... E POSTGRES_USER E Field required [type=missing, input_value={}, input_type=dict]
- 원인: 깃허브 레포지토리에는 보안상 실제
.env.local파일이 올라가지 않는다. 따라서 텅 빈 우분투 러너(Runner)가 테스트를 돌리기 위해config.py의Settings객체를 생성하는 순간, 필수 값을 찾지 못해 대량의 검증 에러를 발생시킨 것이다. - 1차 해결:
ci.yml의 테스트 단계에cp .env.example .env.local명령어를 추가하여 가짜(Dummy) 설정 파일을 복사하여 CI 서버가 이를 읽도록 유도했다.
3. CI 환경 (GitHub Actions) - Pydantic 타입 파싱 에러
현상: 더미 파일을 복사하도록 조치한 후 다시 CI를 돌렸으나, 이번에는 빈 문자열을 숫자로 변환하지 못해 발생하는 타입 파싱 에러가 발생했다.
1 2 3 4
# 발생 로그 E pydantic_core._pydantic_core.ValidationError: 1 validation error for Settings E ACCESS_TOKEN_EXPIRE_MINUTES E Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='', input_type=str]
- 원인:
.env.example파일 내부에ACCESS_TOKEN_EXPIRE_MINUTES=처럼 값이 비어있었다. Pydantic은 값이 비어있는 환경 변수를 빈 문자열("")로 인식한다. 문자열(str) 타입은 에러가 나지 않지만,int나bool등의 타입은 빈 문자열을 형변환할 수 없어int_parsing에러가 발생한 것이다. - 2차 해결: 깃허브에 올라가는
.env.example파일의 비어있던 정수형/논리형 설정값에 Pydantic이 파싱할 수 있는 더미 값(Dummy Value)을 채워 넣었다. (예:ACCESS_TOKEN_EXPIRE_MINUTES=1440)
4. CI 환경 (GitHub Actions) - 외부 API 통합 테스트 처리 및 API 키 연동
현상: 이제 Pydantic 검증은 통과했지만, 로컬에서 겪었던 통합 테스트 Skip 현상이 CI 서버에서도 똑같이 발생했다.
uv run pytest -m integration명령어 실행 시 11개의 테스트가 모두 Skip 되었다.1 2 3 4 5
# 발생 로그 collected 25 items / 14 deselected / 11 selected tests\clients\test_juso_client_integration.py sssssssssss [100%] ========================================================= 11 skipped, 14 deselected in 0.09s =======
해결 방안: 실제 API 키를 코드나 템플릿 파일에 노출할 수는 없으므로, GitHub Secrets를 활용했다.
- GitHub Secrets 설정: 실제 행안부 API 키를 코드에 노출하지 않기 위해 깃허브 레포지토리의 Settings > Secrets and variables > Actions 메뉴에
JUSO_API_KEY를 안전하게 등록. - Workflow 파일(
ci.yml) 수정: CI 구동 시 깃허브 비밀 금고에서 값을 꺼내 환경 변수로 직접 주입하도록 구성.
- GitHub Secrets 설정: 실제 행안부 API 키를 코드에 노출하지 않기 위해 깃허브 레포지토리의 Settings > Secrets and variables > Actions 메뉴에
1
2
3
4
5
6
7
# ci.yml 적용 코드
- name: Run tests
env:
JUSO_API_KEY: $ # Secrets에서 주입
run: |
cp .env.example .env.local
uv run pytest -v
이 조치 이후, CI 서버에서도 외부 API 통합 테스트가 Skip 없이 정상적으로 PASSED 되었다. 🎉
마치며
결국 CI 환경에서 로직 테스트(pytest)를 무사히 통과시키려면, 진짜 운영 환경과 무관하더라도 Pydantic의 엄격한 문지기 역할을 통과할 수 있는 형식에 맞는 가짜(Dummy) 데이터가 필요하다.
- CI 워크플로우 실행 시
cp .env.example .env.local로 껍데기 환경을 만들어 줄 것. .env.example안의 변수를KEY=형태의 빈칸으로 두지 말 것. 정수형에는1, 논리형에는false등을 명시적으로 적어두어야 CI 에러를 예방할 수 있다.- 실제 동작에 필요한 외부 API 키 등은 GitHub Secrets를 통해 동적으로 주입할 것.
Pydantic의 이 엄격함이 CI 구축 초기에는 귀찮게 느껴질 수 있지만, 런타임에 발생할 수 있는 치명적인 환경 변수 설정 오류를 빌드 타임에 완벽하게 차단해 주는 든든한 보호막임을 다시 한번 깨달았다.