Lambda

글쓴이 상배 윤 날짜

REST API 서버를 만들어봤다면, 각 REST API는 “하나의 함수에 대응” 한다는 것을 알 수 있다. 유저의 요청은 여러 개의 함수 중에서 하나의 함수로 전달된다. 이 함수들은 HTTP 요청을 핸들링 한다라는 의미로 HTTP Handler 라고 부른다.

HTTP 클라이언트의 요청을 애플리케이션 서버가 처리하는 방식은 대략 아래와 같다.

  1.  클라이언트가 HTTP 요청을 전송한다.
  2. HTTP Server가 요청을 받는다.
  3. HTTP Server는 웹 애플리케이션 서버로 보낸다.
  4. 애플리케이션 서버의 미들웨어가 인증/권한/로깅/CORS(HTTP 접근제어)/CSRF(사이트간 요청 위조)등 공통작업을 수행한다.
  5. 마지막으로 HTTP Handler에 요청이 전달되서 처리된다.
  6. 처리 결과는 HTTP 응답 형태로 미들웨어를 거쳐서 클라이언트까지 전달된다.

그렇다면 HTTP Server, 미들웨어, HTTP Handler를 실행하기 위한 런타임(RunTime)을 클라우드에서 모두 제공을 하면, 개발자는 펑션만 만들면 될 것이다. 이러한 개념을 서비스로 만든게 AWS Lambda다.

개발자는 운영체제 설정, 웹 서버 설정, 미들웨어 개발 등을 신경 쓰지 않고 오로지 코드만 개발하면 된다. EC2, ECS와 같은 서버 자원이 전혀 필요하지 않기 때문에 서버리스(Serverless) 애플리케이션을 개발하기 위한 주요 툴로 사용한다.

여기에서는 웹 애플리케이션 서버를 중심으로 설명했지만, 람다(Lambda)는 이벤트를 발생하는 모든 영역(이벤트 소스)에서(HTTP 요청도 이벤트라고 생각 할 수 있다.) 사용 할 수 있다. 예를 들어 DynamoDB에 새로운 레코드가 생성되면 람다를 실행해서 처리하게 한다거나, S3에 파일이 올라오면 처리를 하고 그 결과를 저장하는 등의 작업을 할 수 있다.

이벤트 소스

AWS 람다는 이벤트에 대한 응답으로 코드를 실행하는 서버리스 컴퓨팅 서비스다. 사용자는 서버 용량을 산정하고 전개하기 위한 고민을 할 필요가 없다. 코드를 실행하는데 필요한 CPU, 메모리 용량을 설정하면 함수를 호출하는데 걸린 시간(100밀리초 단위) 만큼 비용을 지불하면 된다. 하루 몇 개의 요청부터 초당 수백/수천개의 요청에 이르기까지 쉽게 확장될 수 있다.

AWS 람다가 처리 할 수 있는 이벤트 소스들은 아래와 같다.

  • Amazon S3
  • Amazon DynamoDB
  • Amazon Kinesis Data Streams
  • Amazon Simple Notification Service
  • Amazon Simple Email Service
  • Amazon Simple Queue Service
  • Amazon Cognito
  • AWS CloudFormation
  • Amazon CloudWatch Logs & 이벤트
  • AWS CodeCommit
  • AWS Config
  • Amazon Alexa
  • Amazon Lex
  • Amazon API Gateway
  • AWS IoT 버튼
  • Amazon CloudFront
  • amazon Dinesis data Firehose

이들 이벤트 소스로 부터 이벤트가 발생하면, 이 정보를 읽어서 처리하는 코드를 실행하는게 람다의 정체다.

람다의 장점

유저는 코드만 책임지면 된다. 실행되는 코드 외에 다른 부분들은 AWS에 위임함으로써 서비스의 복잡도를 크게 낮출 수 있다. 대신 운영체제에 대한 설정, 언어 실행 시간, 디스크, 메모리, 기타 다른 소프트웨어들을 세밀하게 설정해서 얻을 수 있는 기능/성능 상의 잇점은 희생해야 할 수도 있다.

람다는 이벤트를 기반으로 비교적 짧은 시간에 휘발성으로 작동하는 워크로드를 목적으로 만들어졌다. 요청당 최대 처리 시간이 300초로 제한된다. 또한 프로세스와 스레드 수(1,024), 이벤트의 본문 크기(128KB), 디스크 공간(512 MB), 파일 디스크립션 수(1,024) 등의 제한이 있다. 자세한 제한 사항은 AWS Lambda 제한 문서를 참고하자.

