AWS Lambda를 사용할 때 불편한 점은 Lambda에 배포해야 테스트를 해볼 수 있다는 부분이다. 그동안 Lambda를 쓰면서 느낀 경험은 최대한 로직은 유닛테스트로 기능을 확인하고 Lambda의 핸들러에서는 간단히 이 로직은 호출만 하는 정도로 작성하면 로컬에서 테스트를 해보면서 코드를 작성할 수 있다. 코드를 수정할 때마다 Lambda에 배포하는 것은 매우 불편한 일이므로 로컬에서 테스트할 수 있으면 시간을 많이 줄일 수 있다.
Node.js이든 Python이든 간단한 코드면 유닛테스트로 충분하지만 좀 더 복잡하게 사용할 일이 있다면 Lambda에 배포를 해야 하므로 매우 귀찮을 일이 된다. 특히 얼마 전에 사용해 본 Headless Chrome을 Lambda에서 사용하는 작업은 Lambda 환경의 제약을 확인해 봐야 하므로 잦은 배포를 해야 해서 아주 귀찮을 일이었다. 특히 Lambda 함수의 용량이 크다면 배포할 때마다 시간도 걸리기 때문에 더욱 작업이 번거로운 일이 된다.
lambci/docker-lambda
LambCI는 Lambda에서 CI를 할 수 있게 하는 서버리스 솔루션인데 여기서 Lambda 환경을 거의 같게 만든 Docker를 제공하고 있다. 내가 작업한 것은 아니지만 LambCI는 nodejs-ko에서 배포용으로 쓰고 있어서 알고는 있었지만 docker-lambda
는 모르고 있다가 얼마 전에 AWS의 Lambda를 로컬에서 테스트해볼 수 있게 하는 SAM Local을 보다가 여기서 로컬 테스트 용도로 LambCI의 docker-lambda 이미지를 사용한다는 것을 알게 되어 Docker만 사용해보았다.(sam-local의 경우 CloudFormation기반이라서 테스트는 해보지 않았다.)
테스트 목적으로 docker-lambda를 사용해봤는데 100% Lambda와 같지는 않아도 거의 비슷하게 환경이 구성되어 있으므로 로컬에서 쉽게 테스트해볼 수 있다. 사용해본 느낌으로는 몇 가지 차이점만 실제 AWS Lambda에 배포해서 테스트해보고 나머지는 docker-lambda에서 동작 여부를 확인해 보는 정도로 충분해서 아주 편했다. AWS Lambda에서 안 되는 경우 docker-lambda에서도 안 되는지 확인하고 docker-lambda에서 동작하도록 코드를 수정한 뒤에 AWS Lambda에 배포하는 식으로 작업하니까 시간을 꽤 줄일 수 있었다. 물론 docker-lambda에서는 동작하지만 다른 환경 문제고 AWS Lambda에서는 안 되는 경우가 없는 것은 아니다.
docker-lambda로 Lambda 함수 실행하기
Lambda 함수를 LambCI의 Docker 이미지로 테스트해볼 수 있는데 Docker Hub에 환경별로 올려져 있다. nodejs4.3
, 'nodejs', 'nodejs6.10', 'python2.7', 'python3.6', 'java8' 등 Lambda에서 지원하는 환경이 모두 Docker 이미지로 존재한다.
Node.js를 사용한다고 할 때 다음과 같은 index.js
가 있다고 해보자.
// index.js
console.log('starting function');
exports.handler = (event, context, callback) => {
console.log('event:', event);
context.succeed('hello world');
};
여기서는 코드의 내용이 중요하진 않으므로 간단한 Lambda 코드만 사용했다. 터미널에서 Docker 이미지로 다음과 같이 Lambda 함수를 실행할 수 있다.
$ docker run -v "$PWD":/var/task lambci/lambda:nodejs6.10
START RequestId: 543c5021-013e-17b4-ffea-e179e78c5b7b Version: $LATEST
2017-08-27T16:35:19.970Z 543c5021-013e-17b4-ffea-e179e78c5b7b starting function
2017-08-27T16:35:19.972Z 543c5021-013e-17b4-ffea-e179e78c5b7b event: {}
END RequestId: 543c5021-013e-17b4-ffea-e179e78c5b7b
REPORT RequestId: 543c5021-013e-17b4-ffea-e179e78c5b7b Duration: 7.65 ms Billed Duration: 100 ms Memory Size: 1536 MB Max Memory Used: 28 MB
"hello world"
위에서 보듯이 현재 폴더에 있는 Lambda 함수를 nodejs6.10
환경에서 실행해 볼 수 있고 실제 AWS Lambda에서 볼 수 있는 로그처럼 실행 결과까지 모두 볼 수 있다. Node.js 6.10 환경이 아니라면 다른 이미지를 사용하면 되고 -v "$PWD":/var/task
에서 볼 수 있듯이 현재 폴더를 Docker 내에서 실행하게 된다.
// dist.js
console.log('starting function');
exports.entry = (event, context, callback) => {
console.log('event:', event);
context.succeed('hello world');
};
위처럼 기본값인 index.js
에 handler
함수를 사용하지 않고 dist.js
파일에 entry
라는 함수를 진입점으로 쓰고 싶다면 Docker 명령어 마지막에 dist.entry
처럼 핸들러를 지정해 주면 된다.(AWS Lambda의 설정에서도 handler를 지정할 수 있다.)
docker run -v "$PWD":/var/task lambci/lambda:nodejs6.10 dist.entry
START RequestId: d5f50084-0ea8-1530-8183-dc2fce92b7b4 Version: $LATEST
2017-08-27T16:45:18.067Z d5f50084-0ea8-1530-8183-dc2fce92b7b4 starting function
2017-08-27T16:45:18.069Z d5f50084-0ea8-1530-8183-dc2fce92b7b4 event: {}
END RequestId: d5f50084-0ea8-1530-8183-dc2fce92b7b4
REPORT RequestId: d5f50084-0ea8-1530-8183-dc2fce92b7b4 Duration: 8.79 ms Billed Duration: 100 ms Memory Size: 1536 MB Max Memory Used: 28 MB
"hello world"
지금까지는 입력 이벤트가 없이 실행했는데 입력 이벤트가 있는 경우에는 docker
명령어 마지막에 JSON을 전달하면 된다.
$ docker run -v "$PWD":/var/task lambci/lambda:nodejs6.10 dist.entry '{ "name": "outsider" }'
START RequestId: b86d5ed1-293d-1be5-9fc6-fb87968e741c Version: $LATEST
2017-08-27T16:48:21.068Z b86d5ed1-293d-1be5-9fc6-fb87968e741c starting function
2017-08-27T16:48:21.070Z b86d5ed1-293d-1be5-9fc6-fb87968e741c event: { name: 'outsider' }
END RequestId: b86d5ed1-293d-1be5-9fc6-fb87968e741c
REPORT RequestId: b86d5ed1-293d-1be5-9fc6-fb87968e741c Duration: 11.63 ms Billed Duration: 100 ms Memory Size: 1536 MB Max Memory Used: 28 MB
"hello world"
위 로그에서 보듯이 전달한 JSON이 출력된 것을 볼 수 있다.
Headless Chrome을 사용하면서 꽤 많은 테스트를 해보았을 때 Lambda 환경이 거의 그대로 구현되어 있어서 테스트를 상당히 편리하게 도와준다. 그리고 sam-local에서도 채택한 걸 보면 LambCI에서 제공하는 Docker 이미지가 신뢰할 수 있는 퀄리티를 제공하고 있다고 생각된다. AWS에서 직접 공식 이미지를 제공해 주면 아주 좋겠지만....
매우 재미난 글 잘 읽었습니다. AWS Lambda function 매커니즘을 보다가 여기까지 왔네요.
한가지 궁금한 사항은...
AWS Lambda Function 을 만든다는 것이 '인스턴스를 생성한다.' 로 이해하면 될지 의문입니다.
예1) Function 을 2개 만들면 인스턴스가 2개 생성되고 호출이 끝나면 인스턴스가 삭제되는 것인지요?
최초 인스턴스는 생성되겠지만 호출하는 것은 이 인스턴스 내부에 존재하는 Task 가 2개 동작하면서 각각 응답하여야 되지 않을까요?
아무리 봐도 docker 컨테이너를 function 이 호출될 때 마다 생성하고 호출이 완료되면 삭제하지는 않을 것 같은데... 이 의문을 가지고 일주일째 여기저기 기웃거리다가 질문드립니다.
혹시 알고 계시면 답변 부탁드립니다.
그부분에 대해서는 아마존에서 설명한 글을 본적이 없어서 자세히는 알지 못합니다. 그동안의 경험과 다른 분들의 Lambda 관련 글을 보면 Lambda를 위한 인스턴스들이 있고 여기에 실행했다가 시간이 지나면 죽이는 것 같습니다.
이게 Docker는 아닐것 같고 비슷한 형태의 어떤 것일것 같은데 추정만 할뿐입니다.
그래서 실제로 장시간 사용안하면 새로 실행할 때 꽤 시간이 걸리는 편이고 때에 완전히 내려가지 않고 다시 실행되어서 이전에 tmp에 남긴 것을 그대로 사용할 수 있는 것으로 알고 있습니다.
친절한 답변 감사드립니다.
저도 나름 찾아본 결과 AWS Lambda function 을 만들때 'Advanced Setting' 메뉴에서 timeout 을 지정하는 것을 보았는데...
아마도 이를 사용하여 '처음 호출 될 때 대기 시간이 추가되고 이후에 모든 후속 호출에 대해 동일한 컨테이너가 재사용' 되는 것으로 추측됩니다.
(https://medium.com/@kamal.rawat/aws-lambda-and-serverless-computing-3d24756a19e4)
하지만, 정말이지 추측에 가까운 제 생각이거니와 말씀하신 것 처럼 이 컨테이너가 어떤 타입으로 실행이 되는지는 아직 잘 모르겠네요.
좀 더 찾아보고 확실해지면 공유하겠습니다.
감사합니다.