최근 친구와 시작한 사이드 프로젝트가 있습니다. 클럽하우스 프로필을 웹에서 조회 할 수 있도록 하는 프로젝트입니다.
개인적으로 이 프로젝트를 통해 처음 써본 기술이나 개념들이 많아서, 수동으로 배포를 마친 지금, 조금 뿌듯해서 어떤 기술을 썼는지, 왜 썼는지 적어보려고 합니다.
프로젝트 소개
프로젝트의 주소는 https://inssa.club 입니다. 다시 한번 설명하자면, 클럽하우스 프로필을 웹에서 조회 할 수 있도록 하는 프로젝트 입니다.
그래서 당신. 무슨 약을 하셨길래 22시간을 코딩한거야..?
당연히 22시간 연속으로 개발한 것은 아니고, wakatime에 찍힌 1일 코딩 시간 중 최장 시간을 기록한 날과 2위로 길게 코딩 한 날을 합쳐서 22시간이라고 했습니다.
먼저 인증부터 하겠습니다.
10시간 28분 + 1시간 23분 + 10시간 45분 = 22 시간 36분
혼자 신나서 인스타 비계에 막 적었어요.
카페인의 도움도 물론 받았는데.. 그것보다는 더 좋은 약을 빨았습니다. 흥미로운 기술들을 다룬것입니다.
하술 할 기술 스택들은, 다 나름의 이유가 있어서 채택 되었어요. 그 이유들도 모두 설명해드리겠습니다.
기술 스택
- MSA
- golang + gin
- postgresql + mongodb
- aws lambda → vercel serverless → aws ec2 + docker-swarm
- submodule
- nginx
Why MSA?
먼저, MSA 부터 살펴볼까요?
MSA는 Micro Service Architecture의 줄임말로, 아주 작은 서비스들로 쪼개진 아키텍쳐를 뜻해요.
하나의 서비스 안에서 모든 기능을 처리 하지 않고, 그 서비스들을 아주 작은 단위로 쪼갠 설계를 MSA, Micro Service Architecture 라고 합니다. 수평적 확장이 용이하다는 장점이 있어요.
그런데 왜 MSA 였을까요?
두괄식으로 결론부터 말하자면, 개발자 커뮤니티들에서 MSA 같은 키워드를 접하며 한번 비슷한 기술을 시도해보고 싶다는 생각이 들었습니다.
이제는 많은 서비스들이 MSA로 자신들의 구조를 바꾸어 가고 있습니다. 물론 그렇지 않은 서비스들도 많지만, MSA가 수평적 확장(scale out)이 용이해지고, 좀 더 유연하게 확장 가능한 구조라는 점에는 이견이 없을 것 같습니다.
즉, MSA를 적용하지 않은것 자체가 경우에 따라서는 일종의 기술 부채로 (펄럭펄럭 아니에요) 작용 할 수 도 있다는 것이지요. 아 물론, 어떤 기술이 그렇듯 무조건 MSA가 좋다거나, 모놀리틱이 좋은것은 아닙니다. 이와 관련한 글도 준비중이에요!
물론 당장 지금 진행하는 프로젝트가 많은 트래픽을 유발 할 것이라고는 생각하지 않았으니 위의 내용과는 사실 관계가 없고, 개인적인 선호도 때문이 었다고 할 수 있습니다.
그래서 inssa.club은 추후 오픈 할 서비스의 대기 신청을 받는 waitlist 서비스와, 클럽하우스 프로필을 조회하는 clubhouse-profile 서비스로 이루어져있어요.
두 서비스가 엮이는 경우는 없고, 독립된 구조로 설계 되어있어요. 위에서 보이는것처럼 여러 상호작용이 있는 복잡한 서비스라기보단 독립된 기능을 담당하는 내용을 분리했다고 볼 수 있죠!
Why golang + gin?
세상에는 수 많은 프로그래밍 언어가 있습니다. 그 중에는 매우 높은 생산성을 제공하는 언어도 많죠. Python, Javascript 같은 친구들이요.
저도 저 두 언어 참 좋아하는데, 그래도 이번 프로젝트에는 golang을 사용했습니다. 그런데 왜 golang일까요..?
- 개인적인 선호도
- 뛰어난 성능
저는 C언어를 참 좋아하는 사람이어서, golang을 사용하면 즐겁게 코딩을 할 수 있다는 장점이 있고,
또 golang 은 컴파일 언어이기 때문에 다른 고(go 아니고 높을 고..) 생산성을 표방하는 언어보다 조금 생산성은 떨어지더라도 성능이 뛰어나다는 장점이 있어요.
돈 없는 학생 개발자여서 프리티어 인스턴스를 사용해야 하는 만큼, 뛰어난 성능을 얻기 위해 golang을 택했습니다.
gin을 택한 이유는 그냥 개인적인 선호 였어요. 마이크로 서비스를 만들기에 적합한 프레임워크라고 생각했고, 또 성능이 뛰어나기도 하구요.
Why PostgreSQL + MongoDB?
상술했듯, inssa.club은 두개의 서비스로 운영중이에요.
- waitlist (PostgreSQL)
- clubhouse-profile (MongoDB)
waitlist 서비스에서 DB의 역할은 등록된 waitlist를 저장하는 역할을 하고,
clubhouse-profile 서비스에서 DB의 역할은 일종의 캐시(💶 아니에요) 에요. 프로필을 매번 클럽하우스에서 조회 하는 대신, 몇시간 단위로 캐싱을 해두는거죠.
waitlist 에서 PostgreSQL을 사용한 이유는 나중에 혹여 데이터를 모아 뭔가 작업 할 일이 생길 수 있다고 생각해서 사용했어요. (SQL을 사용해야 나중에 관계 데이터 분석에 용이하니까요)
clubhouse-profile에서는 그럼 왜 mongoDB를 사용했을까요?
크게 두가지 이유가 있어요.
- MongoDB 의 Document 기반 저장 방식으로, clubhouse 에서 조회해오는 프로필의 스키마가 변경되더라도 유연하게 대응 할 수 있도록 했어요.
- 또 MongoDB는 전통적인 SQL 들에 비해 insert와 select 속도가 좀 더 빠르다고 해요. 저장 단계에서 스키마를 검증하는 내용이 없기 때문이라고 합니다.
사실, 프리티어 인스턴스 하나에서 돌리는 주제에 2번은 크게 작용하지 않았고, 1번이 크게 작용했습니다. 코딩하기에 편한것도 있었고요. 그냥 데이터를 받아와서, userid랑 username 정도만 따로 스키마를 만들어놓고, 나머지 내용들은 생 object로 저장해요.
덕분에 클럽하우스 프로필에 맞게 일일히 스키마를 설계하고 만들지 않아도 되게 되었어요 👏
Why EC2 + DockerSwarm?
WAS와 DB를 돌리기 위해 AWS EC2 와 DockerSwarm을 사용하고 있습니다.
aws야.. 나도 서버리스가 있다.. 1 (lambda)
사실, 초기의 기획단계에서는 aws-lambda 를 사용하려고 했습니다. serverless framework를 활용해서, 쉽게 배포하려고 했고, 프리티어로 받은 인스턴스는 RDS처럼 사용하려고 했죠.
그런데 문제가 생겼습니다.
- serverless framework는 ap-northeast-2 리전을 지원하지 않는다.
- EC2와 lambda를 연결하려면 VPC안에 구성해야한다.
- serverless framework와 vpc를 조합해 사용하는것에 미숙하다.
- VPC에 구성된 lambda는 인터넷에 접근 할 수 없다.
- 만약 인터넷 접근을 원한다면 NAT나 NAT Instance를 별도로 설정해 주어야 한다.
- 그만큼 비용이 추가로 든다
- 만약 인터넷 접근을 원한다면 NAT나 NAT Instance를 별도로 설정해 주어야 한다.
사실, 일본리전에서 서비스해서 1번은 문제가 안됐어요. 그런데 몇시간을 삽질하다 2, 3, 4번의 문제가 연속적으로 발견돼서 지치기도 하고, 프리티어를 사용중인데 NAT 관련 설정을 하면 비용 문제도 생기니 aws-lambda는 다음번에 사용하는것으로 결정 했습니다.
버셀아.. 내가 깡패가 되야겠냐? (vercel serverless functions)
다음으로 찾은것은 vercel serverless functions 였습니다. 같이 프로젝트를 진행하는 친구의 추천으로 택했어요. 문제는, vercel serverless functions 의 golang 지원 현황이 현재 alpha 라는 것이에요.
차라리 이걸 알았으면 프로젝트를 ts로 진행했을텐데 하는 아쉬움은 있었는데, 포기했습니다. 왜냐하면
- 아무리 사이드 프로젝트여도, alpha 단계에 있다는건 불안정하다는 뜻이다
- api 디렉토리 안에 있는 파일명대로 url 라우트를 매치 시키는데 (ex: api/date.go → api/date) gin과 융합해서 사용 할 좋은 방법을 찾지 못했다
사실 1만 아니어도 2를 위한 노력을 더 해봤을텐데(golang 내장 http 모듈을 사용해서 gin을 연동시키는건 어렵지 않으니까요), 아직 golang과 vercel을 같이 쓰는건 엄청 마이너한 것 같아서 나중에 생길 문제를 대비해 그냥 사용하지 않는것으로 결정했습니다.
그렇게 잃어버린 순정 서버리스
그렇게 돌고 돌아, 다시 ec2로 돌아왔어요. DB와 두개의 WAS가 한 인스턴스 안에서 돌고있는 셈이죠.
처음에는 docker-compose 로 배포를 진행하려 했어요. 그런데 CI/CD를 위한 내용을 검색하다가, docker-compose는 production 단계에서 사용하면 안된다고 하더라구요. 그래서 같이 추천 받은 내용이 docker-swarm, kubernetes 였습니다.
DockerSwarm vs Kubernetes
참 많은 분들이 고민하는 주제인데, 저에게 선택지는 하나밖에 없었어요.
AWS 프리티어 인스턴스로는 Kubernetes 의 최소사양을 만족하지 못하거든요
사실 국내의 스타트업들에서는 Kubernetes 를 선호하는것 같고, 저도 그래서 kubernetes를 적용해보고 싶었어요.
그런데, 다음과 같은 문제들 때문에..
- 최소 사양 문제
- 단일 노드 구동
이 두 요소는 돈을 쓰지 않는 한 극복 방법이 없어서 결국 DockerSwarm 이 선정 되었습니다. 👏👏
Why Submodule?
모노레포로 프로젝트를 진행해보고 싶었어요. 모노레포는 여러가지의 프로젝트를 하나의 레포에서 진행하는걸 말해요.
처음에 모노레포를 택한 이유는 docker-compose 와 함께 사용하기로 결정하면서 docker-compose.yml 을 다루기 위해 만든거였는데, 사실 지금은 잘 활용하고 있지 못하는거 같아요.
이 부분은 아직 더 공부가 필요 할 것 같아요. 어떻게 해야 잘 사용 할 수 있는지 아직 잘 모르겠어요.
Why Nginx?
DockerSwarm을 사용하기로 결정하면서, traefik 같은 컨테이너 기반 아키텍쳐에 용이한 웹서버를 사용하려 했어요.
기존에도 배포 할 때 nginx는 호스트 환경에 구성했었고, DockerSwarm 에서 여러 replica 를 만들어서 돌린다고 하더라도, 내장 로드밸런서가 있었기 때문에 로드 밸런싱은 DockerSwarm 에게 위임하는 구조를 만들었어요.
그렇다면 이번에는 왜? 왜 Nginx였을까요?
일단 퍼포먼스 이슈가 컸어요. 낮은 성능으로 서버를 돌리는 만큼 조금 조금의 퍼포먼스가 아쉬웠고, 기존의 방식에서 크게 불편함을 느낀 부분이 없었기 때문이죠. 성능은 Nginx가 짱이기도 하고요.
그래서, 들어오는 요청의 첫 마중은 Nginx가 맞이하게 되었어요. 🤩
아직 한 발 남았다.
이번 프로젝트에서 새로이 경험해본 내용들이 참 많아요. DockerSwarm, Submodule, Conventional Commit 까지. 정체되지 않고 계속 계속 지식을 삼키는 기분이라 프로젝트를 하면서 기분이 참 좋았어요.
그럼에도 아쉬운, 개선될 부분들이 참 많습니다.
일단, 보시면 아셨겠지만 개발 단계보다는 배포 단계에서 삽질을 많이 했어요.
그래서 일단은 배포를 수동으로 진행했는데, 그게 다음과 같은 프로세스 였어요.
- git pull
- git submodule update --recursive
- cd *waitlist
- docker build -t waitlist .
- cd ..
- cd *clubhouse
- docker build -t clubhouse .
- cd ..
- docker stack rm inssa
- docker stack deploy -c docker-stack.yml inssa
엄청 복잡하죠 😢 그래서 일단은 셸 스크립트를 작성해두어서 문제가 되진 않는데, 바람직한 구조는 아니에요.
무엇이 문제일까요? 정말 많은데..
- 너무 장황해요. 배포 과정 중에 실행되어야 할 내용들이 너무 많아요.
- build가 서버에서 직접 되는 구조에요.
- 무중단 배포가 되는 구조가 아니에요.
- 9번 때문에 그런데, build를 진행하면서 버전을 명시하지 않았기 때문에 docker services가 업데이트를 해야 되는지 확인 할 수 없어서, 서비스를 내리고 새로 만들어야해요.
- 자동화 배포가 되지 않아요.
- 코드를 작성하고, 커밋을 진행하고, git push를 하고, 서버에 접속하고, 배포 스크립트를 실행 시켜야해요.
- 놀라운건, 이게 그나마 간소화된 절차라는거죠. 홀리 몰리...
- 서버 모니터링이 되지 않아요.
- docker volume 이 작동하는 과정을 정확히 몰라요.
- 외부 볼륨에 bind_mount 시키는 형태로 만들었는데, 이상하게 뭔가 새로 만들어진 파일이 없네요. 이 부분은 공부가 더 필요해요.
그렇다면 어떻게 개선 되어야 할까요?
- push 이후에 자동으로 서버에 변경사항이 무중단으로 반영되어야 합니다.
- jenkins 같은 도구도 있지만, 이 부분은 github actions를 사용하도록 할 예정입니다.
- 이미지 빌드, 업로드 과정을 github actions 에서 할당해주는 컴퓨터에서 진행하고,
- self-hosted runner 를 서버에 설치해서, 이미지를 다운로드 받고 배포하는 과정을 자동화 할 예정입니다.
- 서버 상태를 모니터링 할 수 있어야 합니다.
- 보통 filebeat 같은것들을 사용하곤 합니다. 그런데 인스턴스 하나로 돌리는 주제에 filebeat, kafka, elastic search까지 다 깔기엔 무리가 클 것 같습니다.
- 이건 나중에 프로젝트가 확장되고 나서 사용하려 합니다.
- 도커 볼륨을 좀 제대로 이해해서, DB 컨테이너들이 외부 볼륨을 사용하도록 해야해요
- 추후 구조가 확장된다면, vpc도 제대로 셋업 할 수 있어야 해요.
- 최종적으로 traefik을 사용 하는 구조로 바뀌어야 합니다.
- 지금은 서버 호스트의 내용을 건드렸는데, 나중에 노드가 더 많이 생겨서 확장하는 구조가 된다면, nginx 에 추가적인 플러그인을 설치하여 관리를 해야 할 것 같습니다.
- 아무래도, 로드밸런서가 마스터 노드에 고정되는건 바람직한 구조는 아닌것같습니다.
- 지금은 서버 호스트의 내용을 건드렸는데, 나중에 노드가 더 많이 생겨서 확장하는 구조가 된다면, nginx 에 추가적인 플러그인을 설치하여 관리를 해야 할 것 같습니다.
- 캐싱의 용도에 더 적합한 DB를 사용해야 해요.
- memcached, redis 같은 InMemory 기반의 DB를 사용하는 편이, 속도의 측면이나 용도의 측면에서 훨씬 용이하겠죠.
- 자원의 제약으로 이 역시 추후 확장되어야 진행 할 수 있어요.
무엇을 알고, 무엇을 모르는지에 대한 능력이 참 중요하다고 생각해요. 당장 해결 할 수 있는 문제는 1, 3 이니 빠른 시일내로 해결해야겠어요 😊
이상 긴 글 읽어주셔서 감사합니다.
'Computer Science' 카테고리의 다른 글
Go 프로젝트 Docker 이미지 크기 99.2% 줄이기 (부제: 이미지 크기 12921% 떡상 시키기) (0) | 2021.04.08 |
---|---|
이리치이고 저리치이고. 고난의 CI/CD 구축기 (0) | 2021.03.26 |
Docker Stack의 env-file 설정시 주의 사항 (0) | 2021.03.22 |
사과를 쓰다 펭귄한테 싸다구 맞은 사연 (맥에서 리눅스로 파일 업로드 시 주의할 점) (0) | 2021.03.04 |
Redis는 뭐고.. In Memory 솔루션은 뭐야? (0) | 2021.02.25 |