결론부터
개인적인 지적 호기심(Cognito를 통한 서버리스 인증 아키텍처와 관련 된)으로 AWS Lambda에서 AWS SDK를 사용할일이 생겼다. 만들면서 SDK for Javascript의 버전3가 출시된걸 알게 됐고, AWS CDK를 통해 Lambda Layer로 만들어서 사용하게 됐다.
AWS Lambda와 Layer를 개발하기 위해 CDK를 사용하면 콘솔이나 CLI를 사용하는 것보다 비교적 쉽게 구현할 수 있다. 특정 라이브러리 버전도 고정 시키면서 CDK Repository 내에 람다 레이어 소스가 관리되는건 덤.
AWS Lambda Layer란?
Lambda Layer는 Lambda 함수와 함께 사용할 수 있는 라이브러리 및 기타 종속성을 패키징하는 편리한 방법을 제공한다고 공식문서에 적혀있다. AWS에서는 Layer를 우리나라말로 계층이라고 표현하지만 레이어가 입에 감기는 이유는 뭘까.
Lambda Layer는 노드나 자바같은 런타임에서 라이브러리를 모든 Lambda에 각각 가져와서 사용하는 것이 아닌, 공통으로 사용하는 기능만 배포해서 각 Lambda에 붙여서 사용할 수 있는 기술이다. 예를 들면, 외부 라이브러리를 레이어로 만들어서 람다에 레이어만 붙이면 그 라이브러리가 람다에 포함되어 설치 없이 바로 사용할 수 있게 된다. 비단 외부 라이브러리 뿐만 아니라, 특정 조직에서 같은 로직(예를 들면 유틸, 헬퍼 함수와 같은 것들) 들을 람다 레이어로 묶어서 배포하는 방법도 가능하다. 특정 람다에서만 사용하는 것이 아닌, 범용적으로 사용 될 가능성이 있는 기능들은 Lambda Layer로 배포하면 된다.
AWS SDK를 특정버전으로 고정하기 위해서도 쓰이는데, 작성 시점을 기준으로 Lambda Node.js 16 버전에서의 AWS SDK는 2.1083.0을 사용하고 있다.
AWS SDK for JavaScript v3를 사용하고 싶은데?
자바스크립트에서 사용하는 AWS SDK v3는 2020년 12월에 출시됐다.
무겁고 모든 기능이 포함된 하나의 SDK 대신 각 모듈별로 쓸 수 있도록 변경된것이 하나의 장점인데, 그건 둘째치고 AWS SDK 버전을 하나로 고정하고 싶었고, 이왕 쓰는김에 많은 사람들이 원했다고 더 가볍게 만들어 달라고 했던 자바스크립트용 AWS SDK v3를 사용하기로 결정했다. 공부는 덤이고.
AWS Lambda에서의 Node.js 런타임은 AWS SDK v2를 지원하고 있고 동적으로 변하기 때문에, 특정 마이너 버전을 고정하면서 v3를 사용하기 위해 Lambda Layer을 사용했다.
AWS CDK를 통한 Lambda & Lambda Layer
AWS CDK를 사용하면 Lambda 소스를 CDK 내부에 포함시킬 수 있다. 이젠 콘솔에서 또각또각 트랙패드 클릭하는것보다 CDK 코드로 개발하는 것이 편하고, 작성한 코드 자체로 버전관리 및 문서화가 된다. Lambda가 실행되는 코드 또한 CDK 프로젝트에 포함돼서, 관리하기 편해진다.
Lambda 자체는 아래 소스와 같이 간단하게 개발할 수 있다.
Lambda CDK 스택 (CDK TypeScript)
/**
* AWS CDK V2
* /lib/lambda/auth-function-stack.ts
*/
import * as path from "path";
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { aws_lambda as lambda } from "aws-cdk-lib";
import { AwsSdkV3Layer } from "./layer/aws-sdk-v3-layer";
/**
* AWS CDK AuthFunctionStack
* @Description Lambda Layer를 사용하는 Lambda Function
*/
export class AuthFunctionStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const fn = new lambda.Function(this, "authFunction", {
functionName: "ChatAuthFunction",
runtime: lambda.Runtime.NODEJS_16_X,
handler: "auth.handler",
code: lambda.Code.fromAsset(path.join(__dirname, "/../../api/functions")),
description: "managed by cdk",
// layers: [new AwsSdkV3Layer(this, "layer").layer],
// 다음과 같이 람다 레이어를 리스트로 포함 시킬 수 있다. 레이어 또한 CDK 소스로 개발한다.
});
}
}
Lambda 내부 코드에는 테스트를 위해 AWS SDK V3에 포함된 @aws-sdk/client-lambda를 추가하고 LambdaClient를 생성하도록 구성했다. 람다 내부에서 람다를 제어하려는건 아니고, Lambda SDK를 사용하려는 목적은 아니지만 sdk v3가 적용 됐는지 대강 테스트 하기 위해 만들었다. 아래와 같이 생겼다.
Lambda Code (JavaScript)
const { LambdaClient } = require("@aws-sdk/client-lambda");
exports.handler = async (event) => {
const client = new LambdaClient({ region: "ap-north-east-2" });
console.info(JSON.stringify(client));
const response = {
statusCode: 200,
body: "Hello Lambda",
};
return response;
};
위에서 작성한 Lambda 코드는 CDK를 통해서 배포하고, 레이어가 적용되지 않은 상태 (CDK 소스 내 layer 항목이 빠졌다면)에서는 aws-sdk v3를 사용하면 아래 이미지와 같이 에러가 난다.
레이어를 적용하기 위해 CDK Lambda 에서 제공하는 LayerVersion을 사용했다. 레이어면 레이어지 이름이 왜 레이어버전인지🤪는 찾아볼만큼 궁금하진 않았다. 레이어버전 역시 람다만큼이나 간단하게 구현된다. 아래 코드와 같이 CDK를 구성하고 cdk deploy로 배포하면 로컬에서 레이어를 위해 만든 deployment.zip 파일이 Lambda Layer로 배포된다.
Lambda Layer Contruct (typescript)
/**
* /lib/lambda/layer/aws-sdk-v3-layer.ts
*/
import * as cdk from "aws-cdk-lib";
import { aws_lambda as lambda } from "aws-cdk-lib";
import { Construct } from "constructs";
import * as path from "path";
/**
* AWS CDK AwsSdkV3Layer (Construct)
* @Description AWS SDK V3 (Node.js)를 포함한 람다 레이어
*/
export class AwsSdkV3Layer extends Construct {
public readonly layer: lambda.LayerVersion;
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id);
const layer = new lambda.LayerVersion(this, "awsSdkV3Layer", {
code: lambda.Code.fromAsset(
path.join(
__dirname,
"../../../api/lambda-layers/aws-sdk-v3/deployment.zip"
// 👆 배포할 람다 레이어 소스(deployment.zip)가 필요하다.
)
),
compatibleRuntimes: [lambda.Runtime.NODEJS_16_X],
description: "managed by cdk. aws-sdk-v3 Layer",
});
this.layer = layer;
}
}
Lambda Layer를 위한 배포파일 만들기
CDK LayerVersion을 통해 배포되는 레이어 소스(.zip)를 만들기 위해 임의의 경로(api)에 레이어 전용 폴더(lambda-layers)를 따로 구성했다. lambda-layer 폴더에서 다음과 같은 명령어를 순서대로 입력하면 aws-sdk-v3에서 lambda sdk를 사용할 수 있는 Lambda Layer가 완성된다.
mkdir aws-sdk-v3
cd aws-sdk-v3/
npm install @aws-sdk/client-lambda
mkdir nodejs
mv node_modules/ nodejs/
zip -r deployement.zip nodejs/
여기서 필수적인 룰이 있는데, Lambda Layer을 위해 각 런타임(node, java, python 등등)별로 Layer 경로를 잡아줘야 한다.
현 시점에서 lambda에서 제공하는 node.js 최신 버전 v16의 경우, nodejs/node_modules 로 잡아주면 된다. 이후 위에서 작성한 CDK 소스를 배포하면 Lambda Layer가 배포되고, Lambda 스택에서도 레이어를 포함시켜 배포하면 디펜던시를 바로 사용할 수 있게 된다.
정리
CDK 프로젝트 디렉토리 구조는 아래와 비슷하게 구성된다. api 폴더는 임의로 지은 이름으로, 사용하는 CDK의 프로젝트 특성따라 효율적으로 구조를 잡아가면 될 것 같다.
CDK 루트/
├── api/
│ └── lambda-layers/
│ └── aws-sdk-v3/
│ ├── nodejs/
│ │ └── node_modules
│ ├── package-lock.json
│ ├── package.json
│ └── deployment.zip
├── bin/
│ └── cdk.ts
├── lib/
│ ├── lambda/
│ │ ├── layer/
│ │ │ └── aws-sdk-v3-layer.ts
│ │ └── auth-function-stack.ts
│ └── 기타 등등 CDK 파일들.ts
├── test
├── .gitignore
├── .npmignore
├── cdk.json
├── jest.config
├── package.json
├── README.md
├── tsconfig.json
└── yarn.lock
람다를 다시 테스트하면 아래와 같이 aws-sdk v3의 LambdaClient JSON 항목을 확인할 수 있다.
AWS SDK v3의 경우 모듈화가 되었기 때문에, v2처럼 필요한 모든 기능을 무겁게 하나의 레이어에 설치하는 것보다는 필요에 따라 레이어를 나눌 수 있게 되었다. 예를 들면, EC2 SDK가 필요하면 레이어를 만들고 Cognito SDK가 필요하면 따로 만들어서 필요에 따라 붙여서 사용하는 전략. 대신 sdk 버전업데이트 할때마다 모든 레이어를 하나씩 올려야한다는게 함정인데, SDK를 많이 사용하는것도 아니고.. 저런식으로 사용하게 되면 다시 고민해봐야지.