들어가는 말
안녕하세요, jingluv입니다.
오늘은 SSAFY 11기 공통프로젝트를 하며 느낀 후기를 나누려 합니다.
다소 두서없고 영양가 없을 수 있지만, SSAFY를 준비하는 분들이 SSAFY 교육생이 2학기에 어떤 프로젝트를 수행하는지 알 수 있는 기회가 되길 바라겠습니다.
프로젝트 개요
제가 진행한 프로젝트 이름은 "STEACH"입니다. "Study & Teach"의 줄임말로, 가정형편이 어려운 취약계층에 속한 학생들이 실시간으로 무료 과외를 받을 수 있는 웹 애플리케이션을 개발하였습니다.
프로젝트 기획 배경
코로나 시대를 거치며, 사교육에 대한 열망이 커졌습니다. 사교육비의 증가는 가정 형편이 어려울수록 더 큰 부담으로 다가옵니다. 또한, 코로나가 본격적으로 유행하기 직전인 19년도와 23년도의 자원봉사자 수를 비교해 보면 약 2배나 차이나는 것을 볼 수 있습니다.
저희는 여기서 아이디어를 얻어 대학생에게는 봉사활동을 할 수 있는 플랫폼과 봉사시간 인증서를 제공하고, 취약계층 학생들에게는 취약계층 학생들만 사용할 수 있는 실시간 온라인 무료 과외 플랫폼 개발을 목표로 개발에 착수하였습니다.
프로젝트 기획 목적
네, 그렇습니다. 대학생들은 시간과 장소에 구애받지 않고 본인의 전공을 살려서 재능을 기부할 수 있고, 취약계층 학생들은 주변의 시선을 신경쓰지 않고 마음 편하게 무료로 다양한 분야의 강의를 수강할 수 있습니다.
저희는 실제로 배포하게 된다면 지방교육청 및 학교 등의 기관들과 연계하여 취약계층 학생들만 받을 수 있는 인증코드를 통해 저희 플랫폼을 이용할 수 있도록 하려 했습니다.
저희의 최종 목표는 취약계층 학생들의 교육 격차를 해소하고, 공정한 출발선까지 나아갈 수 있는 밑거름을 제공하는 것입니다.
시스템 구조도
저희는 Backend, Frontend, Infra 세 분류로 나누어 개발을 진행하였습니다.
Backend
Spring Boot 3
: API 서버, 이미지 서버.
Flask
: OpenCV, Dlib을 통한 안면 인식 + 졸음 감지 기능 서버.
Maria DB
: 메인 DB, 정량데이터(회원, 커리큘럼, 강의, 퀴즈 등) 관리.
MongoDB
: 서브 DB, 조회 성능이 중요한 통계데이터(레이더 차트, AI 진로 추천) 관리.
Redis
: 서브 DB, 조회 성능이 중요한 강의 추천 데이터(Cache 기반) 관리.
Frontend
Node.js
: React, TypeScript 기반의 SPA 서버.
React
: SPA 화면 개발을 위한 JS, TS 기반 라이브러리.
TypeScript
: SPA 화면 개발 언어(JS에 비해 오류 수정에 용이하여 사용).
Infra
EC2
: 아마존 클라우드 서버. SSAFY에서 제공 받았으며, 24시간 가동하여 애플리케이션 배포 가능.
Docker
: 각 서버를 Container로 실행하여, 일관된 환경에서 수행할 수 있고, 유지보수가 용이함.
Docker hub
: Dockerfile로 빌드한 이미지를 저장하여 관리 및 이미지의 tag를 이용한 버전 관리에 용이함.
Jenkins
: CI/CD의 자동화(파이프라인)를 강력하게 지원하며, 유지보수가 용이함.
Nginx
: Certbot으로 SSL 설정을 통한 HTTPS 연결, 트래픽 부하 방지(로드 밸런서).
역할 및 기여
저는 팀의 DB 전반과 Infra를 담당하였으며, 다음과 같은 역할들을 수행하였습니다.
1. DB 모델링 및 설계
앞서 서술하였듯이, 저희 팀은 3가지 DB를 사용하였습니다. 저는 그중, Maria DB의 모델링과 설계, MongoDB의 기본적인 모델링을 맡았습니다.
Maria DB의 경우 ERD 작성을 통해 논리적인 다이어그램을 그려 각 테이블에 어떤 데이터가 필요한지 정의하고, 테이블 간 관계를 설정하고, 관계 테이블을 정의했습니다. 이후 SQL문으로 DB를 설계하고, Python으로 애플리케이션 테스트에 필요한 dummy data들을 관리했습니다.
학생, 선생님, 커리큘럼, 강의, 통계 데이터 등 대부분 정형 데이터들은 RDBMS인 Maria DB로 관리했습니다. 다만, 저희 애플리케이션만의 '관심 분야 레이더 차트'와 'AI 진로 추천' 기능 제공에 필요한 통계 데이터들은 JOIN을 많이 요구하며 잦은 조회가 필요하므로 성능 상 이점을 위해 NoSQL인 MongoDB를 통해 관리했습니다.
MongoDB의 경우 모든 팀원들이 처음 사용했으므로, 기본적인 학습을 수행할 수 있도록 문서를 작성하였습니다. 이후, 통계 자료를 관리할 수 있게 기본적인 구조를 모델링하고 이후 작업은 다른 팀원에게 인계하였습니다.
2. CI /CD 파이프라인 구축
저희는 Backend, Frontend, AI 총 3가지로 나누어 개발을 하였습니다. 저는 각 파트들을 생각하여 Gitlab → Jenkins → Docker hub → Docker → Nginx → Web으로 이어질 수 있게 파이프라인을 설계하였습니다.
먼저, 각 파트들에 맞게 Dokerfile을 작성하여 애플리케이션이 자동으로 빌드되고 배포되어 실행될 수 있는 Docker image를 만들 수 있도록 하였습니다.
이후 Jenkins를 활용하여 Gitlab에서 Webhook을 연동하여 master branch에 push 혹은 merge가 감지되면 자동으로 빌드가 되게 하였습니다.
이 때, 각 파트에 해당하는 디렉토리에서 변경사항이 감지되면 해당 디렉토리만 빌드가 되게 하고, 각 빌드되는 Docker image는 commit hash를 tag로 가져 버전을 관리를 용이하게 하였습니다.
그리고 빌드된 디렉토리의 Docker image만 가져올 수 있도록 별도로 변수를 만들어 관리하였습니다(Docker-compose.yml의 environment에 변수를 받아오고 if문을 통해 배포 여부 체크).빌드된 이미지는 자동으로 EC2 내부의 Docker에 container로 실행될 수 있도록 docker-compose를 작성하고 Jenkins에 추가했습니다.
Nginx를 통해 SSL 인증서를 설정하고, https로 요청을 받는 업무는 다른 팀원이 수행하였으며, 저는 파이프라인 구축이 마무리 된 이후에 서버 관리를 하였습니다.
3. 문서 작성
저희 개발 진척도와 일정 관리를 제외한 대부분의 문서를 Notion으로 관리하였습니다. 저는 팀원 limlim(가명)과 함께 팀 문서의 기틀을 닦고, 프로젝트의 기능 명세서, API 명세서, Backend에서 같이 공부하면 좋을 내용들(NoSQL, Docker, MongoDB 등)에 대해 학습 가이드를 작성했습니다.
4. 기타
상술한 내용 외에도 잡다한 일들을 많이 했습니다. SSAFY의 경우 전공자와 비전공자(Python 반)의 실력 차이가 꽤 두드러지므로, 첫 프로젝트에서 대부분의 Backend는 전공자 출신 교육생들입니다.
저 역시도 Backend에 한 발 걸쳐 있었지만 DB 설계를 제외하곤 다른 업무들을 수행했습니다. 개인 업무 외에, 팀 내의 프리젠테이션 업무를 도맡아 아이디어 해커톤부터 중간 점검, 최종 프리젠테이션까지 모든 자료를 제작하고, 프리젠테이션 진행을 하였습니다. 남는 시간에는 Frontend의 디자인을 도왔습니다.
도전 과제
프로젝트를 진행하면서 정말 많은 어려움에 부딪혔습니다. 처음으로 배우는 지식들이 너무 많았고, 이를 실제 프로젝트에 녹여내야 했기에 많은 에러에 직면해야 했습니다. 그중, 대표적으로 직면한 어려움과, 실수들에 대해 소개하려합니다.
1. DB 모델링 및 설계
저희 팀은 개발 와중에서 DB를 수십 번은 더 갈아엎었습니다. 초기 기획에서 제대로 모든 기능을 설정하고, DB 모델링을 하지 않으니 중간에 계속 데이터 타입이 변하고, table과 column이 생성/삭제되고, 세부적인 부분들이 부족하여 DB를 삭제하고 생성하는 일을 반복하였습니다.
이 때문에 불필요하게 시간을 많이 낭비하게 됐습니다. 앞으로의 프로젝트들에서는 DB 모델링을 최대한 꼼꼼하게 하여 시간 낭비를 하지 않도록 하자는 결심을 하였습니다.
2. Jenkins 설치 및 Credentials
정말 많은 에러에 시달렸습니다. SSAFY에서는 Github이 아닌 Gitlab을 사용하도록 하는데, DVCS에서 기본적으로 제공하는 Auto CI/CD 기능을 막아놨습니다. 그래서 직접 여러 도구들을 사용하여 파이프라인을 설계해야 합니다.
SSAFY에서는 각 프로젝트마다 Jenkins 서버를 제공합니다. 중앙제어는 SSAFY 사무국에서 하여 credential 설정이나 plugin 설치 등 다양한 기능들을 사용하기 위해서는 중앙에 별도로 요청을 해야 하는 번거로움이 있습니다.
그래서 저는 처음에 EC2에 Docker를 설치하고, Docker 내에서 Jenkins를 container로 실행시키면서 그 안에서 파이프라인을 구축하려고 했습니다.
하지만 심각한 문제에 봉착한 것이, 이렇게 하려면 Docker 내에 Jenkins 내에 Docker를 또 설치해야 하는 번거로움이 있었습니다. 저는 두가지 이유로 이 방법을 포기했습니다.
첫째, EC2 서버 역시 SSAFY에서 제공받은 것이므로 저희에게 권한이 없는 부분이 있습니다. 그래서 Jenkins내에 Docker를 설치하기 위해서는 permission denied를 해결해야 하는데, 해결한 타 팀에 문의한 결과 시간이 많이 걸린다는 피드백을 받았습니다.
둘째, 어차피 저는 Jenkins와 Docker 모두 EC2 서버에서 실행할 것이기 때문에, docker-in-docker 사용 여부에 상관없이 storage 같은 자원은 똑같이 소모합니다. 따라서, 더 어려운 docker-in-docker 방법을 고수할 필요가 없어졌습니다.
Jenkins를 실행하고 처음 마주친 에러는 Credentials와 관련된 에러였습니다. Gitlab의 기능들을 처음 사용해보기도 하고, Jenkins 역시 처음이었기에 Webhook을 연결하는 것도 쉽지 않았습니다. 처음에는 Jenkins pipeline이 아닌 freestyle을 사용해 Item을 만들었기 때문에 더 고생했던 측면도 있는 것 같습니다.
저는 Gitlab 프로젝트의 Settings → Repository → Deploy tokens에서 token을 발급 받아 credentials를 설정하고, Jenkins에서 Gitlab과 pipeline을 연결하고 받은 Secret token을 Gitlab의 Webhook에 설정하여 해결하였습니다. 만약 Repository에 대한 권한이 모두 있다면 Personal token으로도 credentials 설정을 할 수 있을 것 같습니다.
또한, 저는 Docker hub에 image를 빌드해서 올려서 버전 관리를 하였기에 docker hub credentials도 별도로 수행해주었습니다. 처음에는 제 개인 계정 정보를 입력했는데, 너무 위험한 것 같아 변경하였습니다.
3. Docker Image Build
너무 많은 에러를 만났으므로(서버가 회수되어 찾을 수도 없다) 별도로 코드를 첨부하지는 않겠습니다.
Dockerfile을 처음 작성하다 보니, 각 프레임워크나 언어에서 요구하는 빌드, 배포 명령어와 어긋나는 코드를 작성하는 경우가 많았습니다. 이는 검색과 GPT를 통해 해결할 수 있었으나, properties 파일의 부재와 의존성 에러가 큰 문제였습니다. 의존성 문제는 이미 온라인 상에 수많은 문제에 직면한 개발자 분들이 해결한 소스가 많아 도움을 받을 수 있었고, properties는 yml로 대체하여 해결하였습니다.
gradle 또한 문제였는데, Spring이 어떻게 빌드되는지 모르기 때문에, 이 부분은 빌드되는 명령어만 따로 검색하여 찾아보고 하나하나 수행해보며 해결하였습니다.
React 또한 수많은 에러를 발생시켰습니다. 대부분의 에러는 npm library의 의존성 에러였습니다. 이는 직접 의존성을 작성하며 해결할 수 있었으나, host 설정이 굉장히 오래 발목을 잡았습니다. 결국, host를 localhost에서 0.0.0.0으로 변경하여 해결하였습니다(아직도 해결된 이유가 무엇인지 정확하게 모릅니다).
Flask 역시 다른 라이브러리들과의 호환성 때문에 특정 버전만 사용해야 해서 라이브러리 설치로 인한 에러가 많았습니다. 저는 버전을 많이 낮추더라고 무조건 실행될 수 있도록 변경해서 사용했습니다.
4. Docker Image Version
Jenkinsfile에 docker image가 빌드되어 올라갈 때, commit hash를 tag로 할 수 있도록 하여 잘 만들어지는 것을 확인하였음에도 불구하고, 실제로 컨테이너를 실행시키면 imgae만 다운하고, 실행은 'lts' tag를 가진 컨테이너가 실행되고 있는 것을 확인하였습니다.
이건 휴먼 에러가 원인이였는데, 이전에 'lts' 버전을 사용하도록 한 코드 일부가 남아있었던 것과, docker volume을 잘못 연결하고 있던 것이 원인이었습니다. 모두 제거하여 해결하였습니다.
5. Docker Compose Running
제일 많은 시간을 부여잡고 있었던 에러입니다. docker-compose.yml을 작성하고 난 뒤에 jenkinsfile을 통해 자동화를 하고 나니, 계속 port 번호 충돌로 인한 에러가 발생했습니다.
수동으로 직접 컨테이너를 삭제하고 실행하면 잘 실행되고, 실제로 에러 발생 이후 실행 중인 컨테이너와 port를 사용중인 프로세스가 없는 것을 확인했음에도 에러는 계속 발생했습니다.
이미 팀원들이 많은 프로그램을 설치하고 실행 중이었으므로, 서버를 초기화 할 수 없었습니다. 또한, 컨설턴트님 역시 이건 무언가 잘못됐다고 말씀하셨습니다. 그래서, 차선책으로 docker-compose에 해당하는 모든 컨테이너들을 멈추고, 삭제하고, 대기하고, 이후에 docker-compose를 실행할 수 있도록 jenkinsfile에 script 명령어를 작성하여 해결했습니다.
6. Jenkins Reverse Proxy Error
중간에 Spring 서버와 port 번호가 8080으로 충돌해서 8081로 바꿀 때 한 번, 우리 프로젝트의 도메인 steach.ssafy.io를 별도로 받으면서 한 번 jenkins의 url이 변경된 적이 있었습니다.
Manage 설정을 통해 url을 수정하고, Webhook의 url을 변경하는 등의 작업을 해 봤지만 경고창만 사라질 뿐 제대로 CI/CD가 실행되지 않아 결국 모두 밀어버리고 재설치하였습니다(진짜 제일 하기 싫었음).
성과 및 결과
개인적으로 프로젝트는 성공적으로 마무리 되진 않았다고 생각합니다. 부족했다고 생각한 부분들은 다음과 같습니다.
1. 보안 문제
개발 과정의 편의성 때문에 실제로 필요한 여러 기능을 생략한 채로 테스트를 진행했었는데, 프로젝트 종료 막바지까지 계속 개발을 하느라 이 기능들을 다시 넣지 못하고 마무리하고 말았습니다. 대표적으로 회원가입 시 거쳐야 하는 인증 절차를 생략하기도 하였고, url로 특정 페이지로 넘어갈 수 있었습니다.
2. 디자인 문제
디자인적으로 많이 미흡했습니다. 저희 팀에는 애초에 Frontend 개발자를 희망하던 사람이 없었기도 하고, UI/UX 측면으로 뛰어난 감각을 가진 사람도 없었기에 애플리케이션 디자인은 전체적으로 투박할 수 밖에 없었습니다.
이는 더 나아가 프리젠테이션 자료에서도 여실히 드러났습니다.
그래도 주요한 기능들은 모두 잘 작동했습니다. 선생님은 커리큘럼을 생성하고, 학생들은 수강 신청하고, 둘 모두 개인 강의실에서 생성한 강의와 등록한 강의를 관리할 수 있었습니다.
또한, 강의가 진행될 시간에 맞게 자동으로 강의실이 생성이 되고, 강의실 내에서 저희 팀이 요구한 퀴즈, 선생님과 학생 화면의 기능 차이 등이 잘 작동했습니다. 마지막으로, 저희 프로젝트만의 특장점 중 하나인 통계 자료 및 진로 추천 기능은 원하는 만큼의 퍼포먼스를 보여주었습니다.
배운 점
팀적인 측면에서 배운 점이 있다면 "기획은 최대한 꼼꼼히 해야한다"는 점입니다. 저희 팀은 기능 명세서, 화면 명세서, API 명세서 등 다양한 명세서들을 통해 저희가 필요로 하는 기능과 구성, API 등을 잘 기록했다고 생각했지만 단 며칠만으로는 어림도 없다는 점을 알게 됐습니다.
기획 단계를 꼼꼼히 하지 않으니 계속해서 기능이나 화면이 추가되거나 삭제되고, API 통신에서 전달해야 할 매개변수들이 계속 변경되어 DB를 여러번 갈아엎어야 했습니다(ERD를 3주나 잡고 있었음).
기술적인 측면으론 NoSQL을 습득할 수 있었던 점과 CI/CD 파이프라인 설계의 기초를 다질 수 있었던 점입니다. 비록, 프로젝트를 마무리한 지금까지도 잘 한다고 말하지는 못 합니다.
하지만, 이와 관련된 문서들을 정리하면서 습득할 수 있던 지식들과 프로젝트에 적용하면서 만났던 다양한 에러들을 해결한 경험은 다음 프로젝트에서도 같은 업무를 더욱 원할하게 수행할 수 있도록 도와줄 것이라 생각합니다.
개선 사항
사실 고칠 점을 하나하나 꼽자면 프로젝트를 다시 시작하는게 맞다고 생각합니다만... 제가 맡은 업무를 다시 하게 된다면 개선할 수 있는 사항들에 대해 말해보고자 합니다.
1. NoSQL의 적극적인 사용
MongoDB를 좀 더 적극적으로 사용하고 싶습니다. 특히, 계속해서 데이터를 조회해야 하고 데이터를 구하기 위해서 많은 JOIN이 필요한 통계 데이터를 아예 MongoDB에서 전담하도록 설계하는 편이 어떨까 싶습니다.
2. Master-Slave 구조 서버
Master-Slave 형태의 DB 서버를 설계하고 싶습니다. 이번에는 주어진 DB 서버(특히, MongoDB의 경우 사용할 수 있는 Collection 개수가 제한되어 있었습니다)의 한계로 시험하지는 못했으나 다음 번에는 좀 더 기능 측면에 맞춰서 DB를 설계해보고 싶습니다.
3. 꼼꼼한 DB 설계
좀 더 꼼꼼한 Query 작성을 하고 싶습니다. 이번에 Query를 작성할 때는 마음이 조급해 테이블을 생성하고, 제약 조건을 설정하는 측면에 집중하였는데 다음 번에는 좀 더 세세하게 데이터의 입출력 형태에 대해서도 많이 고민해서 작성하고 싶습니다.
4. Blue-Green 배포 전략 사용
이번에는 Docker container로 각 1개씩의 서버만 돌렸는데, 다음 번에는 각 2개씩 서버를 돌려서 업데이트나 버그 같은 여러 상황에서도 서버가 다운되지 않고 계속 실행될 수 있도록 하고 싶습니다.
5. Nginx 설정
Nginx 공부가 좀 더 필요하다고 절실히 느낍니다. 이번에 SSL 인증서를 받고, 도메인 연결을 하는 부분은 다른 팀원이 수행하였습니다. 만약, 프로젝트를 다시 하게 된다면 이 부분까지 제가 마무리 지어보고 싶습니다.
6. CI/CD 파이프라인 설계 숙달 / 고급화
실제 DVCS에서 제공하는 Auto CI/CD 기능도 공부해서 Jenkins와 Docker를 이용하는 것과 어떤 차이점이 있는지 비교해보면서 성능을 측정해보고 싶습니다.
7. 기획 목표 실현
마지막으로 기획 단계에서 발급한 인증코드를 공공기관과 연계하는 부분에 대해서도 생각하고 있었는데, 이 부분은 현실적인 문제로 넘기게 되었습니다. 다음 번에는 직접 공공기관에 문의하여 실제로 프로젝트에 녹여내보고 싶습니다.
감사 인사
이번 프로젝트는 좋은 팀원들 덕분에 늘 웃으며 진행할 수 있었습니다. 물론 저희 팀이 수상을 목적으로 하지 않고 배우고 프로젝트에 실제로 여러 기술들을 적용시키는 것을 목적으로 하였습니다만, 사람들이 좋지 않았다면 불가능겠죠.
먼저 우리 든든한 금쪽이 팀장님과 limlim에게 너무 고맙습니다. Backend 기능을 2주만에 다 해놓고, 최대한 아는 지식들을 팀원들과 나누려고 할 때의 감동이란... 특히, 금쪽이 팀장님은 여러 새로운 기술들을 배워서 프로젝트에 적용하고, 테스트를 열심히 해주어서 감사합니다. limlim은 API 통신과 Frontend 업무도 도와주고 코로나에 걸렸음에도 프로젝트 진행에 차질이 생기지 않게 힘 써줘서 너무 고생했다고 말하고 싶습니다.
우리 Frontend의 송이형과 흐규도 너무 고생했습니다. Frontend 개발자를 희망하지 않는데도 불구하고 앞장서서 궂은 업무를 도맡아 해주었고, 정규 업무 시간 외에도 수많은 시간 외 근무를 하며 프로젝트의 완성을 위해 힘 써줘서 너무 감사합니다. 덕분에 최대한 완성도 있는 프로젝트를 마무리 할 수 있었다고 생각합니다.
마지막으로, 프로젝트의 핵심 기능을 혼자 완성해버린 원영러버 lsc에게도 너무 고맙습니다. 생전 처음 써보는 WebRTC와 Web Socket을 프로젝트에 잘 접목시켜준 덕분에 프로젝트를 완성할 수 있었습니다. 또한, Nginx 설정을 잘 해준 덕분에 우리가 도메인을 문제 없이 사용할 수 있었습니다. 긴 시간 혼자 이 업무들 부여잡고 고생해줘서 너무 고맙습니다.
다시 한 번 우리 팀 "2NE1 멤버들"에게 고맙다는 말씀 드립니다.
처음엔 제가 했던 개발 프로젝트 중 가장 많은 인원들이 모여 진행했으며, 가장 규모도 큰 프로젝트였기 때문에 걱정이 많았습니다.
그래도 좋은 팀원들 덕분에 새로운 기술들을 시도해 볼 용기를 낼 수 있었고, 제가 맡은 업무들을 수행해 낼 수 있었습니다. "팀 프로젝트란 무엇인가", "분업이란 무엇인가"에 대해 배울 수도 있었습니다.
다음 프로젝트에서는 제가 팀장을 맡게 되는데, 이번 프로젝트에서 배웠던 점들을 토대로 더 나아진 모습으로 프로젝트를 수행할 수 있도록 노력하겠습니다.
만약, 프로젝트 결과물에 대한 전반적인 내용에 대해 이미지로 보고 싶으신 분들은 제 팀원의 블로그 링크를 아래에 첨부하겠습니다.
https://velog.io/@joohr1234/Steach-프로젝트-후기
두서없는 긴 글 읽어주셔서 감사합니다.