La foret rouge
Published on

DEVoca 팀장 기록 #4: 빌드 자동화는 가능하면 꼭 하자

Authors
  • avatar
    Name
    Shin Juyong

DEVoca 프로젝트를 하며 Jenkins를 처음 사용해봤는데, 그 필요성을 절감했다.

1,249개의 커밋, 450번의 빌드

프로젝트 기간은 부트 캠프 1주를 제외하고 6주 가량이 되었으나, 기획이 조금 늦어지는 바람에 실질적인 구현은 1월 27일부터 2월 16일까지 약 3주간 진행되었다. 이 기간 동안 6명의 팀원이 1,249개의 커밋을 하여 351번의 Merge가 발생했다. (이후 컨설턴트님이 말씀하시길 우리 반에서는 우리 팀이 커밋 수가 제일 많았다고 하셨다.)

jenkins

이 때마다 Jenkins가 자동으로 빌드를 하고 배포를 진행했다. 가장 많이 빌드를 한 날은 발표 당일(2/16)이었다. 발표 직전인 오전 11시까지 자잘한 버그 픽스를 할 때마다 Push 이벤트가 발생하여 11시간동안 무려 60회의 빌드가 실행되었고, 전체적으로 봤을 때는 일 평균 28회의 빌드가 있었다.

Jenkins 구축을 제일 먼저 하기를 잘했다

이전에는 대부분 혼자서 한 토이 프로젝트였으니 자동 빌드의 필요성이 크지 않았고, 팀으로 진행한 프로젝트도 있었으나 해당 프로젝트에서는 배포 주기가 매우 길었기 때문에 나에게 Jenkins(또는 CI/CD 파이프라인 구축)는 '익히고 싶지만 당장 우선은 아닌 기술'이었다.

그래서 이번 프로젝트에서는 내가 인프라를 담당하며 제일 먼저 Jenkins로 빌드 과정 자동화를 하는 것을 목표로 삼았다. 팀원이 6명인데, 한 명 한 명 기능을 구현할 때마다 내가 빌드와 배포를 직접 하는 것은 귀찮고 집중이 필요한 일을 하는 도중에 배포 요청이 들어오는 경우도 발생할 것 같았기 때문이다.

이번 프로젝트에서 잘한 선택 중 하나가 이것이었다. 물론 당시에는 이 정도로 많은 커밋과 빌드가 수행될 줄은 몰랐고, 단순히 '6명이 구현한 기능이 merge 될 때마다 빌드를 해야되니 자동화를 해두는게 좋지 않을까?'였다. 하지만 결과적으로 위에서 말한 수치를 다시 보며 내가 하루에 28번 EC2 서버에 접속을 하고 빌드를 수행했다면 내가 맡은 일은 절대 못 했겠다는 생각이 들었다.

Jenkins를 구축해서 좋았던 또 한 가지 장점은 빌드 오류 시 대응하는 데 유리하다는 점이었다. GitLab에서 merge하면 Jenkins 빌드 스크립트가 실행이 되고 MatterMost로 결과를 알리도록 구축을 했기 때문에 만약 빌드 오류가 발생하면 자신이 방금 붙인 Merge Request에 포함된 커밋만 확인하면 되기 때문이다. 실제로도 빌드 실패한 경우가 종종 있었는데 대부분 금방 문제 위치를 파악하는 모습을 보였다. 그리고 아직 git에 익숙치 않아 커밋 내역을 직접 수정하기 부담스러운 경우도 있었는데, 오류가 났던 코드를 주석 처리한 추가 커밋으로 빌드 성공을 만들어 다른 팀원들의 개발에 방해를 주지 않는 방법을 택하기도 했다.

모니터링을 통한 파이프라인 스크립트 수정

빌드 오류가 발생한 경우가 종종 있었는데, 그 중에는 기능 오류가 아니라 Jenkins 파이프라인 스크립트로 인해 문제가 발생한 적도 있었다. Jenkins 파이프라인은 step(빌드 단계)으로 구분된다. 그래서 api 서버 배포 과정은 빌드(docker compose build api)와 배포(docker compose up -d api) 같이 두 개의 step으로 구성했고 이 단계들이 문제 없이 실행되면 MM으로 성공 알림을 보내도록 했다.

문제는 배포 단계 이후였다. docker compose up -d api 명령 실행이 성공하고 MM으로 성공 알림을 받았으니 서버가 잘 떠있는 줄 알았다. 그러나 Grafana 대시보드에서 CPU 사용이 주기적으로 피크를 찍는 모습을 보여 확인해보니 (1) 이 직전 Merge Request에서 Redis와 연결하는 코드가 추가되었는데 (2) Redis 서버를 못 찾아서 (3) Spring boot 서버 초기화 과정에서 실패했고 (4) api 컨테이너가 바로 종료된 뒤 (5) restart 옵션때문에 계속 다시 실행되는 일종의 좀비 컨테이너가 되어 있던 것이었다.

이 문제를 겪은 후 고민을 하다가 두 가지 안전 장치를 마련했다. 첫 번째는 Jenkins 스크립트에 api를 호출하는 단계를 추가했다. /health라는 빈 엔드포인트를 하나 만들고 curl -X GET http://apiserver/health 처럼 호출하는 step을 추가하여 실제로 서버가 정상 실행되고 {"health": "good"} 같은 응답을 받은 후에 MM으로 배포 성공 알림을 보내도록 했다. 두 번째는 Prometheus에서 cadvisor 메트릭을 활용하여 지난 5분동안 restart가 두 번 이상 된 컨테이너가 있으면 알림을 보내도록 룰을 추가했다. 이후에는 서버 실행 자체에 실패하는 경우를 못 잡아낸 적은 없었다.

빌드 자동화는 가능하면 꼭 하자

빌드 자동화는 이번에 처음 공부하고 구축해서 사용해봤는데, 그 장점을 정말 제대로 느낀 것 같다. 그래서 새로 시작하는 프로젝트에서도 Jenkins 구축을 우선할 것 같다. 한 번 구축한 경험도 있으니 이번엔 조금 더 쉬울 것 같다. 추가적으로 지난 번에는 develop 브랜치 하나만 보고 아무 기능이 추가될 때마다 fe, be 전체를 빌드하고 배포했는데, 이를 적절한 규칙에 따라 나누어 fe 기능 추가 때는 fe만, be 기능 추가 때는 be만 빌드 + 배포하고 단순 문서 수정일 때는 빌드하지 않는 식으로 나누어 보는 것도 해보려고 한다. 이 내용은 잘 준비해서 추후 포스팅도 해볼 계획이다.