EC2 기반으로 직접 구성 할 경우, 사용자는 선택의 폭이 넓어진다. 대신 용량 산정, 성능 모니터링, 내 결함성을 위한 가용 영역의 설정, 오토 스케일링, 보안, 로깅 등에 대한 많은 책임을 가지게 된다.

지원하는 언어

AWS Lambda는 Node.js(Javascript), Python, Java(java 8 호환), C#(NET Core), Go 언어를 지원한다. 코드는 표준라이브러리 뿐만 아니라 다른 라이브러리도 포함 할 수 있다. 여기에서는 python 언어로 람다 서비스를 만들어 볼 계획이다.

서비스 시나리오 예제

람다, API Gateway, DyanmoDB를 이용해서 ToDo List 서비스를 만들어 보겠다. 서비스 구조는 아래와 같다.

람다는 다양한 애플리케이션을 개발 할 수 있지만, 아무래도 HTTP 기반의 애플리케이션 기반에 널리 사용 한다. 앞서 언급했듯이 람다는 순수한 함수이기 때문에 앞단에서 HTTP 요청을 처리해서 원하는 람다를 호출하는 미들웨어 역할을 하는 소프트웨어가 필요하다.

Amazon API Gateway를 이용해서 미들웨어 역할을 하는 소프트웨어 레이어를 만들 수 있다. 유저 요청을 받은 API Gatway는 HTTP 메서드(Method)와 URL을 읽어서, 해당 요청을 처리할 람다 함수를 호출 할 수 있다. 예를 들어 GET /todos/username 요청은 DynamDB로 부터 ToDO 목록을 읽어오는 람다함수로 보내는 식이다.

람다 함수는 DynamoDB에 접근을 할 수 있어야 하는데, IAM 롤을 만들어야 한다. IAM 롤은 여기에서 다루지 않는다.

나는 두 개의 API를 만들고, 각 API가 호출할 람다함수를 만들려 한다.

  1. GET /todos/username : username의 ToDo 목록을 가져온다.
  2. POST /todos/username : username을 가지는 유저의 ToDo 목록에 아이템을 추가한다.

다만 이 포스트는 람다의 기본 사용법과 특징을 살펴보는게 목적이므로 DynamoDB를 엑세스하는 코드를 만들지는 않을 것이다. 그냥 “Hello username” 을 출력하는 간단한 코드를 만들 것이다.

람다 코드 개발

AWS 람다 설정

람다 대시보드로 이동해서 Create a function 을 클릭한다.

3가지 방법 중 하나를 선택해서 람다를 등록 할 수 있다.

  1.  Author from scratch : 직접 한 땀, 한 땀 람다를 설정한다.
  2. Blueprints : 미리 만들어둔 설계도를 이용해서 람다를 등록 할 수 있다. 예를 들어서 dynamodb의 update 이벤트를 받아서 작동하는 람다 함수를 만들고 싶다면 dynamodb-process-stream-python blueprint 를 이용해서 시간을 절약 할 수 있다.
  3. AWS Serverless Application Repository : 다른 사용자가 만든 람다를 가져다가 (약간 수정해서) 사용 할 수 있다. s3 파일 업로더, 이미지 리사이즈, cloudwatch 알람을 slack으로 보내는 람다 등, 수백가지의 유용한 람다가 저장돼 있다. 일종의 라이브러리라고 보면 된다.

람다 사용 법을 익히는게 목적이므로 Author from scratch로 직접 만들어보기로 했다.

  • Name : 람다 함수 이름
  • Runtime : 람다 함수를 실행할 런타임
  • Role : 람다 함수에 적용할 IAM 롤. AWS의 자원들을 사용할 거라면 Role 설정이 필요하다.

이제 Designer를 이용해서 mytodo 람다 함수를 디자인 하면 된다. AWS 람다는 “이벤트를 받아서 처리하는” 이벤트 드리븐 아키텍처(Event-driven architecture)모델을 따르고 있다. 따라서 이벤트 소스가 람다 함수를 디자인하는 핵심요소가 된다. Designer 탭에는 이벤트를 발생하는 AWS의 서비스 목록이 있다. mytodo 람다 함수는 Application Load Balancer를 이벤트 소스로 하기로 했다. Application Load Balancer는 2018년 11월 쯤에 이벤트 소스로 추가 됐다. Application Load Balancer를 만들 때, 리스터가 호출할 타겟그룹을 설정하는데, 타겟그룹의 타입을 lambda로 하고, mytodo를 선택하면 된다.

이벤트 소스로 Application Load Balancer이 등록된 걸 확인 할 수 있다.

