본문으로 건너뛰기
개발 머꼬
개발 노트GitHub Actions
hohyeon.dev2

테스트가 실패하자 정리 step까지 건너뛰어 컨테이너가 그대로 남아 있던 이유

  • #CI/CD
  • #Engineering Note
  • #GitHub Actions

문제 발생

self-hosted 러너에서 E2E 테스트를 돌리는 job이었습니다. 테스트용 컨테이너를 띄우고, 테스트를 돌리고, 마지막에 내리는 단순한 구조였습니다.

steps:
  - uses: actions/checkout@v4
  - run: docker compose -f e2e.yml up -d
  - run: pnpm test:e2e
  - name: 정리
    run: docker compose -f e2e.yml down -v

테스트가 한 번 실패한 뒤로 다음 실행부터는 port is already allocated로 시작조차 못 했습니다. 실행 기록을 열어 보니 정리 step 옆에 건너뜀 표시가 붙어 있었습니다. 컨테이너가 러너에 그대로 살아서 포트를 쥐고 있었던 겁니다.

원인 분석

if를 안 적은 step에도 조건은 걸려 있습니다. GitHub 문서의 상태 확인 함수 설명에 따르면, 이 함수들 중 하나를 넣지 않으면 기본으로 success()가 적용됩니다. success()는 이전 step이 모두 성공했을 때만 참이라, 테스트가 실패한 순간 그 뒤의 step은 전부 건너뜁니다. 정리 step도 예외가 아니었습니다.

그렇다고 아무 데나 always()를 붙이면 되는 것도 아니었습니다. 문서에 따르면 always()는 실행이 취소됐을 때도 step을 돌게 만듭니다. 그래서 문서는 소스 받기처럼 치명적으로 실패할 수 있는 작업에는 always를 쓰지 말라고, 그러면 워크플로가 타임아웃까지 멈춰 있을 수 있다고 경고합니다. 성공·실패와 상관없이 돌리고 싶을 때 권하는 대안은 if: ${{ !cancelled() }}입니다.

결국 step마다 "언제 돌아야 하는가"를 직접 적어 줘야 했던 겁니다.

해결 방안

  1. 조건 네 가지를 구분해서 씁니다. 문서의 설명을 기준으로 정리하면 이렇습니다.
  • 아무것도 안 적음(success()): 이전 step이 모두 성공했을 때
  • failure(): 이전 step 중 하나라도 실패했을 때
  • !cancelled(): 취소되지만 않았다면 성공이든 실패든
  • always(): 취소됐을 때까지 포함해서 무조건
  1. 러너에 흔적을 남기는 정리 작업은 always()로 둡니다. 취소된 실행에서도 컨테이너는 남으니까요. 대신 이 step이 멈추면 끝없이 기다리게 되니 timeout-minutes를 같이 겁니다.
  - name: 정리
    if: ${{ always() }}
    timeout-minutes: 3
    run: docker compose -f e2e.yml down -v
  1. 실패 원인을 남기는 step은 failure()로 둡니다. 성공한 실행에서까지 로그를 쌓을 필요는 없습니다.
  - name: 실패 로그 저장
    if: ${{ failure() }}
    run: docker compose -f e2e.yml logs > e2e.log
  1. 테스트 리포트 업로드는 !cancelled()로 둡니다. 실패한 실행의 리포트야말로 가장 필요하지만, 사람이 취소한 실행의 리포트는 의미가 없습니다. 문서가 권하는 형태 그대로입니다.

  2. 시작할 때 한 번 더 치웁니다. 러너 자체가 죽어 버리면 어떤 조건도 소용이 없습니다. 띄우기 전에 docker compose -f e2e.yml down -v를 먼저 돌려 지난 실행이 남긴 것을 정리하고 시작합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.