.NET 환경에서 EC2 관련 AWS Lambda 개발

요즘은 DevSecOps 팀 소속되어 개발할 일이 많이 없습니다.

다만 코딩 이외의 새로운 기술을 익히면서 좋은 시간을 보내고 있습니다.

회사가 AWS를 사용하고 팀이 Visual Studio Code를 주로 사용하기 때문에 Visual Studio Code에서 AWS Lambda를 개발하고 배포할 일이 많았는데 그간 Python으로 하다가 제가 온 이후 .NET으로 개발이 일정부분 되는 게 있어서 기록을 남기고자 합니다.

찾아보면 많이 있는 자료이지만, 한국어로 된 정리된 자료가 있으면 좋겠다고 생각했습니다.

저는 Visual Studio Code에서 PowerShell Core를 이용하여 개발 중 입니다.
나중에는 PowerShell로 AWS Lambda에도 배포해보고 싶습니다.

Visual Studio 를 주로 쓰셔서 IDE가 익숙하면 dotnet CLI 쪽이 아무래도 약해지게 되는데, Visual Studio도 dotnet 명령어 실행기인 것이기 때문에 명령어가 익숙하지 않으신 분들은 이번 기회에 한번 보시면 좋을 것 같습니다.

아직 Lambda를 많이 만들어보지 않아서 AWS .NET Toolkit에서 제공해주는 많은 .NET Project Template들을 사용하는게 익숙하지 않습니다. 따라서 비어있는 Lambda 함수 템플릿에 Nuget Package를 설치하여 진행했습니다.

8 Likes

필요한 Visual Studio Code Extension

  • .NET Extension Pack
  • .NET Install Tool
  • .NET Core Test Exploror
  • C#
  • C# Dev Kit
  • AWS Toolkit

.NET 명령어

dotnet new list

  • 현재 PC에 설치된 .NET 환경에서 생성할 수 있는 .NET 프로젝트 템플릿 목록 출력

dotnet new install Amazon.Lambda.Templates

  • .NET 환경으로 AWS Lambda를 개발할 수 있는 프로젝트 템플릿을 PC에 설치

dotnet new lambda.EmptyFunction --name {프로젝트 이름}

  • .NET AWS Lambda 비어있는 함수 프로젝트 생성

dotnet restore

  • .NET 프로젝트 복원 (프로젝트 생성 후 1회 실행 반드시 필요)
  • 추후 nuget 패키지 복원 과정에서도 꼭 필요한 명령

dotnet new class --name {클래스 이름}

  • 현재 경로에 .NET class 파일 생성

dotnet add package AWSSDK.ElasticLoadBalancingV2

  • .NET으로 AWS EC2 Load Balancer를 제어할 수 있는 패키지
  • 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를 생성하면 기본적으로 솔루션 파일이 없습니다.

dotnet sln {솔루션 파일 경로} add {프로젝트 파일 경로}

  • .NET 솔루션 파일에 .NET 프로젝트 파일 추가
  • 여러 번 입력으로 여러 프로젝트를 하나의 솔루션 파일에 추가 가능

dotnet tool list

  • PC에 설치된 .NET을 다루기 위한 추가적인 add-in tool 설치 목록

dotnet tool install -g {Nuget ‘Tool 패키지’ 이름}

AWS .NET Mock Lambda Test Tool
  1. aws-lambda-dotnet/Tools/LambdaTestTool/README.md at master · aws/aws-lambda-dotnet (github.com) 문서 확인
  2. .NET 8.0 기준,
dotnet tool install -g Amazon.Lambda.TestTool-8.0

위 명령어로 설치하고 .NET 버전이 업데이트 되어서 도구도 함께 업데이트 되었을 경우

dotnet tool update -g Amazon.Lambda.TestTool-8.0

으로 업데이트할 수 있습니다. 그리고 Visual Studio는 디버깅 시 자동으로 Lambda Test Tool이 실행되지만, Visual Studio Code의 경우 아래 명령어로 테스트 도구를 먼저 실행해놓고, 디버그 해야합니다.

dotnet lambda-test-tool-8.0

이 테스트 도구를 설치하면 아래 경로에 테스트 실행 파일이 있습니다.

C:\Users\%USERNAME%\.dotnet\tools\dotnet-lambda-test-tool-8.0.exe
AWS Lambda Deployment By Profile
dotnet lambda deploy-function -cfg [게시프로필 JSON 파일]
3 Likes

Visual Studio Code로 AWS Lambda에 .NET 앱 배포

  1. Visual Studio Code 플러그인 - AWS Toolkit for Visual Studio Code - AWS 다운로드 및 설치
  2. aws toolkit 을 이용하여 aws lambda에 배포할 region으로 로그인 (Region 마다 계정이 다를 것이며, Visual Studio Code에서는 여러개의 region에 동시에 로그인 가능합니다.)
  3. .NET 프로젝트 경로(csproj 파일이 있는 경로)에서 Release 명령(dotnet build -c Release) 수행
  4. 사진을 준비하지 못하지만, 아마도 정상적으로 AWS 로그인이 되었다면 1번 링크의 vscode aws 확장 기능을 통해 aws의 리소스들을 조회할 수 있을 것이고 그중 Lambda 폴더도 있을 것입니다.
  5. AWS Console (AWS 웹사이트)에 접속해서 내 .NET Application을 배포할 수 있는 AWS Lambda 리소스를 생성합니다. 일종의 배포공간이라고 보시면 됩니다.
  6. 생성하고 나서 vscode에서 4번의 lambda 항목을 새로고침하면 5번에서 생성한 Lambda 리소스가 보일 것입니다. 여기서 마우스 우클릭 하여 Upload Lambda… 버튼을 누릅니다.
  7. vscode 명령 팔레트에 압축파일로 올릴 것인지, 아니면 폴더를 링크하면 폴더에서 알아서 가져갈 것인지 선택할 수 있는데 저는 Directory를 사용했습니다.
  8. vscode 명령 팔레트에 no / yes 가 나오는데 솔직히 잘 모르겠으므로 no 눌렀습니다.
  9. 그러면 Windows 대화상자를 통해서 디렉토리를 선택할 수 있는데, 아까 dotnet build -c release를 수행했던 경로에 가면 내 .NET 버전별로 빌드된 파일들이 있습니다. 아마도 아래와 같은 형태의 경로일 것입니다.
{.NET 프로젝트 경로}\bin\Release\net8.0\

