Gitea Actions로 Jekyll 배포하기: 검사부터 롤백까지
집 Gitea에서 Jekyll을 빌드해 S3와 CloudFront에 배포한다. 실제 워크플로의 검사, 캐시, 롤백 방식과 아직 남은 연결 고리를 정리했다.
글을 쓴 뒤 main에 push하면 집의 Gitea Runner가 Jekyll 사이트를 만들고 AWS에 올린다. 배포 뒤에는 실제 공개 주소까지 확인한다. 손으로 파일을 복사하던 과정을 없애면서도, 실패 지점을 찾을 수 있도록 단계를 나눴다.
push 한 번에 워크플로 세 개
main push에는 검증, 내부 미리보기, 배포 워크플로가 각각 반응한다. 미리보기는 초안까지 포함해 집 안에서만 열리는 사이트를 따로 만든다. 공개 사이트와 관련된 것은 검증과 배포다.
validate: 원본 검사 → Jekyll 빌드 → 결과물 검사
deploy: 원본 검사 → Jekyll 빌드 → 결과물 검사 → S3 동기화
→ CloudFront 무효화 완료 대기 → 공개 사이트 점검 → prod 태그 시도
원본 검사는 Front Matter, Markdown, 비밀정보 등을 확인한다. 결과물 검사는 필수 파일, 메타데이터, 내부 링크, 초안·예약 글 유출을 확인한다. 처음에는 배포 워크플로가 결과물 검사만 다시 하고 원본 검사는 건너뛰었다. 두 워크플로가 따로 돌기 때문에 validate가 실패해도 배포는 진행될 수 있었다. 지금은 배포 워크플로 맨 앞에서도 같은 원본 검사를 돌려, 실패하면 빌드와 배포까지 가지 않는다.
Runner와 배포 권한
Runner는 Proxmox의 전용 LXC에서 돈다. Ruby·Node·AWS CLI가 든 CI 이미지를 쓰며, 작업 컨테이너에는 Docker 소켓을 넘기지 않았다. 이 설정은 워크플로가 Docker 데몬을 직접 조작하는 경로를 줄여 준다.
배포용 IAM 사용자는 사이트 버킷을 목록 조회하고 객체를 읽고 쓰고 지울 수 있다. 다만 media/에는 쓰기·삭제를 명시적으로 거부한다. 사진 업로드는 별도 계정으로 처리하므로 --delete를 써도 배포 작업이 사진을 지울 권한은 없다. CloudFront 권한도 해당 배포의 무효화 생성·조회로 제한했다.
파일 종류에 따라 캐시를 나누기
배포 스크립트는 정적 파일을 먼저, 페이지 파일을 나중에 동기화한다.
# CSS·JS·이미지·폰트 등
aws s3 sync "$SITE_DIR/" "s3://$BLOG_BUCKET/" --delete \
--exclude 'media/*' \
--exclude '*.html' --exclude '*.xml' --exclude '*.txt' \
--cache-control 'public, max-age=86400'
# HTML·XML·TXT
aws s3 sync "$SITE_DIR/" "s3://$BLOG_BUCKET/" --delete \
--exclude '*' --include '*.html' --include '*.xml' --include '*.txt' \
--exclude 'media/*' \
--cache-control 'public, max-age=300, s-maxage=86400'
페이지는 브라우저에 5분, 공유 캐시에는 최대 하루를 지정한다. 배포할 때 /* 무효화를 만들고 완료를 기다린 뒤 공개 주소를 점검한다. CloudFront 캐시가 비워졌더라도 방문자의 브라우저에 남은 5분 캐시까지 즉시 지울 수는 없다.
점검은 홈, 사이트맵, 피드, 주요 리다이렉트, 없는 페이지의 404, 보안 헤더, 피드의 첫 글을 확인한다. 정상 응답을 확인한다는 점은 유용하지만, 이것만으로 이번 커밋의 내용이 정확히 공개됐는지 증명하지는 않는다.
태그로 되돌리기
배포와 공개 사이트 점검이 끝나면 prod-날짜-시각 태그를 만들어 원격에 push한다. 문제가 생기면 Gitea의 Actions → deploy → Run workflow에서 rollback_to에 그 태그나 커밋을 넣는다. 워크플로가 그 지점을 checkout해 다시 빌드·배포한다.
처음에는 입력 이름을 ref로 썼다가 Gitea의 실행 대상 브랜치 값과 겹쳤다. 수동 실행 API에도 별도의 ref 필드가 있다. 입력 이름을 rollback_to로 바꾸고, checkout 단계에서 명시적으로 사용해 해결했다. 수동 롤백은 과거 결과물을 그대로 복사하는 방식이 아니라 그 시점의 소스로 다시 빌드하는 방식이다. 워크플로 파일은 현재 것이 쓰이지만, 빌드·검사·배포 스크립트는 checkout한 옛 커밋의 것이 쓰인다. 원본 검사 스크립트가 없던 옛 커밋으로 되돌릴 때는 그 단계를 건너뛴다.
한 가지 더: 지금 워크플로의 태그 단계에는 continue-on-error: true가 있다. 태그 push가 실패해도 배포는 성공으로 끝날 수 있다. 롤백 지점을 반드시 남기려면 태그 실패를 무시하지 않도록 바꿔야 한다.
정기 작업과 운영 한계
| 주기 | 작업 |
|---|---|
| 매일 00:05 KST | 그 날짜의 예약 글이 있으면 재빌드·배포 |
| 매주 월요일 | 외부 링크 점검 |
| 매시 23분 | 공개 사이트와 인증서 점검 |
| 매주 일요일 04:17 KST | 저장소 암호화 백업 |
각 작업은 실패하면 Telegram 알림을 보낸다. 다만 Runner가 집에 있으므로 집 인터넷이나 전원이 끊기면 배포뿐 아니라 정기 점검도 멈춘다. 공개 사이트는 계속 열릴 수 있어도, 그동안 새 글 발행과 감시는 비게 된다.