Apex로 좀 더 편리한 람다 개발환경 만들기

웹사이트의 성능 측정을 위한 도구 중 구글의 PageSpeed 라는 툴이 있다. 웹사이트 주소를 입력하면 자바스크립트, CSS 파일 등 정적파일이 몇 개고 총 용량이 얼마인지 분석해 준다. 게다가 개선 방법에 대한 가이드라인도 제시해 주는 웹사이트 성능 측정 도구다.

사이트 개선 작업 후 개선 효과를 확인하기 위해 툴을 돌리고 있는데 문제는 사이트 내용이나 네트웍 환경(?)에 따라 분석결과가 조금씩 다르다는 것이다. 단편적인 결과보다는 여러 결과의 추이를 확인하는 것이 더 낫다는 생각이 들었다.

분석 결과의 추이를 확인하기 위해서는 주기적으로 툴을 돌려야 하는데 AWS 람다 + 클라우드 와치 조합으로 해결하면 될것 같다. 마침 이 분석툴은 API 도 제공하고 있다.

간단히 로컬에서 노드로 개발한 뒤 압축하여 파일을 업로드 했다. 몇 가지 써드파티 라이브러리도 사용했기 때문에 node_modules` 폴더도 통재로 압축했다. 업로드 몇 번만 하면 되겠지라는 생각으로 "코딩 -> 압축 -> 업로드"를 작업을 수작업으로 반복했다. 저번에도 경험한 것이지만 간단한 기능이라도 쉽게 끝나지 않았다.

잠시 손을 놓고.. 자동화, 자동화에 대해서 생각했다. 작년 세미나에서 봤던 Apex ! 이것을 사용해야겠다.

Apex 설치

Apex 커맨드라인 툴을 설치한다.

curl https://raw.githubusercontent.com/apex/apex/master/install.sh | sh

실행에 앞서 Apex는 두 가지 AWS 권한이 필요하다 .

  • AWSLambdaFullAccess: 람다 함수 생성시 사용
  • IAMFullaccess: 생성할 람다 함수의 IAM 역할(role) 생성시 사용

따라서 내 로컬 환경에서 사용하는 IAM 유저에 위 권한들을 IAM 대시보드에서 추가해야 한다. 내 계정에는 모든것이 가능한 AdministratorAccess 권한이 있기때문에 별도로 설정할 필요는 없었다. 여기까지만 웹 대쉬 보드를 사용하고 이후에는 터미널로만 진행할 수 있다.

프로젝트 생성

hello_world 폴더를 만들어 이동한 뒤 Apex 명령어로 람다 프로젝트를 생성한다.

apex --profile myProfile init
             _    ____  _______  __
            / \  |  _ \| ____\ \/ /
           / _ \ | |_) |  _|  \  /
          / ___ \|  __/| |___ /  \
         /_/   \_\_|   |_____/_/\_\

   Enter the name of your project. It should be machine-friendly, as this
  is used to prefix your functions in Lambda.

    Project name: hello_world

  Enter an optional description of your project.

    Project description: hello world proejct

  [+] creating IAM hello_world_lambda_function role
  [+] creating IAM hello_world_lambda_logs policy
  [+] attaching policy to lambda_function role.
  [+] creating ./project.json
  [+] creating ./functions

  Setup complete, deploy those functions!

    $ apex deploy

프로젝트 정보를 입력하고 나면 람다 함수를 위한 IAM 역할이 만들어지고 새 폴더에는 project.json 파일과 functions 폴더가 생성된다.

  • project.json: 람다 함수 환경 설정을 위한 파일. AWS 대쉬보드에서 설정할수 있는 정보를 이 파일에 기술한다.
  • functions: 함수별로 하위 폴더가 생성된다. 기본으로 hello 폴더가 생성된다.

람다 함수 배포

곧 바로 람다 함수를 배포해보자.

apex deploy
 • creating function         env= function=hello
 • created alias current     env= function=hello version=1
 • function created          env= function=hello name=hello_world_hello version=1

매우 빠르게 배포되었다. hello 라는 이름의 람다 함수가 배포 되었고 버전도 1로 생성되었다. 여기서는 다루지 않지만 Apex는 람다 함수의 배포버전을 관리하기 때문에 이전 배포 버전으로 롤백도 가능하다.

테스트

그럼 테스트는 어떻게 할까? 대쉬보드로 가지 않고 콘솔에서 테스트할 수 있는 점이 apex의 장점이다. 다음 명령어로 테스트해보자.

apex invoke hello
{"hello":"world"}

헬로월드 제이슨 문자열이 응답되었다. 헬로 함수가 어떻게 생겼길래 이런 응답을 주는 건지 확인해보면 이렇다.

console.log("starting function")
exports.handle = function (e, ctx, cb) {
  console.log("processing event: %j", e)
  cb(null, { hello: "world" })
}

마지막 콜백함수를 실행하면서 두번째 파라매터로 전달한 제이슨 객체가 함수 반환값이 되는 것이다.

로그

코드에는 두 개의 로그가 찍혀 있는데 이건 어떻게 확인해야 할까? 개발할때 가장 자주 사용하는 것이 로그 보는 것이고 람다는 이런 로그를 웹 대쉬보드에서 봐야하는 불편함이 있다. Apex를 사용하면 친숙한 터미널 창에서 로그를 확인할 수 있다.

apex logs
/aws/lambda/hello_world_hello 2017-03-05T14:43:43.769Z	undefined	starting function
...
2170da85-01b2-11e7-97ef-0badce391b68	processing event: {}
...
2170da85-01b2-11e7-97ef-0badce391b68	Duration: 21.47 ms	Billed Duration: 100 ms 	Memory Size: 128 MB	Max Memory Used: 9 MB

"starting function"과 "processing event: {}" 문자열이 코드에서 console.log() 함수로 찍은 로그다.

매개변수 전달

람다 함수는 첫번째 매개변수로 호출측에서 보낸 데이터를 수신한다. 이 코드에서는 processing event 문자열에 매개변수 값을 로깅하도록 되어 있다. 그럼 apex을 이용해서 람다 함수에 매개변수를 전달해 보자.

echo '{"name":"6pack"}' | apex invoke hello
{"hello":"world"}

에코와 파이프라이닝을 통해 전달한 6pack 이름이 로그에 찍혀 나온다.

apex invoke
/aws/lambda/hello_world_hello 2017-03-05T14:51:47.518Z	41e01edc-01b3-11e7-8813-b77eb76b4f12	processing event: {"name":"6pack"}

사이트 성능 측정 람다 함수 개발

간단한 헬로 월드 코드로 사용법을 익혔으니 이제 본격적으로 사이트 성능 측정 함수를 만들어 보자. 방법은 간단하다. hello 폴더를 복사해서 사용하면 된다. 그리고 입맛에 맞게 코드를 작성하면 되는 것이다. 단 여기서 주의해야 할 것이 있다.

(1) index.js의 handle 메소드는 반드시 있어야한다. AWS 람다에서 이 메소드를 진입점으로 사용하기 때문이다. 그리고 메소드 인터페이스도 function(e, ctx, cb) 순으로 맞춰야 한다.

(2) 노드 버전 4를 사용해야 한다. 로컬 환경에서는 노드 최신버전으로 개발하다가 AWS에 배포하면 겪는 대부분의 문제가 버전에 따른 문제다. 노드 최신버전은 es6 문법을 거의(99%) 지원하는 반면 4 버전은 60%가 채 안된다. 제너레이터나 나머지 파라매터 등의 문법은 사용할 수 없다.

(3) 서드파티 라이브러리는 배포할 함수 폴더의 하위에 위치해야 한다. Apex은 배포할 폴더만 압축해서 업로드하기 때문이다. 그래서 각 함수별로 package.json 파일을 만들고 node_modules를 각 각 관리했다. 대신 테스트를 위한 모듈을 프로젝트 최상위 폴더에 위치하여 배포시에는 제외되도록 했다.

그래서 폴더구조는 다음과 같다.

project.json
functions
  - hello
    - index.js
    - package.json
    - node_modules
  - pageSpeed
    - index.js
    - config.js
    - models.js
    - PageSpeed.js
    - package.json
    - node_modules
package.json
node_modules
tests
  - hello
    - index.spec.js
  - pageSpeed
    - index.spec.js
    - PageSpeed.spec.js
    - mdoels.spec.js

RDS 연결

람다 함수에서 구글 페이지 스피드 API를 호출하여 결과를 응답 받은 뒤 이 데이터를 디비에 저장하는 것 까지가 함수의 기능이다. RDS를 사용할 것인데 보안 그룹이 다르기 때문에 RDS와 동일한 서브넷으로 람다 함수을 구성해야 한다.

대쉬보드에서도 설정할수 있지만 apex의 설정파일인 project.json에서도 설정할 수 있다.

{
  "vpc": {
    "subnets": ["subnet-111111", "subnet-222222"],
    "securityGroups": ["sg-333333"]
  }
}

데이터베이스 연결시 로컬은 로컬 디비를 사용하고 람다 환경에서는 시스템 환경 변수를 사용하도록 했다.

{
  "environment": {
    "DB_HOSTNAME": "12.34.56.78",
    "DB_DATABASE": "my_db_name",
    "DB_USERNAME": "user1",
    "DB_PASSWORD": "password1"
  }
}

이렇게 설정하면 노드에서는 process.env.DB_HOSTNAME 으로 환경변수 값을 가져올 수 있다.

서버 자원 설정

눈치껏 알겠지만 project.json에는 메모리와 람다함수 타임아웃 설정도 가능하다. 이번에 설정한 proeject.json 파일 내용이다.

{
  "name": "hello_world",
  "description": "hello world project",
  "memory": 128,
  "timeout": 60,
  "role": "arn:aws:iam::xxxxxx:role/hello_world_lambda_function",
  "vpc": {
    "subnets": ["subnet-111111", "subnet-222222"],
    "securityGroups": ["sg-333333"]
  },
  "environment": {
    "DB_HOSTNAME": "12.34.56.78",
    "DB_DATABASE": "my_db_name",
    "DB_USERNAME": "user1",
    "DB_PASSWORD": "password1"
  },
  "profile": "myprofile"
}

결론

"람다 함수로 작성하면 되겠지?"라고 생각할 때쯤이면 그 기능은 매우 단순한 기능일 것이다. 몇 시간 안에 로직을 만들고 배포하면 되겠지라는 생각으로 접근했다가 호되게 당한 경험이 많았었다.

Apex은 웹 대시보드 기능은 터미널로 가져왔다는 점에서 개발자에 친숙하다. 배포, 로깅, 설정을 모두 파일로 관리하고 터미널에서 실행할수 있으니깐 말이다.