작성자: 로빈(임수빈)
우리가 데벨업을 만드는 과정은 다음과 같다.
🌐 기획 → 이슈 → 브랜치 작업 → PR 제출 → 리뷰 및 반영 → main에 머지
간단히 말하면 깃허브 플로우를 사용해 작업을 하고, 이를 개발 서버에 배포하는 방식이다. 초기 버전이 완성되고 나서 실사용자 배포와 개발 서버 배포가 분리되어야 할 시점에는 좀 더 플로우가 복잡해 지겠지만, 지금은 이렇다.
개발을 하다보면 당연히 오류가 발생할 수 있다. 예를 들어 빌드가 되지 않는 경우도 있고, 빌드는 되지만 테스트가 실패하는 경우도 있을 수 있다. 당연히 이런 코드는 배포되면 안된다. 특히, 우리의 작업 플로우에서는 main 브랜치의 매 상태가 특정 기능이 개발된 상태이기 때문에 main 브랜치의 코드는 언제나 배포 가능한 상태여야 한다. 이를 보장하기 위해 CI 작업이 필요했다.
맨 처음 CI 작업을 수행할 때의 요구사항은 다음과 같다.
💡 PR이 제출되면 테스트가 동작한다. → 테스트 결과를 슬랙을 이용해 알린다.
스크립트를 작성하는 과정에서 팀원(릴리)가 좋은 의견을 내주었다.
🙋♀️ 백엔드 작업에서만 CI 가 수행되면 좋겠네요!
우리의 작업 과정 특성상 하나의 이슈에 프론트와 백엔드 작업이 모두 포함되는 경우는 원칙 상 없다. 따라서 프론트 PR 에 백엔드 CI가 굳이 동작할 이유가 없고, 백엔드 PR에 프론트 CI가 필요없다. 따라서 이를 구분하여 워크플로우가 실행되도록 했다. 또한, 아직 깃허브 액션에 대한 이해도 부족으로 모든 작업이 하나의 Job에 포함되고 step 단위로만 구분되어있었다.
이렇게 구현된 상태에선 아래와 같은 문제가 발생했다.
위 사진은 프론트 작업 후 CI 작업 결과다. 백엔드 작업이 아니므로 작업이 스킵되었지만, 실패 판정이 나는 상황이었다. 이는 빠른 실패 및 깔끔한 코드를 작성하겠다며 깃허브 cli 명령을 이용해 작업을 중지했기 때문이다.
이런 문제를 팀원들과 공유하니 팀원(아톰)이 개선 방안을 말해주었다.
🙋♂️ step 단위가 아니라 job 단위로 분리하고, if 조건을 job에다 달면 되지 않을까요?
이 방안에 따라 개선된 CI 과정은 다음과 같다. 또한, 프론트엔드 CI와 백엔드 CI가 두개의 워크플로우로 분리되어있어 한눈에 보이지 않는다고 판단해 하나의 워크플로우로 통합했다
또한, 이 시점에 두개의 정상적인 PR이 머지될 경우 빌드가 되지 않는 의미적 충돌이 발생했고, 우리의 CI 과정이 이를 검증하지 못한다는 것을 알았다. 이를 해결하기 위해 PR 테스트 시 main 코드를 같이 가져와 테스트를 시도하는 것을 고안했다. 하지만 이것이 완전한 해결책이 될 수 없다는 것을 알게되어 팀원들에게 이를 알렸고, 팀원 중 한명(버건디)가 깃허브 자체 설정에 우리 상황을 해결할 수 있는 옵션이 있다는 것을 알려주었다.
위 사진은 PR을 머지하기 위해선 main의 최신 변경사항이 PR에 반영되어있어야 함을 강제하는 옵션이다. 이를 위해서는 main 의 코드를 역으로 PR에 머지를 한 뒤, 이를 다시 원격에 push 해야 하므로 자연스럽게 main의 코드와 합쳐진 코드를 테스트하게 된다. 이것의 활성화를 위해 필수로 통과해야 하는 Check(깃허브 액션 등)을 추가했다.
이전 버전까지는 프론트엔드, 백엔드 영역의 작업을 구분하는 것을 라벨로 구분했다. 라벨을 빼먹은 경우 CI가 수행되지 않을 수 있는 리스크가 존재했다. 따라서, 이를 해결하기 위해 변경되는 파일의 경로를 기준으로 워크플로우가 트리거되도록 변경했다. 이를 위해서는 다시 프론트엔드와 백엔드 CI를 워크플로우 단위로 구분해야 했다. 오히려 하나의 워크플로우로 합쳐서 더 보기 힘들어졌다는 의견도 있었기 때문에 이 부분은 별 문제가 아니었다.
버전 2에서 백엔드 CI 작업과 프론트 CI 작업을 필수로 통과해야 하는 작업으로 지정했다. 깃허브가 작업의 통과를 판정하는 기준과 버전 3에서 변경된 내용이 충돌해 그 어떤 PR도 머지할 수 없는 오류가 발생했다.
깃허브는 작업이 트리거되었으나, 조건에 따라 스킵된 경우 작업을 통과한 것으로 판정하지만, 작업이 아예 트리거되지 않으면 작업을 통과하지 않은 것으로 판정한다. 따라서 버전 3이후 다른 영역의 CI가 아예 작업이 트리거 되지 않아 PR을 머지할 수 없게 된 것이다. 따라서, 트리거 조건이 아니라, 변경 내역을 판단하는 작업을 선행 작업으로 추가하는 방식으로 변경했다.
AWS EC2 에 ssh로 접속할 수 있는 IP 가 잠실 캠퍼스와 선릉 캠퍼스로 제한이 걸려있었다. 따라서, 빌드된 파일을 깃허브가 호스팅하는 runner 에서 ec2 로 전송할 수 없었다.
깃허브 액션은 사용자가 runner 를 직접 호스팅 할 수 있는 설정을 제공한다. 이를 Self-hosted runner 라고 부른다. self-hosted runner는 깃허브와의 연결을 내부에서 먼저 시작하므로 aws 의 보안 그룹 설정과 무관하게 연결이 된다. 즉, 아예 빌드를 우리 ec2에서 할 수 있게 되는 것이다.
앞서 말한 self hosted runner를 사용하면 애초에 빌드가 우리 ec2에서 수행되기 때문에, 빌드 후 바로 bash 스크립트로 명령을 수행하면 된다.
이를 구축하는 과정에서 메모리 부족 문제로 ec2가 죽는 문제가 발생했다. Gradle 빌드 과정이 메모리를 많이 요구하기 때문이다. 따라서 스왑 메모리 설정을 추가해 이를 해결했다.
Gradle 빌드 과정이 메모리를 많이 요구하기 때문에 설령 스왑 메모리를 사용해도 ec2에 많은 부하를 주는 것은 동일하다. ec2 는 웹서버도 실행하기 때문에 이 부하를 제거하는 것이 유리했다.
따라서 빌드와 배포, 메시지 전송을 모두 분리해 배포만 ec2 에서 수행되도록 하여 부하를 제거하고, 워크플로우가 한 눈에 들어오도록 개선했다.