회사가 AWS를 사용하고 팀이 Visual Studio Code를 주로 사용하기 때문에 Visual Studio Code에서 AWS Lambda를 개발하고 배포할 일이 많았는데 그간 Python으로 하다가 제가 온 이후 .NET으로 개발이 일정부분 되는 게 있어서 기록을 남기고자 합니다.
AWS 에는 Load Balancer가 3종류가 있는데, 그중 AWS Lambda Package를 통해 Template 지원을 받을 수 있는 Load Balancer는 Application Load Balancer 입니다. Network Load Balancer와 Class Load Balancer는 Template이 없어서 이렇게 따로 해줘야합니다.
dotnet add package AWSSDK.Lambda
.NET 에서 AWS Lambda 관련된 추가 라이브러리 패키지 설치
dotent new record --name {레코드 이름}
.NET record 파일 생성
dotnet build
프로젝트 어셈블리 빌드
dotnet build -c Release
명령으로 Release 빌드. 빌드는 일반적으로 Release 배포를 사용하기 때문에 -c 라는, --configuration 옵션을 줘서 Release를 지정했습니다. 그냥 dotnet build 라고하면 debug로 빌드됩니다. 물론 -c Debug 로 명시적인 Debug 빌드도 가능합니다.
dotnet new solution --name {솔루션 이름}
.NET 솔루션 파일 생성
.NET은, Project 단위로 빌드/배포 되기 때문에 솔루션 파일이 꼭 필요하지는 않습니다. 다만 여러개의 .NET Project를 하나로 묶어서 관리할 수 있기 때문에 편리하여서 주로 쓰는 편이고, Visual Studio를 이용해서 프로젝트 생성을 하면 Solution과 프로젝트 파일이 함께 생성되지만, dotnet CLI를 이용하여 .NET Project를 생성하면 기본적으로 솔루션 파일이 없습니다.
aws-lambda-tools-defaults.json 파일에 구성합니다. 일종의…게시 프로필 이라고 생각하시면 될 것 같습니다. Visual Studio 2022와 잘 통합되어 있는 Azure의 경우 Visual Studio 2022의 Publish 기능에 게시 프로필을 생성하는 게 있는데 그것과 같은 역할을 한다고 보시면 됩니다. Visual Studio 2022에서는 기본적으로 1개를 다루지만, 게시 프로필을 여러개 만들면 dotnet 명령어로 다양하게 배포할 수 있기 때문에 .NET 프로젝트 1개 안에 여러개의 AWS Lambda Class, Method를 생성하고 여러 AWS Lambda로 배포하여 각기 다른 기능을 하게 할 수 있습니다. 즉, 여러 개의 Lambda를 다루기 위해서 .NET 프로젝트를 1:1 대응으로 여러개 만들 필요가 없다는 것입니다. 1개의 .NET 프로젝트와 N:M 관계의 Class와 게시 프로필 관계이면 됩니다.
임의로 변수는 대괄호[ ] 안에 표기하겠습니다.
{
"Information" : [
"[문자열1]",
"[문자열2]"
],
"profile" : "[AWS Region ID]",
"region" : "[AWS 리전 이름 ex) us-west-2]",
"configuration" : "[빌드 형태 ex) Release, Debug]",
"function-architecture" : "[아키텍쳐 ex) x64, arm64]",
"function-runtime" : "[.NET 버전 ex) dotnet8]",
"function-memory-size" : 512,
"function-timeout" : 15,
"function-handler" : "[.NET 어셈블리 이름::네임스페이스.클래스 명::Lambda로 트리거되는 메서드 이름]",
"framework" : "net8.0",
"function-name" : "[AWS Lambda 리소스 이름]",
"function-description" : "",
"package-type" : "Zip",
"function-role" : "[AWS Lambda 리소스를 만들면 만들 때 role을 새로 생성할 수도 있고, 기존의 role을 가져다 쓸 수도 있습니다. 그 role의 ARN을 적습니다.]",
"function-subnets" : "",
"function-security-groups" : "",
"tracing-mode" : "PassThrough",
"environment-variables" : "",
"image-tag" : ""
}
AWS role이 놓치기 쉬운 부분인데 지금 예제로 드는 EC2를 제어하는 AWS Lambda의 경우 새로 생성한 권한이던, 기존에 있던 권한이던, 권한 정책에 아래 두개의 정책이 들어있어야만 제어가 가능합니다.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace [네임스페이스~];
public class Function
{
private static readonly AmazonElasticLoadBalancingV2Client elbv2Client = new(RegionEndpoint.USWest2);
public async Task<TargetHealthDescription[]> FunctionHandler(LoadBalencingTargetGroupRequest input, ILambdaContext context) // 이 메서드의 이름은 128 바이트 이하여야 합니다. 따라서 기능의 구분은 Class 이름으로 하는 것이 좋습니다.
{
SemaphoreSlim semaphore = new(input.TargetGroupArn.Length);
List<Task<TargetHealthDescription[]>> tasks = new(input.TargetGroupArn.Length);
foreach (string arn in input.TargetGroupArn)
{
await semaphore.WaitAsync();
Task<TargetHealthDescription[]> task = Task.Run(async () =>
{
DescribeTargetHealthRequest request = new() { TargetGroupArn = arn };
try
{
DescribeTargetHealthResponse response = await elbv2Client.DescribeTargetHealthAsync(request);
TargetHealthDescription[] resRaw = [.. response.TargetHealthDescriptions];
return resRaw;
}
catch (AmazonServiceException ex)
{
context.Logger.LogError($"AWS Service Exception: {ex.Message}");
context.Logger.LogError($"HTTP Status Code: {ex.StatusCode}");
context.Logger.LogError($"AWS Error Code: {ex.ErrorCode}");
context.Logger.LogError($"Error Type: {ex.ErrorType}");
context.Logger.LogError($"Request ID: {ex.RequestId}");
throw;
}
catch (Exception ex)
{
context.Logger.LogError($"Exception: {ex.Message}");
throw;
}
finally
{
semaphore.Release();
}
});
tasks.Add(task);
}
TargetHealthDescription[][] response = await Task.WhenAll(tasks);
return response.SelectMany(x => x).ToArray();
}
}
Usings.cs
global using System;
global using System.Threading;
global using System.Threading.Tasks;
global using Amazon;
global using Amazon.Runtime;
global using Amazon.Lambda.Core;
global using Amazon.ElasticLoadBalancingV2;
global using Amazon.ElasticLoadBalancingV2.Model;
LoadBalancingTargetGroupRequest.cs
namespace [네임스페이스~]
public record LoadBalencingTargetGroupRequest(string[] TargetGroupArn);
.NET 프로젝트를 위와 같이 구성해두고, 아래와 같이 Input을 넣으면 됩니다.
역시 변수는 대괄호 [ ] 안에 표기하겠습니다.