[번역]빠르게 배워보는 Node.js를 이용한 서버리스(Serverless)

*이 글은 Adnan RahićA crash course on Serverless with Node.js를 번역한 글입니다. 모든 저작권과 권리는 Adnan에게 있습니다. 도움이 되셨다면 Adnan의 원글에 clap 한번씩 부탁드립니다 :)
*This article is a translated version of
Adnan Rahić’s article: A crash course on Serverless with Node.js. All rights goes back to him.
*최대한 이해하기 쉽도록 곳곳에 의역이 들어간 점 양해 부탁드립니다.

오늘은 서버리스를 이용하면서 느끼게되는 장점과 단점에 대해서 이야기해보려 한다. 그 과정에서 이 주제에 관련된 주 키워드를 정의하고, 실제 서버리스 함수를 작성해보고, 환경을 재현해본 후, 퍼포먼스 모니터링까지 해보도록 할 것이다. 이 예제를 따라하려면 AWS 계정이 있어야 하므로, 없는 분들은 프리 티어 등을 이용해서 시도해보면 좋겠다.

그래서 서버리스란..?

서버리스는 어떻게 갑자기 대세가 된 것일까? 서버를 사용하는 것이 더 이상 달갑지 않은 일이 되어버린걸까? 나는 서버를 사랑하는데, 그냥 쓰던데로 쓰면 안되나? 커맨드라인을 이용해서 뭐든 할 수 있고, 서버가 얼마나 좋은가? 왜 굳이 다른것을 이용해야 하는거지?
나도 처음엔 이런 생각을 하며 서버리스의 등장에 경악했다. 하지만 한 발짝 떨어져 생각해보면, 기존의 서버들이 완벽한것은 아니다. 갯수가 늘어나면 관리하기가 여간 까다롭지 않고, 스케일링도 우아하게 해내기 쉽지 않다. 그 외에도 여러가지 문제가 있을 수 있다.

조금만 다르게 생각해보자. 서버 대신 함수를 사용하는 것으로. 서버 없이 코드만 이용하는 것이다. 꽤 괜찮지 않은가?
커맨드라인을 이용한 지루한 작업은 운영팀에게 맡기고, 개발자들은 함수만 관리한다. 이런 설계를 뭐라고 하면 좋을까? 함수만 이용하기? 작은 함수들? 소형 서비스들?

Functions as a Service (FaaS)

이와 같은 훌륭한 설계를 Functions as a Service 라고 부른다*(역: 함수로 이루어진 서비스). 서버리스 컴퓨팅에 기반한 컨셉으로, 하나하나의 코드 혹은 함수를 자유롭게 배포할 수 있게 해준다.
코드가 실행되면 결과값을 반환하고, 프로세스는 종료된다. 간단하지 않은가? REST API를 작성해본 적이 있는 개발자라면, 금새 익숙해질 것이다.
보통은 한곳에 몰아두었던 서비스와 endpoint 들을 분리해서 작은 부분들, 즉 마이크로서비스로 나눈다. 이렇게 해서 서버로부터 개발자를 완전히 자유로이 하고, 실행된함수를 기준으로 비용을 지불하게끔 하는 것이다. 즉, 스케일링이 쉬운 서비스라는 뜻이다.

하지만 모든 일이 다 잘 풀릴 수는 없는 법. FaaS도 문제가 없지 않다.
예외 처리는 어떻게 해야하는가? 물리적 서버가 없으니 모니터링을 하기도 쉽지 않다. 서비스의 규모가 커질수록 시스템의 전반적인 상태를 파악하기가 어려워진다.

차근차근 시작하기

서버리스 애플리케이션을 작성하는 방법을 배우기 위해서 먼저 필요한 도구와 서비스들을 파악할 필요가 있다.

AWS Lambda
AWS Lambda is a compute service that lets you run code without provisioning or managing servers.
(역: AWS 람다는 서버를 준비하거나 관리할 필요 없이 코드를 실행하게 해주는 컴퓨팅 서비스입니다)
- AWS Documaentation

