서버리스를 좋아하는 이유

7

현업에서 EC2, Elastic Beanstalk, GKE 위에서 돌아가는 백엔드를 운영해 본 후로, 개인 사이드 프로젝트는 거의 모든 신규 백엔드를 서버리스(AWS Lambda)로 시작하고 있다. 본업이 백엔드 개발이면서 인프라까지 함께 보는 환경에서, 서버리스가 제공하는 트레이드오프가 압도적으로 좋다고 느꼈기 때문이다. 그동안 써오면서 느낀 점들을 공유한다.

현업에서 본 운영의 무게

이전 회사에서는 훌륭한 인프라팀과 함께 했기 때문에 백엔드 개발자가 인프라까지 직접 손댈 일이 거의 없었다. 다만 내가 맡았던 블록체인 노드 서버들은 EKS가 아니라 EC2 위에 있어서, vim과 ubuntu와 씨름하면서 직접 설정을 만지는 일이 꽤 있었다. 그 이후 합류한 회사에서는 환경이 또 달랐다. 인프라를 전담할 사람이 없어서 백엔드 개발자가 같이 떠안아야 했고, 전임자가 GCE(AWS로 치면 EC2) 위에 Docker로 구축해둔 서비스와 Elastic Beanstalk 위의 서비스들을 상속받았다.

1) logrotate 하나 빠트리면 서버가 죽는다. 그 시절, 서버 IP 대역대를 통째로 옮기는 이관 작업이 있었다. 15대 넘는 EC2를 한 달 꼬박 써서 옮겼는데, 겉으로 안 보이던 설정들이 산더미였다: nginx, pm2, 방화벽 규칙, 블록체인 노드별 peer whitelist, 폐쇄망 환경 때문에 일일이 날라야 했던 환경 파일까지. 그 과정에서 logrotate 설정이 빠진 서버 하나가 조용히 디스크가 차올라서 결국 장애로 터졌다. 설정 한 줄 빠졌다고 서버가 죽는다는 걸 그때 처음 체감했다.

2) 컨테이너 이미지가 쌓여서 배포가 터진다. 어느 날 배포가 실패해서 원인을 파보니, 배포 스크립트가 이전 이미지를 삭제할 때 이름을 fuzzy 매칭으로 잡고 있었고 그 과정에서 일부가 누락되면서 옛날 이미지들이 조용히 누적돼 디스크를 먹어치우고 있었다. 당장은 GitHub Actions 스크립트를 손보는 것으로 막았지만, 이런 비핵심 업무가 제품 개발 시간을 야금야금 갉아먹는 구조라는 걸 그때 눈으로 확인했다.

3) TLS 인증서 갱신과 새벽 알람. Let's Encrypt 90일 만료. certbot cron이 한 번이라도 못 돌면 사이트가 죽는다. 복구하려면 DNS 검증/재발급/재로드 다 거쳐서 30분이 우습게 깨지고, 거기에 죽은 걸 감지할 헬스체크와 Slack 알람까지 직접 붙여야 한다.

4) 매니지드라고 손 놓을 수 있는 건 아니다. 매니지드 서비스는 내가 통제 못 하는 영역이 늘어난다는 의미이기도 하다. Beanstalk platform1에 묶인 Node 버전이 한참 옛날에 멈춰 있어서, 새 의존성 쓰려고 platform 업그레이드 검토하다가 호환성 이슈로 꽤 헤맸다. 추상화 한 단계 위에서 망가지면 SSH로 들어가 고칠 수도 없다.

5) K8s(GKE)는 또 다른 종류의 무게. GKE도 굴려봤는데, 추상화가 매력적인 만큼 hidden cost가 많다. 예를 들어 GKE 워크로드를 Cloud SQL Postgres에 붙이려면 노드 outbound IP가 매번 바뀌어 whitelist가 무용지물이라, Cloud NAT으로 outbound를 고정하거나 Cloud SQL Auth Proxy를 sidecar로 띄워야 한다. K8s 학습 곡선 위에 GCP 추상화가 한 겹 더 쌓이는 구조다.

이런 잡일들이 제품 개발 시간을 꾸준히 먹었다. 하나씩 따로 보면 30분에서 한 시간이지만, 합쳐 보면 일주일에 반나절은 인프라에 사라지고 있었다.

그래서 사이드 프로젝트는 처음부터 Lambda 로

위 경험들이 누적되면서 결론은 분명해졌다. 1인 환경에서 사이드 프로젝트를 시작할 때 위와 같은 운영의 무게를 다시 짊어질 수는 없다. 회사라면 적어도 인프라 동료와 분담이라도 하지만, 개인 프로젝트는 새벽에 깨서 디스크 정리할 사람도 나뿐이다.

그래서 사이드 프로젝트들은 처음부터 Lambda로 시작했다. 옮긴 순간 위에서 적은 운영 잡일들이 거의 다 사라졌다.

  • 디스크가 따로 없으니 로그 폭발이 없다 (CloudWatch 로 자동 송출)
  • Docker 정리도 없다 (이미지/컨테이너 개념 없음)
  • TLS 인증서 직접 관리 안 함 (API Gateway / ACM 이 알아서)
  • 인스턴스가 죽었나 살았나 신경 쓸 일이 없다 (서버 자체가 없음)