AWS는 온라인에서 코드를 개발하고 테스트 할 수 있는 클라우드 에디터를 제공한다. Designer 아래에 보면 클라우드 에디터가 있는데, 여기에서 코드를 만들고 올리기로 했다. S3나 zip 파일로 코드를 올릴 수 있고, 실제 환경이라면 이렇게 해야 겠지만 테스트가 목적이니 온라인 에디터를 이용하기로 했다.

import json

def lambda_handler(event, context):
    response = {
        "statusCode": 200,
        "statusDescription": "200 OK",
        "isBase64Encoded": False,
        "headers": {
        "Content-Type": "text/html; charset=utf-8"
        }
    }
    response['body'] = "Hello World"
    return response

AWS Lambda는 lambda_handler를 호출해서 실행한다. Application Load Balancer로 부터 넘어오는 HTTP 요청은 event 파라메터로 넘긴다. event 파라메터 값을 출력하도록 코드를 수정했다.

import json

def lambda_handler(event, context):
    print("Received event: " + json.dumps(event, indent=2))
    response = {
        "statusCode": 200,
        "statusDescription": "200 OK",
        "isBase64Encoded": False,
        "headers": {
        "Content-Type": "text/html; charset=utf-8"
        }
    }
    response['body'] = "Hello World"
    return response

이제 테스트를 해보자. 람다 설정 화면 윗 부분을 보면 “Configure test event”가 있다. 클릭하면 아래와 같이 이벤트 데이터를 편집할 수 있는 창이 만들어진다.

개발자는 하나 이상의 event 를 만들어서 테스트를 할 수 있다. 간단한 json 데이터를 만든 다음 “test”라는 이름으로 저장 했다. 이제 “test” 버튼을 눌러서 테스트를 할 수 있다.

test 이벤트로 람다를 호출했을 때, 어떤 일이 일어나는지 확인 할 수 있다. 파이선에서 print로 표준출력한 데이터들은 “로그”로 출력된다는 걸 알 수 있다. 현재 로그는 CloudWatch Log로 출력된다. S3로 저장하고 싶다면, Designer 에서 S3를 추가하면 된다.

모니터 & 로깅

람다 대시보드의 Monitoring 탭을 클릭해서 람다 상태를 모니터링 할 수 있다.

View logs in CloudWatch를 클릭하면, 표준출력한 로그 메시지를 확인 할 수 있다.

로드밸런서 설정 정보 확인

이벤트 소스인 Application Load Balancer의 설정을 살펴보자. 내가 만든 mytodolist-lb 의 정보를 가져왔다.

$ aws elbv2 describe-load-balancers --names mytodolist-lb
{
    "LoadBalancers": [
        {
            "IpAddressType": "ipv4", 
            "VpcId": "vpc-0700d43bd5ba9b0a4", 
            "LoadBalancerArn": "arn:aws:elasticloadbalancing:ap-northeast-2:522373083963:loadbalancer/app/mytodolist-lb/7f763eb84ace6b63", 
            "State": {
                "Code": "active"
            }, 
            "DNSName": "mytodolist-lb-1391392157.ap-northeast-2.elb.amazonaws.com", 
            "SecurityGroups": [
                "sg-0ca9d5ae536f3fe54"
            ], 
            "LoadBalancerName": "mytodolist-lb", 
            "CreatedTime": "2018-12-05T14:29:23.140Z", 
            "Scheme": "internet-facing", 
            "Type": "application", 
            "CanonicalHostedZoneId": "ZWKZPGTI48KDX", 
            "AvailabilityZones": [
                {
                    "SubnetId": "subnet-096df9942bd02e1b6", 
                    "ZoneName": "ap-northeast-2c"
                }, 
                {
                    "SubnetId": "subnet-0d2c643a8c5e17355", 
                    "ZoneName": "ap-northeast-2a"
                }
            ]
        }
    ]
}
  • LoadBalancerArn : AWS는 ARN(AWS Resource Name)를 이용해서 클라우드 자원을 식별한다.
  • LoadBalancerName : 로드밸런서의 이름
  • Scheme : 인터넷 접근을 허용해야 하므로 internet-facing를 선택했다.

이제 LoadBalancerArn을 이용해서 리스너 정보를 읽을 수 있다.