람다는 클라우드상에서 코드를 실행하게 해주는 이벤트기반 시스템이다. 서버에 대한 걱정 없이 코드 작성에만 집중하면 된다. 자동으로 스케일링되고, 코드가 실행되는, 즉 처리에 사용되는 시간 만큼 과금이 된다. 자동으로 스케일링이 된다는 것은 매우 중요하다! EC2 인스턴스가 당신의 서비스의 유저들을 수용하기에 너무 작거나 하지는 않은지 더이상 걱정할 필요가 없다는 이야기다.

AWS API Gateway

람다는 API Gateway를 같이 사용하므로써 완성된다. 모든 람다 함수는 이벤트가 발생해야 실행된다. API Gateway는 REST Endpoint를 제공하고, 이 endpoint들이 함수를 실행시킨다.
평범한 Express 앱이 있다고 가정해보자. 보통 특정 경로(route)에 대해 app.get() 메서드를 생성할것이다.

app.get(‘/’, function(req, res, next) { /* execute some code */ });

유저가 '/' 경로에 접근하면 이벤트가 콜백 함수를 실행시킨다. 즉, Gateway가 경로이고, 람다는 콜백 함수라고 보면 된다.

서버리스 프레임워크

이걸 모두 직접 관리하기는 어려운 일이다. 처음 시도했을 때, 뭐가 어떻게 작동하는지 파악하는데만 거의 하루가 걸렸다. 여기에는 이유가 있는데 문서가 너무 복잡하고, 초보자들에게 어렵게 작성되어있다.
여기서 Serverless가 등장한다.(*역: Serverless는 이 문서에서 소개되는 프레임워크의 이름이기도 합니다.)

Serverless is your toolkit for deploying and operating serverless architectures. Focus on your application, not your infrastructure.
(*역: Serverless는 서버리스 환경을 배포하고 운영하기 위한 툴입니다. 인프라가 아닌 애플리케이션에 집중하세요.)
 
 -Serverless.com

Serverless 프레임워크는 서버리스 애플리케이션을 생성하고 배포하기 위해 필요한 모든 도구들을 쉽게 관리할 수 있는 패키지 형태로 묶어서 제공한다. 당신이 AWS 콘솔에서 해야할 지루하고 어려운 과정들- 함수를 생성하고 이벤트와 연결시키는 등 -을 간소화 시켜주는 아주 좋은 프레임워크이다. 한가지 안좋은 점은 로컬 환경을 구성하기 쉽지 않아 함수들을 테스트하고 싶을 때 마다 AWS에 코드를 push해야 한다는 것이다.

Serverless는 아주 다양한 상황에서 이용할 수 있다. 여러분이유저의 접속량이 빠르게 변화하는 프로덕션 상태의 애플리케이션을 가지고 있다면, 관리도 스케일링도 쉬운 서버리스 설계로 바꿔볼 가치가 있다고 생각한다. 
마지막으로, 당신이 리눅스 쉘 사용에 미숙하다면, 그리고 데브옵스가 좀 낯설다면, Serverless 를 시도하지 않을 이유가 없다.

새롭게 접근하기

서버리스 설계는 꽤 어렵다. 세팅하는데만 해도 상당한 정신적 에너지를 소비한다. 로컬 환경 세팅은 또 어떤가? 그건 또 그거대로 보통 일이 아니다.

이러한 서버리스를 적용하기 위해서는 접근법을 바꿔야한다. 시스템 전체를 모니터링 하는것이 불가능하다는 것을 인정해야 하는 것이다. 하지만 인간은 적응하게 되어있다. Serverless 프레임워크가 등장하는 순간이다.

그럼 이제 직접 간단한 서버리스 함수를 만들어보자.

Serverless를 세팅하는 방법은 간단하다. npm을 이용해서 설치하고 나서 AWS 계정을 연동하면 된다. AWS 콘솔이 어렵더라도 걱정할 필요 없다. 같이 차근차근 진행해보도록 하자.

  1. Serverless를 global로 설치한다.
     터미널을 열고 아래 명령을 실행해보자.
$ npm install -g serverless