게시 프로필이 여러 개 일 경우 명령어로 배포

이 댓글을 참고하시면 됩니다.
게시 프로필이 여러개일 경우 아래와 같이 각각 .NET CLI를 이용하여 배포할 수 있습니다.

dotnet lambda deploy-function -cfg aws-lambda-tools-defaults-function1.json
dotnet lambda deploy-function -cfg aws-lambda-tools-defaults-function2.json
dotnet lambda deploy-function -cfg [게시프로필 JSON 파일]
3 Likes

AWS Lambda Debugging for Visual Studio Code

위 링크에 있는대로 vscode의 테스트 파일(launch.json)을 구성하면 됩니다. 구성 형태는 위 링크에 나와있습니다.

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": ".NET Core Launch (console)",
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "build",
            "program": "C:/Users/{윈도우 계정 이름}/.dotnet/tools/dotnet-lambda-test-tool-8.0.exe",
            "args": [],
            "cwd": "${workspaceFolder}",
            "console": "internalConsole",
            "stopAtEntry": false,
            "internalConsoleOptions": "openOnSessionStart"
        }
    ]
} 
Visual Studio Code launch.json Variable Intelisense

launch.json에서 설정하다보면 vscode가 사용가능한 ${} 형태의 변수들을 자동완성 해주는데, 공식 vscode 문서에도 잘 안나오는 변수 목록 설명을 적어놨습니다.

  • ${cwd} - 시작 시 작업 실행기의 현재 작업 디렉터리
  • ${defaultBuildTask} - 기본 빌드 작업 이름 (tasks.json 파일}
  • ${extensionInstallFolder} - Visual Studio Code 확장이 설치된 경로
  • ${fileBasenameNoExtension} - 현재 열려 있는 파일의 확장자를 제외한 기본 이름
  • ${fileBasename} - 현재 열려 있는 파일의 기본 이름
  • ${fileDirname} - 현재 열려 있는 파일의 경로
  • ${fileExtname} - 현재 열려 있는 파일의 확장자
  • ${fileWorkspaceFolderBasename} -
  • ${file} - 현재 열려있는 파일
  • ${lineNumber} - 현재 열려 있는 파일에서 활성화된 줄 번호
  • ${pathSeparator} - 현재 운영 체제에서 파일 경로를 구분하는 문자
  • ${relativeFileDirname} - ${workspaceFolder}와 관련된 현재 열려있는 파일의 Directory Name
  • ${relativeFile} - ${workspaceFolder}를 기준으로 현재 열려있는 파일
  • ${selectedText} - 활성된 파일에서 현재 선택된 텍스트
  • ${workspaceFolderBasename} - vscode에서 연 폴더의 이름
  • ${workspaceFolder} - vscode에서 연 폴더 경로

테스트 파일을 구성하고 나면 이후부터 실행하고 값넣고하는 노가다가 아니라 breakpoint를 찍을 수 있는 디버그 환경으로 프로세스를 시작할 수 있습니다.

3 Likes

AWS Lambda 배포 설정

  • 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의 경우 새로 생성한 권한이던, 기존에 있던 권한이던, 권한 정책에 아래 두개의 정책이 들어있어야만 제어가 가능합니다.

  • ElasticLoadBalancingFullAccess
  • ElasticLoadBalancingReadOnly
1 Like

ARN을 이용해서 EC2 정보 얻기

Function.cs

[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을 넣으면 됩니다.
역시 변수는 대괄호 [ ] 안에 표기하겠습니다.

{
    "TargetGroupArn": [
        "[AWS Network Load Balancer ARN 정보1]",
        "[AWS Network Load Balancer ARN 정보2]",
        ...
    ]
}
1 Like

위 문서는 과거 버전이지만, 공식적으로 .NET으로 AWS Lambda를 배포하는 방법이 설명되어 있습니다.

1 Like

사용하면서 느낀 것은, 위의 수많은 행위들이 Visual Studio 2022를 통해서 하면 마우스 딸깍딸깍으로 아주 쉽게 잘 된다는 것입니다.

무슨 테스트 환경구성한다고 파일 만들 필요도 없고, 명령어 하나하나 입력할 필요도 없고… (물론 vscode에서도 마우스 클릭으로 .NET 프로젝트를 만들 수는 있습니다.)

역시 vscode는 강력하지만 IDE는 아니고 좋은 메모장인 것 같습니다.

연결된 맥락으로 일전에 MS에서 Visual Studio for Mac을 지원 중단하고 C# Dev kit을 공식 대체로 지정해주면서 JetBrains Rider가 떡상했던 경험이 떠오르네요.

역시 IDE는 IDE인가 봅니다~ 리눅스나 MacOS에서 .NET 개발하시는 분들은 vscode보다는 속편하게 Rider쓰시는 걸로…

4 Likes