CI/CD
GitHub Actions 배포가 성공해도 웹이 예전 버전일 때 확인한 캐시 비교
CI 초록불만으로 배포 완료를 판단하지 않고 빌드 캐시, CDN, 브라우저 캐시를 나눠 검증한 비교 글입니다.
초록불인데 변경 사항이 안 보였다
문구 하나를 고친 뒤 GitHub Actions는 성공으로 끝났지만, 휴대폰에서는 옛 문구가 계속 보였다. 서버에서 git rev-parse HEAD를 확인하니 최신 커밋이었다. 이때 “배포가 안 됐다”고 뭉뚱그리지 않고, 캐시가 생길 수 있는 위치를 세 구간으로 나눴다.
| 구간 | 확인 방법 | 의심할 값 |
|---|---|---|
| 빌드 산출물 | CI 로그와 커밋 SHA | 잘못된 checkout, 재사용된 산출물 |
| 원본 서버 | curl로 직접 요청 | 프로세스, reverse proxy |
| 사용자 경로 | 응답 헤더와 새로고침 | CDN, 브라우저 캐시 |
먼저 산출물의 기준을 고정했다
Actions에서 checkout 단계 뒤에 SHA를 출력하도록 했다. 캐시 키는 브랜치 이름만 쓰지 않고 lockfile과 런타임 버전을 포함했다.
- uses: actions/checkout@v4
- name: Print revision
run: git rev-parse HEAD
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
문제의 실행에서는 SHA가 맞았고 빌드 결과에도 새 문구가 있었다. 따라서 CI 캐시를 지우는 것으로 끝내지 않았다.
원본과 공개 주소를 따로 요청했다
Nginx가 있는 서버의 로컬 포트와 공개 도메인에 각각 요청해 헤더를 비교했다.
curl -sS -D - http://127.0.0.1:3000/ -o /tmp/local.html
curl -sS -D - https://blog.example.com/ -o /tmp/public.html
sha256sum /tmp/local.html /tmp/public.html
원본 HTML은 새 문구였지만 공개 응답에 age가 높게 남아 있었다. CDN 캐시가 원인이었다. HTML은 변경 즉시 반영되어야 해서 s-maxage를 짧게 잡고, 정적 자산에는 해시 파일명을 쓰는 쪽으로 정책을 나눴다.
무엇을 캐시할지 결정하는 표
- HTML: 짧은 공유 캐시 또는 배포 후 purge
- 해시가 붙은 JS/CSS: 긴 캐시
- 관리자 응답:
private, no-store - 인증이 섞인 응답: 공개 CDN 캐시 금지
이번 일의 완료 조건도 바꿨다. Actions 성공은 빌드 단계의 성공일 뿐이고, 공개 URL의 응답 본문과 헤더까지 확인해야 배포 완료로 기록한다. 브라우저에서 한 번 더 새로고침하는 것보다, 각 경계를 직접 요청하는 편이 훨씬 빨랐다.