앞으로 최종 프로젝트를 진행하면서 겪었던 일들을 기록하고자 한다
백엔드 개발자가 나를 포함하여 두 명이다 보니, 생각보다 담당해야 할 업무가 많았다. 3~4주차에 다양한 기술을 사용하면서, 각 기술을 선택한 이유와 이 과정에서 마주친 문제들과 팀원과의 문제들에 대해서도 얘기해보고자 한다.
개발을 진행하다 보니, 예외처리한 에러에 대해서는 어디에서 에러가 발생했는지 위치를 파악할 수 있지만, 예상치 못한 에러의 발생지는 찾기 어려웠다.
그래서 모든 요청을 받았을 때, 어떤 응답을 보내줬고, 에러가 발생했다면 모든 에러를 기록하는 로깅할 필요성을 느끼게 되었다.
NestJS의 기본 로거도 유용하지만, 다양한 로깅 레벨이 존재하고 커스텀 설정을 제공하여 더 세밀한 로깅을 가능하게 하는 Winston 로거를 선택했다.
공식 문서는 각 컨트롤러에 Winson 인스턴스를 주입하는 방식을 제안하지만, 이는 컨트롤러에서 담당하는 역할이 과도하게 많아진다고 판단했고, 따라서 중앙에서 모든 요청과 응답, 에러를 가로채서 로깅하면 좋을 것 같다고 판단했다.
처음으로 고민했던 방식이다. NestJS에서는 인터셉터를 통해 메소드 실행 전이나 후에 로깅하는 작업을 추가할 수 있다. 그러나 이러한 로깅 작업은 요청이 컨트롤러의 라우터 핸들러에 도달하기 전에는 작동하지 않기 때문에, 잘못된 요청이나 악의적인 공격에 대해서 로깅할 수 없다라고 판단했다.
다음으로 고민한 것은 미들웨어와 예외 필터를 사용하는 것이었다.
위 그림은 NestJS의 LifeCycle인데, Middleware를 사용한다면, 모든 요청과 응답에 대해서 로깅할 수 있고, 예외 필터를 통해 에러를 로깅할 수 있다고 판단하여 미들웨어와 예외 필터를 사용하기로 결정했다.
이제 위와 같이 Winston 로거를 사용해서 모든 요청, 응답 그리고 에러를 로깅할 수 있게 되었는데, 이 로그들을 수동으로 검토하는 것은 가독성도 떨어지고, 매우 비효율적이라고 생각한다.
결국, 위와 같은 로그들을 모니터링하기 위한 도구가 필요할텐데, 어떤 도구를 사용해야할지 고민해봐야 할 것 같다.
지금까지 API를 테스트하기 위해 Postman을 사용하며, 요청과 응답의 구조를 작성해놓은 API문서에서 직접 확인해야 했다.
그러나 위 과정은 번거롭고 시간이 많이 소요되는 작업이었다.
그래서 Swagger라는 도구를 도입하게 되었는데, Swagger는 Rest API를 편리하게 문서화 해줌으로써, 사용자가 요청 값과 응답 값의 예시를 확인해 볼 수 있고, 편리하게 API를 호출해보고 테스트 할 수 있게 해주는 도구다.
Swagger를 적용함으로써, 프론트엔드 개발자와의 협업을 크게 개선했다. API의 요청과 응답 형식을 실시간으로 확인하고, 직접 테스트 할 수 있는 환경을 제공함으로써 개발 효율성을 높일 수 있었다.
초기에는 EC2에 Nginx 웹 서버를 설치하고 Minikube를 설치한 후, 컨테이너 안에 Docker 이미지를 수동으로 배포하였다.
그러나, 현재 우리의 프로젝트가 성장함에 따라, 변경 사항을 신속하게 적용하는 것이 점점 어려워졌다.
그래서 배포를 진행함에 있어서 발생할 수 있는 버그 및 코드 오류를 예방하는 동시에 수정된 production 코드를 수동으로 배포했하던 과정을 자동화 함으로써, 사용자 피드백을 더 자주 효과적으로 통합할 수 있게 하는 CI/CD 파이프라인을 구축하게 되었다.
CI/CD 파이프라인을 구축하기 위한 툴은 여러가지가 존재하지만, 우리는 YAML 파일을 통해 커뮤니티에서 제공하는 액션들을 사용하여 손쉽게 CI/CD 파이프라인을 구축할 수 있는 Github Actions를 사용하기로 했다.
우리 팀은 브랜치 전략을 Git-Flow전략을 사용하기로 했다.
그래서 변경된 코드를 production이 배포되어 있는 AWS EC2에 적용하기 위해서 main 브랜치에 PR을 보냈을 때만 트리거가 발동하도록 설정하였다.
테스트와 빌드해보기 전에, node_module 폴더를 캐시하는 작업을 추가하였다.
node_module 폴더를 캐시함으로써, 반복적인 의존성 설치 시간을 줄이고, 빌드 속도가 향상되도록 설정하였다.
그 후, Jest를 사용하여 테스트를 진행하고, 테스트를 통과하면 Build 과정에서 오류가 발생하지는 않는지 확인해본다.
위의 CI 과정을 통과했다면, Dockerfile을 통해 docker 이미지를 만든 후, ECR(Elastic Container Registry)에 Push 되도록 설정하였다.
ECR에 Push하기 위해서는 자격 증명 과정이 필요하기 때문에, accessKeyId와 secretAccessKey를 통해 로그인 과정을 거친 후, docker 이미지가 Push 되도록 설정하였다.
Github Actions는 최근에 나온 CI/CD 툴이다. 그래서 생각보다 참고 자료가 적은 편이었는데, 우리는 EC2에 Minikube Cluster를 구성하여 그 안에 배포를 진행해야 했는데, 참고할만한 자료가 없는 상황이었다.
그러나 우리는 Github Actions가 쿠버네티스 환경을 조작할 수 있도록 kubectl이 설치되어 있다는 것을 알게 되었고, 원격으로 EC2의 Minikube Cluster에 접속할 수 있도록 환경 설정을 구성하였다.
쿠버네티스는 Deployment를 사용하여 컨테이너화된 애플리케이션을 관리하고 자동으로 업데이트하는데, Deployment를 활용하여 컨테이너의 환경을 구성하기 위해서는 보안적인 문제가 발생할 수 있는 환경 변수들을 입력해줘야 했다.
그래서 Github Secrets를 활용하여 필요한 환경 변수들을 등록해줬고, 그 환경 변수들을 활용하여 동적으로 Deployment YAML 파일을 생성해주기 위해 envsubst를 사용하였다.
동적으로 생성된 Deployment에서 Docker 이미지를 ECR에서 가져와 컨테이너에게 Push해주기 위해서는 ECR에서 해당 docker 이미지를 Pull해올 필요가 있었는데, 이미지를 Pull해오기 위해서는 인증 토큰이 필요했다.
인증 토큰은 12시간마다 갱신해야하기 때문에, 배포를 진행할때마다 기존에 인증 토큰이 있다면 갱신되도록 설정하였다.
그 후, 이미지를 가져와 쿠버네티스의 컨테이너에 배포를 성공적으로 할 수 있게 되었다.
이번 프로젝트에서는 우리에게 주어진 리소스가 EC2, ECR 뿐이었기 때문에, 위처럼 구성하였지만, Minikube는 개발 및 테스트 용도로 많이 사용한다고 한다. 쿠버네티스 클러스터의 전체 기능을 간소화된 형태로 제공하기 때문에, 프로덕션 환경에는 적합하지 않을 수 있다.
따라서 다음에는 EKS(Elastic Kubernetes Service)를 사용하여 배포를 진행해 보고 싶고, 또한 Github Actions이 아닌 다양한 플러그인이 존재하는 Jenkins를 사용하여 CI/CD를 구축해보고 싶다.
우리팀은 메인 퀘스트를 수정하기 위해서는 메인 퀘스트와 퀘스트에 종속된 사이드 퀘스트를 함께 수정되도록 기획하였다.
예상치 못한 오류로 인해 일부만 수정되는 상황을 예방하고자 메인 퀘스트와 종속되어 있는 사이드 퀘스트를 트랜잭션을 활용하여 하나의 작업 단위로 묶기로 하였는데, 개발하시는 팀원 분이 메인 퀘스트 수정과 사이드 퀘스트 수정을 분리하여 개발하는 상황이 발생하였다.
위와 같은 변동 사항이 존재할 때, 회의 시간에 말씀해주셨다면 위와 같은 문제가 발생하지 않았겠지만, 위의 문제에 있어서는 나 뿐만 아니라 다른 팀원들에게도 문제가 있었다.
개발 시간이 부족하다는 이유로 팀원분이 올린 PR을 제대로 리뷰하지 못했던 것도 문제였기 때문이다.
그래서 이번 일을 계기로 위와 같은 문제가 발생할 수 있음을 인지하게 되었고, 좀 더 PR을 꼼꼼히 체크하고 리뷰를 진행해야겠다고 다짐하게 되었다.
이번 주도 쉽지 않았다. 할 일은 산더미 같이 쌓여 있고, 이를 해결하기 위해 최대한 빠르게 작업을 진행했다.
그러다보니 팀원과의 소통 문제가 발생했지만, 오히려 좋은 교훈이 되었다.
팀원과의 충분히 소통하지 않았을 때 발생할 수 있는 문제들을 명확히 파악할 수 있었고, 코드 리뷰를 소홀히 할 경우 문제가 더 악화될 수 있다는 것을 깨닫게 되었다.
이 경험을 바탕으로 비슷한 실수를 반복하지 않기 위해 더욱 노력해야겠다.