website: CD 파이프라인을 job 단위로 쪼개서 배포 흐름을 그래프로 보이게 하기
cd.yml이 build-and-deploy라는 job 하나에 15개 스텝이 다 몰려 있었다. GitHub Actions 그래프에서는 박스 하나로만 표시되니, 지금 배포가 어디서 멈췄는지 보려면 매번 스텝을 펼쳐서 로그를 훑어야 했다. ci.yml은 이미 여러 job으로 나뉘어 있어서 그래프만 봐도 흐름이 보이는데, cd.yml만 그렇지 않았다.
그래서 같은 방식으로 쪼개기로 했다. config(환경 판별) → migration-check(마이그레이션 위험도 판단) → build(이미지 빌드) → deploy(OCI 배포) → smoke-test(공개 도메인 검증) → confirm(배포 확정) 순서로 나누고, 실패했을 때 갈라지는 rollback과 manual-intervention까지 총 8개 job으로 만들었다. 이렇게 나누면 그래프 자체가 절차도가 된다 — 성공 경로, 추가형 실패로 인한 자동 롤백, 삭제형 실패로 인한 수동 개입 갈림길이 그대로 시각적으로 드러난다.
문제는 스텝을 job으로 쪼개면 steps.config.outputs.xxx 같은 참조가 더 이상 안 먹힌다는 것이었다. 같은 job 안에서만 유효한 컨텍스트라서, job이 갈리는 순간 상태를 어떻게 넘길지 따로 정해야 했다. 살펴보니 실제로 넘겨야 하는 상태는 두 종류뿐이었다. 하나는 config와 migration-check가 만들어내는 문자열 값들(env, tag_prefix, service, port, destructive) — 이건 GitHub Actions의 outputs로 선언하고 뒤 job에서 needs.config.outputs.env 식으로 참조하면 된다. 다른 하나는 배포 전 이전 태그를 백업해두는 .prev_backend_tag_* 파일인데, 이건 deploy job이 SSH로 OCI 서버에 직접 써두고 rollback job이 다시 SSH로 읽어가는 구조다. 이건 GitHub Actions의 job 컨텍스트가 아니라 서버 쪽 파일 시스템에 남는 상태라서, job이 몇 개로 갈리든 그대로 유지된다는 걸 확인하고 나니 안전하게 쪼갤 수 있겠다는 판단이 섰다.
쪼개면서 걸린 부분이 하나 더 있었다. 원래는 각 스텝에 if: failure() && steps.migration-check.outputs.destructive == 'false' 식으로 조건을 걸어서 롤백/수동개입을 구분했는데, job 단위로 나누면 needs.deploy.result나 needs.smoke-test.result를 봐야 한다. 그런데 GitHub Actions는 needs로 지정한 job이 실패하면 그 뒤에 오는 job을 기본적으로 스킵해버린다. deploy가 실패했는데 rollback job이 자동으로 스킵되면 아무 의미가 없다. 그래서 rollback과 manual-intervention 두 job 모두 if: always() && (needs.deploy.result == 'failure' || needs.smoke-test.result == 'failure') && ... 형태로 always()를 붙여서 기본 스킵 동작을 뚫었다. always()가 왜 필요한지는 코드만 봐서는 바로 안 와닿을 수 있어서, 주석에 이유를 남겨뒀다.
confirm job도 마찬가지로 needs 동작을 그대로 활용했다. 롤백용 마커 파일은 deploy와 smoke-test가 둘 다 성공했을 때만 지워야 하는데, 예전에 헬스체크 직후 바로 지우는 방식으로 했다가 스모크 테스트가 실패해도 마커가 이미 사라져서 롤백이 그냥 스킵된 적이 있었다(#133, dev→main 승격 중 prod에서 실측). confirm job을 needs: [config, deploy, smoke-test]로 걸어두면, 둘 중 하나라도 실패했을 때 confirm 자체가 자동으로 스킵되니까 마커가 남아있고, 뒤에서 rollback이 정상 작동한다. 이 부분은 추가 로직 없이 job 의존 관계만으로 해결됐다.
각 스텝 안에서 돌아가는 SSH 스크립트 로직 자체는 손대지 않았다. steps.xxx.outputs를 needs.xxx.outputs로 바꾸고, job 경계와 outputs 선언을 추가한 정도다. 로직을 안 건드렸다고 해서 검증을 생략할 수는 없다고 판단했다 — job으로 쪼개는 과정에서 참조 하나만 빠뜨려도 배포 파이프라인이 통째로 망가지는 종류의 변경이라, dev 브랜치에 실제로 push해서 성공 경로, 추가형 실패로 인한 자동 롤백, 삭제형 실패로 인한 수동 개입 세 가지 경로를 다 확인해볼 계획이다.