Serverless 프레임워크가 글로벌 설치되었다. 이제 Serverless 커맨드들을 언제든지 사용할 수 있다.
Note: 리눅스 환경이라면 sudo로 실행해야 할 수도 있다.

2. AWS 콘솔에서 IAM 유저를 생성한다.
 AWS 콘솔을 열고 왼쪽 상단에 있는 서비스 드롭다운을 클릭해보자. 많은 서비스들이 나올 것이다. 검색창에 IAM을 치고, 클릭한다.

본인 계정의 IAM 페이지로 이동할것이다. 새로운 유저를 추가하자.

맘에 드는 멋진 이름을 골라서 입력하고, programmatic access를 체크한 후 다음 단계로 이동한다.

이제 권한들을 부여할 수 있다. Serverless가 우리의 AWS 계정에 다양한 asset들을 추가/삭제할 수 있도록 할것이므로, AdministratorAccess를 체크한다.

다음 스텝으로 진행하면, 유저가 생성된 것을 확인할 수 있을 것이다. 딱 이때만 유저 Access Key ID와 Secret Access Key를 알 수 있으므로, 지금 적어두거나 .csv 파일로 다운받아서 안전한 곳에 보관하도록 하자. 이것은 데모이지만 여러분에게 보안의 심각성을 일깨워주기 위해 나도 모자이크 처리를 해 보았다.

이제 Serverless 설정에 위의 키들을 입력할 수 있게 되었다.

3. IAM 키를 Serveless 설정에 추가하기
잘 따라왔다면 훌륭하다! 이제 저장한 키를 이용해서 Serverless를 여러분의 AWS 계정에 접속할 수 있게 할 수 있다. 터미널로 돌아와서 다음과 같이 입력해보자.

$ serverless config credentials --provider aws --key xxxxxxxxxxxxxx --secret xxxxxxxxxxxxxx

엔터를 치면 Serverless가 어떤 계정에 연결할지 설정이 완료된다. 어떻게 되는지 실제로 한번 보도록 하자.

4. 첫 서비스 만들기
Serverless 애플리케이션을 넣을 새로운 디렉토리를 생성하자. 터미널에서 해당 경로로 이동하고 나면, 서비스를 생성할 준비가 되었다. ‘서비스’가 무엇인지 잘 모르겠다면, 조금 다르기는 하지만 프로젝트와 비슷하다고 볼 수 있겠다. 여기서 AWS 람다 함수, 함수 실행 이벤트, 필요한 AWS 인프라 리소스들을 전부 serverless.yml 파일에 선언하도록 한다.

이제 터미널에서 다음 명령어를 실행한다.

$ serverless create --template aws-nodejs --path my-service

이 커맨드는 새로운 서비스를 생성한다. 짠! 하지만 이제부터가 진짜다. 
우선 함수의 런타임을 선택해야한다. 이것을 템플릿이라고 부른다. aws-node옵션을 통해 우리가 원하는 대로 런타임을 Node.js로 설정하게 된다. 이 예제에서는 my-service가 서비스의 이름이다.

5. 텍스트에디터를 이용해서 서비스 디렉토리 탐색하기
본인이 좋아하는 텍스트에디터에서 my-service 디렉토리를 열어보자. 3개의 파일이 있을것이다. serverless.yml은 모든 설정들을 포함한다. 이 파일에서 전반적인(general) 설정과 각각의 함수의 설정을 지정한다. serverless.yml은 주석이 추가되어 있을 뿐, 기본적으로 다음과 같이 생겼다.

# serverless.yml 
service: my-service
provider:   
name: aws
runtime: nodejs6.10
functions:
hello:
handler: handler.hello

functions속성은 서비스 내의 모든 함수들을 나열하고 있다. 여기서는 hello가 현재 hander.js 파일에 있는 유일한 함수이다. handler속성은 여러분이 실행하고자 하는 함수의 코드가 어느 파일 혹은 모듈에 있는지를 가리킨다. handler.js라는 이름으로 기본 파일이 생성된다. 매우 편리한 기능이다.