여기에 더해 비용 구조가 인디 프로젝트에 너무 잘 맞는다. AWS Lambda 의 영구 무료 티어는 월 100만 요청 + 월 400,000 GB-초의 컴퓨팅 시간이다. 무분별한 폴링2 같은 게 끼어 있지 않다면 검증 단계의 인디 프로젝트는 이 한도에 한참 못 미친다.

DB 도 무료 티어를 활용하면 사실상 0원에 가깝게 운영 가능하다.

  • MongoDB Atlas M0 (512MB, 무료)
  • Neon Postgres (무료 티어, 충분한 컴퓨팅 시간 + 0.5GB)
결과적으로 새 프로젝트를 시작할 때 백엔드 + DB 가 거의 무료로 떠 있는 상태가 된다. 검증되지 않은 아이디어를 공짜로 띄울 수 있다는 건 인디 환경에서 굉장히 큰 의미다. 이게 신규 프로젝트마다 같은 스택을 템플릿처럼 재사용하는 이유 중 하나다.

물론 단점도 있다

서버리스가 만능은 아니다. 내가 겪었던 진짜 단점들도 적어둔다.

1) 벤더 락인. Lambda로 만든 코드를 그대로 다른 환경으로 옮기는 건 쉽지 않다. AWS 의존성이 깊어진다. 다만 인디 프로젝트 입장에선 "지금 시작하느냐 못 하느냐"가 더 큰 변수라 락인이 결정적인 단점은 아니라고 본다.

2) Node 버전 deprecation은 여전히 신경 써야 한다. EC2처럼 OS 패치까지 챙길 일은 없지만, AWS가 Lambda 런타임의 Node 버전을 EOL 시키면 내가 업그레이드해야 한다. Beanstalk 시절보단 훨씬 깔끔하지만 0은 아니다.

3) Serverless Framework 생태계가 LLM 이전 시대에는 미성숙했다. 서버리스를 처음 쓰기 시작했을 때(LLM 코딩 어시스턴트가 보편화되기 전) 가장 힘들었던 게 이 부분이다. 명령어, 플러그인, deploy 시 ENV 주입 등 stackoverflow 검색해 가면서 삽질했다.

4) NestJS + Lambda 조합은 사짜스러운 부분이 있었다. NestJS를 Lambda에서 돌리려면 어댑터가 필요한데, 원래 AWS Labs가 만들었던 aws-serverless-express가 2022년경 아카이브됐다. 이후 사실상 표준이 된 게 커뮤니티 fork인 @codegenie/serverless-express다. 즉 NestJS를 Lambda에 올리는 "공식 경로"가 사라지고 커뮤니티 fork에 의존하는 상태가 된 것이다. 동작은 잘 했지만 production 환경에서 핵심 의존성이 한 사람의 fork라는 게 마음 한구석에 계속 걸렸다.

5) Cold start가 분명히 있다. 체감상 가장 큰 단점이다. 트래픽이 적어 instance가 자주 죽는 시점에 첫 요청이 들어오면 응답이 1~3초씩 늦어진다. 결제처럼 가끔 호출되는 critical path에서 이게 누적되면 UX에 직접 영향을 준다. 다만 이건 프레임워크 선택으로 상당 부분 풀리는 문제다. 실측 데이터는 Part 2에서 따로 다룰 예정이다.

그래서 지금의 default 는

지금 인디 프로젝트의 백엔드 default 를 정리하면 이렇다.

  • 호스팅: AWS Lambda + API Gateway (Serverless Framework로 IaC)
  • 프레임워크: 가벼운 것으로 (Hono 같은). NestJS 같은 무거운 프레임워크는 cold start 페널티가 누적된다.
  • DB: MongoDB Atlas M0 또는 Neon Postgres 무료 티어
  • CI/CD: GitHub Actions + OIDC (액세스 키 안 들고 다님)
이 조합이면 새 프로젝트를 0원에 가깝게 띄우면서, 인프라 잡일은 거의 없고, cold start도 인디 트래픽이라면 무시 가능한 수준에 들어온다.

마치며

트래픽 큰 production이나 장기 실행 작업 같은 경우는 다른 선택이 더 맞을 수 있다.

다만 검증 안 된 아이디어를 1인이 빠르게 띄우고, 운영 잡일은 최소화하면서, 비용은 0에 가깝게 가져가야 하는 인디 환경에서는, 서버리스 + 가벼운 프레임워크가 거의 default가 되어야 한다고 본다. 적어도 내가 지금까지 사이드 프로젝트들을 운영하면서 내린 결론은 그렇다.

  1. Beanstalk의 'platform'은 OS 이미지, 언어 런타임, 웹 서버가 하나로 묶인 스택을 가리키는 고유 개념. 그 안의 Node 버전만 따로 업그레이드할 수 없다.
  2. 분당 1회 호출은 월 43,800회로 무료 티어 안에 들어온다. 초당 1회면 월 2.6M회로 티어를 넘는다.