sam에서 노드 모듈을 레이어로 빼기

  • 람다는 가벼운게 최고다
  • 노드 모듈은 따로 빼버리기!

Lambda Layer

  • nodejs로 치면 node_modules이다
  • 람다 함수에서 노드 모듈같은 종속성을 람다 함수 밖에으로 뺄 수 있음
  • 왜 쓰냐?
      1. 공통적으로 쓰이는 패키지들을 묶어서 관리할 수 있음
      1. 람다함수가 커지면 브라우저로 aws console에서 코드 조회를 못함

따라하기

외부 모듈을 사용해보기

  • sam template.yaml에서 layer설정할 수 있다
  • 지난번에 만든 hello world에서 이어서 진행해 보겠다
hello-world/app.js
1
2
3
// const axios = require('axios')
// const url = 'http://checkip.amazonaws.com/';
let response;
hello-world/app.js
1
2
3
const axios = require('axios');
const url = 'http://checkip.amazonaws.com/';
let response;
  • 맨 윗 2줄 주석 해제한다
hello-world/app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
exports.lambdaHandler = async (event, context) => {
try {
// const ret = await axios(url);
response = {
statusCode: 200,
body: JSON.stringify({
message: 'hello world',
// location: ret.data.trim(),
}),
};
} catch (err) {
console.log(err);
return err;
}

return response;
};
hello-world/app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
exports.lambdaHandler = async (event, context) => {
try {
const ret = await axios(url);
response = {
statusCode: 200,
body: JSON.stringify({
message: 'hello world',
location: ret.data.trim(),
}),
};
} catch (err) {
console.log(err);
return err;
}

return response;
};
  • 여기도 2곳 주석을 해제한다

package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"name": "hello_world",
"version": "1.0.0",
"description": "hello world sample for NodeJS",
"main": "app.js",
"repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs",
"author": "SAM CLI",
"license": "MIT",
"dependencies": {
"axios": "^0.18.0"
},
"scripts": {
"test": "mocha tests/unit/"
},
"devDependencies": {
"chai": "^4.2.0",
"mocha": "^6.1.4"
}
}
  • 필요없는 devDependencies 를 없앤다
  • test 할때 필요한 패키지인데, 우리는 안쓸거다
package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "hello_world",
"version": "1.0.0",
"description": "hello world sample for NodeJS",
"main": "app.js",
"repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs",
"author": "SAM CLI",
"license": "MIT",
"dependencies": {
"axios": "^0.18.0"
},
"scripts": {
"test": "mocha tests/unit/"
}
}
  • 없애면 이런 모양이 된다

cmd
1
2
cd hello-world
npm i
  • app.js가 있는 hello-world 디렉터리로 와서 종속성을 설치한다
1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── .aws-sam/
├── events
│ └── event.json
├── hello-world
│ ├── node_modules/
│ ├── tests/
│ ├── .npmignore
│ ├── app.js
│ └── package.json
├── .gitignore
├── README.md
└── template.yaml
  • node_modules 폴더가 생기면서 현재 디렉터리 구조는 이렇게 된다
  • hello-world/tests 폴더를 삭제한다. 우리는 안쓴다
  • events 폴더를 삭제한다. 우리는 안쓴다
  • .aws-sam 폴더를 삭제한다. 이 폴더가 있으면 local start-api 했을때 .aws-sam를 우선 참조하기때문에 소스 코드에 변경사항이 발생해도 반영되지 않는다
1
2
3
4
5
6
7
8
9
.
├── hello-world
│ ├── node_modules/
│ ├── .npmignore
│ ├── app.js
│ └── package.json
├── .gitignore
├── README.md
└── template.yaml
  • 현재까지 디렉터리 구조는 이렇게 된다

cmd
1
2
cd ..
sam local start-api --skip-pull-image
cmd log
1
2
3
4
5
6
7
C:\tmp\hello-world\hello-world>cd ..

C:\tmp\hello-world>sam local start-api --skip-pull-image
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM
CLI if you update your AWS SAM template
2020-09-21 14:49:47 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
  • 이렇게 location에 자신의 공인 IP가 뜨면 성공이다

  • 주석 해제한 소스 코드는

  • axios라는 모듈로 http://checkip.amazonaws.com/ 에서 공인 ip를 얻어와 response 객체에 location이라는 이름으로 담아서 반환한 것이다

  • http://checkip.amazonaws.com/ 에 접속해 보면 알 수 있듯이 자신의 공인 ip를 알려주는 api다

  • 여기까지 axios라는 모듈을 이용해봤다

  • 이제 sam build & sam deploy 를 통해 aws에 올려보자

cmd
1
sam build & sam deploy
  • 배포가 완료되면 엔드 포인트를 통해서 aws에 올린 람다를 실행해보자
  • 그러면 이상한 아이피가 나온다
  • 이게 aws 컴퓨터중에서 현재 람다가 실행된 컴퓨터의 공인 ip 주소이다
  • 여기까지 잘 작동되는 것이 확인 되었다