handler.js 를 열어보면 handler 모듈과 hello라는 이름의 함수를 볼 수 있을것이다. 이 함수는 세개의 파라미터를 받는다. event 파라미터는 함수에 넘겨진 이벤트 데이터이다. context 파라미터는 함수의 컨텍스트, 러닝타임, 상태(state) 등의 중요한 정보를 담고 있다. 마지막으로 callback은 어떤 데이터를 반환할 것인지를 뜻한다. 이 예제에서는 response가 콜백 함수의 두번째 파라미터로 지정되어있다. 첫번째는 언제나 에러이며, 에러가 없으면 null이 넘어온다.

// handler.js
module.exports.hello = (event, context, callback) => {
const response = { statusCode: 200, body: 'Go Serverless!' };
callback(null, response);
};

여기까지는 좋으나, 아직 함수를 실행하지 못한다. 아직 연동된 이벤트가 없기 때문이다. 이것을 해결하기 위해서 serverless.yml 파일로 다시 가서 events: 로 시작하는 부분의 주석을 풀어보자.

# serverless.yml 
service: my-service
provider:   
name: aws
runtime: nodejs6.10
functions:
hello:
handler: handler.hello
events: # uncomment these lines
- http:
path: hello/get
method: get

코드의 인덴트(들여쓰기)가 망가지지 않도록 주의해야 한다. eventshandler의 바로 밑에 위치하여야 한다. 이제 함수를 AWS에 배포할 수 있게 되었다.

6. AWS에 배포하기
배포과정은 상당히 직관적이다. 서비스 디렉토리에서 다음의 커맨드를 실행하자.

$ serverless deploy -v

터미널이 여러개의 메세지를 띄우는 것을 볼 수 있을것이다. -v 옵션 때문이다. verbose 로그는 언제나 멋지다.

하지만 가장 중요한것은 endpoint를 확인하는것이다. Serverless가 자동으로 API Gateway 엔드포인트를 생성해서 람다 함수에 연동했음을 알 수 있다. 훌륭하지 않은가? 브라우저에 그 엔드포인트를 입력하면 Go Serverless! 라는 문구를 볼 수 있을 것이다.

Note: 함수를 커맨드라인으로 테스트하고 싶다면 다음과 같이 치면 된다

$ serverless invoke -f hello -l

이렇게 하면 전체 response 객체와 더불어 람다 함수의 현 상태(state)에 대한 정보-실행 시간, 메모리 사용량 등-도 같이 반환한다.

귀찮은 과정 줄이기

함수를 테스트하고 싶을 때 마다 매번 AWS에 다시 배포해야 한다니, 여간 성가신 일이 아닐 수 없다. 이것을 로컬 환경에 구축해놓을 수는 없을까?

조금 주제에서 벗어나기는 하지만, Serverless Offline이 있다! 이제 모든 코드를 AWS에 푸시하기 전에 모든 코드를 테스트해볼 수 있다. 이걸로 혈압이 오를 일도 많이 줄어든다.

Serverless Offline을 서비스에 추가하는 것도 간단하다. npm 모듈을 하나 추가하고 serverless.yml 에 코드 두 줄만 추가하면 된다.

바로 보여주도록 하겠다.

1.서비스 디렉토리에서 npm을 초기화
 my-service 디렉토리로 가서 터미널을 열고 다음과 같이 실행하자.

$ npm init

2. Serverless Offline 설치
 npm이 초기화되었다면, 바로 설치를 진행하면 된다.

$ npm install serverless-offline --save-dev

— -save-dev옵션은 페키지를 개발 dependency로서 패키지를 저장하게끔 한다.

더 진행하기 전에, 터미널에게 새로운 커맨드가 사용 가능하다는 것을 알려줘야 한다. serverless.yml 파일에서 두 줄을 추가한다.

# serverless.yml 
service: my-service
provider:   
name: aws
runtime: nodejs6.10
functions:
hello:
handler: handler.hello
events:
- http:
path: hello/get
method: get
# adding these two lines
plugins:
- serverless-offline

3. 로컬에서 실행
모든것이 잘 설치되어있는지 확인하기 위해서 다음의 명령어를 쳐보자.

$ serverless