$ aws elbv2 describe-listeners --load-balancer-arn arn:aws:elasticloadbalancing:ap-northeast-2:522373083963:loadbalancer/app/mytodolist-lb/7f763eb84ace6b63
{
    "Listeners": [
        {
            "Protocol": "HTTP", 
            "DefaultActions": [
                {
                    "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-2:522373083963:targetgroup/todolist-target/7415652fef2f8dff", 
                    "Type": "forward"
                }
            ], 
            "LoadBalancerArn": "arn:aws:elasticloadbalancing:ap-northeast-2:522373083963:loadbalancer/app/mytodolist-lb/7f763eb84ace6b63", 
            "Port": 80, 
            "ListenerArn": "arn:aws:elasticloadbalancing:ap-northeast-2:522373083963:listener/app/mytodolist-lb/7f763eb84ace6b63/506b843f08d0e4c6"
        }
    ]
}
  • Protocol : 리스너의 프로토콜
  • Port : 80번포트로의 연결을 허용하고 있다.
  • DefaultActions : todolist-target 으로 forward 하고 있음을 알 수 있다.

타겟그룹 todolist-target의 정보를 읽어보자.

$ aws elbv2 describe-target-groups --name todolist-target
{
    "TargetGroups": [
        {
            "HealthCheckPath": "/", 
            "HealthCheckIntervalSeconds": 35, 
            "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-2:522373083963:targetgroup/todolist-target/7415652fef2f8dff", 
            "TargetType": "lambda", 
            "Matcher": {
                "HttpCode": "200"
            }, 
            "LoadBalancerArns": [
                "arn:aws:elasticloadbalancing:ap-northeast-2:522373083963:loadbalancer/app/mytodolist-lb/7f763eb84ace6b63"
            ], 
            "HealthyThresholdCount": 3, 
            "HealthCheckTimeoutSeconds": 30, 
            "HealthCheckEnabled": true, 
            "UnhealthyThresholdCount": 2, 
            "TargetGroupName": "todolist-target"
        }
    ]
}

타겟그룹의 상세 정보를 확인 할 수 있다. 지금 가장 중요한 정보는 TargetType 인데, lambda로 설정된걸 확인 할 수 있다.

람다 코드의 배포

4가지 방법으로 람다 코드를 배포 할 수 있다.

  • CodePipeline
  • CodeBuild
  • AWS CloudFormation
  • CodeDeploy

이들 AWS 서비스를 이용해서 람다 코드를 배포하는 것은 별도의 포스트에서 자세히 다루도록 하겠다.

AWS 람다 주의 사항

함수 인스턴스 재사용

람다는 컨테이너 위에서 실행된다. 단지 눈에 보이지 않을 뿐이다. 컨테이너를 실행 할 때는 시간이 걸리기 때문에, 성능 향상을 위해서 한번 실행한 인스턴스를 일정 시간 보존해서 재 사용한다. 어쨋든 Lambda 함수가 처음 호출될 때나 업데이트 될 때는 지연시간이 생길 수 있다. 아래 그림은 람다의 실행을 묘사하고 있다.


람다를 등록하고 처음 호출하면, AWS는 코드를 다운로드하고 컨테이너를 셋업하는 등의 작업을 수행한다. 이러한 초기화 작업 때문에 지연시간이 생기는데, 이를 cold start 라고 한다. 일단 실행되고 나면, 이 후로는 코드만 실행하며 이를 warm start 라고 한다. 마지막 람다가 실행되고 난 후 5분 동안은 컨테이너를 유지하기 때문에 빠르게 시작될 것이다.

람다로 유저 요청을 직접 처리하고 있을 경우 cold start는 문제가 될 수 있다. 이 문제를 해결하기 위한 가장 간단한 방법은 5분 주기로 람다를 호출하는 거다. ELB로 람다를 호출 할 경우 헬스체크(health check)주기를 조절하는 것으로 cold start 문제를 해결 할 수 있다. 비용이 추가되기는 하지만 어차피 주기적으로 API를 모니터링 하겠다라는 관점에서 보자면, 애초에 지불해야 되는 비용이라고 생각할 수 있다. HBSmith 의 API 모니터링 서비스를 선택하는 것도 좋은 방법이다.

CloudWatch Event Trigger 기능을 이용 주기적으로(Schedule) 이벤트를 실행해서 Lambda 를 warm 상태로 유지할 수 있다.

VPC내 자원 접근

AWS 람다는 VPC 바깥에 있는 AWS 공통 클러스터에서 실행된다. 람다는 VPC 내부의 자원(RDS 같은)에 접근하기 위해서, 접근해야 할 자원이 있는 서브넷에 연결되는 ENI(Elastic Network Interfaces)를 만들고 사설 IP를 할당 한다.

ENI 인터페이스를 설정해야 하기 때문에 cold start에 의한 지연시간이 더 길어질 수 있다. 만약 subnet에 사용 할 수 있는 private ip가 없다면 람다가 제대로 작동하지 않을 수 도 있다. 뭐 그냥 람다 전용의 subnet을 만들면 별 문제 없을 것이다.


댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다

Bitnami