외부 모듈을 layer로 빼기

  • 이제 브라우저를 열고 aws console > lambda > HelloWorldFunction 으로 가서 함수가 어떻게 생겻는지 확인한다
  • 함수 안에 노드 모듈이 포함되어 있는 것을 확인할 수 있다
  • 이제 이 글의 핵심인 node_modules 폴더를 layer로 빼볼것이다
  • 레이어를 만드는 방법이 여러가지가 있지만 나는 내 방식을 설명해 보겠다
  • 나는 cmd명령을 사용할 것이다

  • template.yaml에서 빨간 네모 부분을 추가한다
template.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
hello-world

Sample SAM Template for hello-world

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3

Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
Layers:
- !Ref DependencyLayer
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: nodejs12.x
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /hello
Method: get
DependencyLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: HelloWorldFunction-layer
Description: Dependencies for HelloWorldFunction
ContentUri: opt/
CompatibleRuntimes:
- nodejs12.x
RetentionPolicy: Retain

Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
HelloWorldApi:
Description: 'API Gateway endpoint URL for Prod stage for Hello World function'
Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/'
HelloWorldFunction:
Description: 'Hello World Lambda Function ARN'
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: 'Implicit IAM Role created for Hello World function'
Value: !GetAtt HelloWorldFunctionRole.Arn
  • 최종코드는 이러하다
  • 눈여겨 봐야할 것은
  • Resources아래에 DependencyLayer라는 이름으로 layer정의하고
  • ContentUri: opt/ 를 지정한다. 이 opt 디렉터리에 우리가 layer로 따로뺄 패키지가 들어가게 될것이다
  • 이제 배치파일을 2개 만들거다
layer.bat
1
2
3
4
5
6
7
echo layer process

set functionDir=HelloWorldFunction

echo y|rmdir /s opt\nodejs
mkdir opt\nodejs
move .aws-sam\build\%functionDir%\node_modules opt\nodejs
  • layer.bat을 sam project 최상위에 만들고 내용은 위와같이 한다
  • set functionDir=HelloWorldFunction 에서는 template.yaml에서 지정한 함수명을 적어주면 된다. template.yaml을 안거드렸으면 HelloWorldFunction이니까 그대로 사용하면 된다
template.yaml
1
2
3
Resources:
HelloWorldFunction: // <- 이름과 매칭되도록
Type: AWS::Serverless::Function
deploy.bat
1
2
3
echo deploy

sam build & layer.bat & sam deploy
  • 또 deploy.bat을 sam project 최상위에 만들고 내용은 위와같이 한다
  • build하고 방금 만든 layer.bat으로 배포될 빌드 디렉터리에서 node_modules을 opt 디렉터리로 옮기고
  • deploy한다

  • 결과는 이렇게 된다
  • 1.2. 디렉터리 구조를 봐보면 node_modules 폴더가 없다
  • 3.4. yaml에서 지정한 layer 등록되어 있는 것을 확인할 수 있다
  • 당연하게도 이 함수는 잘 동작한다
  • 여기까지 batch파일을 활용해서 노드모듈을 레이어로 빼는 방법을 알아보았다
  • 여기까지 프로젝트 파일

심볼릭 링크 만들기

  • 동기화 하고 싶은 폴더가 있을때 정말 좋다

심볼릭 링크 만드는 법

  • 절대 위치 사용 ; mklink /d [링크위치] [실제위치]
  • 상대 위치 사용 ; mklink /d [링크위치] [링크위치를 기준으로 상대위치]
    • ex ) mklink /d .\.aws-sam\build\CommentFunction ..\..\dist
  • 링크이름 ; 새로만들어질 폴더 ; 현재 존재하지 않는 폴더여야 함
  • 실제위치 ; 원본 폴더 ; 이미 만들어져 있는 폴더

심볼릭 링크 삭제

rmdir [링크]

심볼릭 링크

  • 폴더 내용물이 공유된다. (사본이 아님)
  • 링크 폴더도 실제 폴더 처럼 작동한다
  • 바로가기 아이콘을 하고 있다
  • 원본 폴더에 별명을 지어준 느낌이다
  • 포인터 같은 느낌이다

  • 권한이 없다고 하면 관리자 권한으로 명령을 실행한다

상대경로를 사용해 만든경우

  • 당연한 말이지만 원본 폴더나 링크 폴더의 위치가 바뀌면 안된다
  • 디렉터리 구조가 변하지 않고 두 폴더를 이동한다면 잘 작동한다

절대경로를 사용해 만든경우

  • 링크 폴더를 아무렇게나 이동시켜도 잘 작동한다
  • 당연한 말이지만 원본 폴더를 옮기면 작동하지 않는다

내가 활용한 곳

aws lambda layer

  • lambda 에서는 함수의 크기를 줄이기 위해 layer라는 것을 사용한다
  • sam project에서 lambda를 작성하고 node_modules 같은 무거운 얘들은 layer로 빼야한다
  • 그런데 이 과정이 매우 귀찮다
    • package.json 에서 종속성들이 빌드되지 않게 주석
    • sam yaml에서 지정한 종속성 경로에 node_modules 사본 또는 옮기기
    • 수정작업할때, 코딩할때 자동완성 및 문서를 보기 위해서는 node_modules 폴더가 함수 안에 필요함
  • sam yaml에서 지정한 종속성 경로는 함수안이 되면 안됨. 즉, 직접 다이렉트로 node_modules를 지정할 수 없음. sam project 루트로 빼던가 해야함
  • 여기서 심볼릭 링크를 활용해서 두 폴더간 동기화를 진행!