offline이라는 옵션을 확인할 수 있으면 잘 설치 된것이다.
Note: Serverless Offline에 대해서 더 많은 정보를 알고 싶다면, serverless offline --help을 터미널에서 실행해보면 된다.

자 이제 준비가 되었으니 로컬에서 람다와 API Gateway를 재현해보자.

$ serverless offline start

터미널에 모든 경로들이 나열될 것이다. 람다가 로컬호스트에서 돌아가기 시작한 것이다. 기본 포트는 3000번이다. 브라우저를 열고 바로 확인해보자.
http://localhost:3000/hello/get 엔드포인트를 주소창에 입력하면 위에서 봤던 결과를 똑같이 볼 수 있을 것이다.

아주 좋다. 이제 AWS에 매번 푸시해가면서 제대로 작동하는지 확인하지 않아도 된다. 로컬에서 테스트하고, 확신이 들 때에만 푸시하면 되는것이다.

예외 상황에 대비하기

기존의 애플리케이션 환경에서는 무언가 잘못되면 바로 알 수 있다. 어디서 발생했는지도 스택 트레이스를 확인하면 바로 알 수 있다. 모니터링이 꽤 간단하고 직관적이었던 것이다.
하지만 서버리스 환경에서는 어떻게 하면 좋을까? AWS CloutWatch의 로그는 끔찍하다. 간단한 애플리케이션에서도 왜 함수가 안 돌아가는지 알아내는데에 엄청나게오래걸렸다. 더 큰 애플리케이션이라면 상상하기도 싫다.

내가 찾은 대안은 Dashbird이다. 무료이고, 상당히 괜찮아보이는 서비스이다. 미리 신용카드를 등록하게 하지도 않기 때문에 “한번 해볼까?” 싶은 마음이 들게 해준다.

아주 좋은 튜토리얼을 제공하기 때문에 세팅하고 실행하는데에 5분정도면 충분하다.

Dashbird를 Serverless와 연동하고 나면 드디어 내 앱에서 무슨일이 일어나고 있는지 모니터링 할 수있다. 든든한 경비가 생기는 것이다.

에러들은 하이라이트로 표시되고, 전반적인 헬스 체크도 가능하다. 정말 안심이 된다. 비용도 트래킹해주니, 과금 폭탄도 걱정하지 않아도 된다. 실시간 모니터링도 포함되어있으니, 더할 나위 없다.

이런 툴이야말로 규모가 큰 애플리케이션 관리도 손쉽게 만들어주는게 아닌가 싶다.

마치며

즐거운 여정이었다. 여러분들은 방금 기존의 웹개발 방법에서 서버리스 환경으로 바꾸는 과정을 경험했다. 간단한 툴 몇개로 견고하고 유연한 애플리케이션을 만들 모든 준비가 끝났다.

이제 남은 것은 우리 자신의 마음가짐 뿐이다. 함수들은 서버와는 다르다는 것을 인지하는 것이 곧 터닝포인트가 될 것이다. 하지만 올바른 방향으로 가고 있다. ServerlessDashbird같은 툴들은 이 과정에서 생기는 어려움을 많이 해결시켜준다. 나 역시 이러한 툴들로 미지에 가까웠던 서버리스 설계에 성큼 다가설 수 있었다.

이 글을 읽는 여러분 모두 한번씩 이 도구들을 계속해서 이용해보기를 강권한다. 지금 개발하고 있는 것들에 직접 적용해보라. 새로 생긴 지원군들이 든든하게 느껴질 것이다. 덤으로 개발 스트레스도 확 줄여준다.

내가 쓴 코드들을 보고싶다면, 이 리파지토리에서 확인하면 된다. 혹은 내 최신 글들을 보고싶다면, 다음의 링크를 확인하라.

https://medium.com/@adnanrahic/latest

여러분 모두 내가 이 글을 쓰면서 느꼈던 즐거움을 읽으면서 느꼈기를 바란다. 혹시 이 튜토리얼이 다른 누군가에게 도움이 될것이라고 생각한다면, 꼭 공유해주기 바란다. 이 글이 좋았다면 clap도 한번 눌러서 다른 사람들도 볼 수 있게 해주면 좋겠다!