본문 바로가기
AWS/Lambda

Lambda Function에서 Python용 Handler를 호출해주는 코드

by 내꿈은한량 2024. 11. 15.

Lambda Function 설정할 때 내가 만든 코드의 함수를 핸들러 항목에서 지정하면 Lambda가 호출을 해준다.

여기서 다음과 같은 호기심이 발동했다.

  • 핸들러에 지정한 함수는 누가 호출해 줄까?
  • Python이나 Node.js 같은 Interpreter 형식의 언어라면 원본 코드를 찾아볼 수 있지 않을까?

그래서 먼저, 핸들러 함수를 호출해주는 코드 위치를 찾기 위해 아래 코드를 만들어 Lambda에서 실행해 봤다. (Python용)

import json
import inspect

def lambda_handler(event, context):
    # TODO implement

    print(inspect.stack())

    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

inspect.stack() 부분이 핵심인데, 실행하면 호출 순서를 출력해 주는데, 정리하면 다음과 같다. (전체 결과는 맨 마지막 참고-1)

  1. /var/runtime/bootstrap.py
  2. /var/lang/lib/python3.13/site-packages/awslambdaric/__main__.py
  3. /var/lang/lib/python3.13/site-packages/awslambdaric/bootstrap.py
  4. /var/task/lambda_function.py

1번부터 실행이 되면서 내가 작성한 코드가 저장되는 4번 파일이 마지막이었다.

2,3번에 표시되는 package 인 awslambdaric이라는 건 찾아보니 AWS에서 이미 오픈소스로 GitHub에 공개해 놓은 "AWS Lambda Python Runtime Interface Client"라는 패키지이며, Lambda Runtime API를 Python용으로 구현해 놓은 것이다.

Lambda runtime API

내 원래 목표인 처음 시작 코드를 보기 위해 1번의 파일을 찾아 출력하도록 아래 코드를 만들어 다시 실행해 봤다.

import json
import inspect

def lambda_handler(event, context):
    # TODO implement

    with open('/var/runtime/bootstrap.py', 'r') as bootstrap_py:
        lines = bootstrap_py.readlines()

    for line in lines:
        print(line)


    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

결과는 다음과 같았다.

"""
Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
"""

import json
import logging
import os
import site
import sys
import time
import traceback
import warnings
import awslambdaric.__main__ as awslambdaricmain


def is_pythonpath_set():
    return "PYTHONPATH" in os.environ


def get_opt_site_packages_directory():
    return '/opt/python/lib/python{}.{}/site-packages'.format(sys.version_info.major, sys.version_info.minor)

def get_var_lang_site_package_directory():
    return '/var/lang/lib/python{}.{}/site-packages'.format(sys.version_info.major, sys.version_info.minor)

def get_opt_python_directory():
    return '/opt/python'


# set default sys.path for discoverability
# precedence: LAMBDA_TASK_ROOT -> /opt/python/lib/pythonN.N/site-packages -> /opt/python
def set_default_sys_path():
    if not is_pythonpath_set():
        sys.path.insert(0, get_var_lang_site_package_directory())
        sys.path.insert(0, get_opt_python_directory())
        sys.path.insert(0, get_opt_site_packages_directory())
#     'LAMBDA_TASK_ROOT' is function author's working directory
#     we add it first in order to mimic the default behavior of populating sys.path and make modules under 'LAMBDA_TASK_ROOT'
#     discoverable - https://docs.python.org/3/library/sys.html#sys.path
    sys.path.insert(0, os.environ['LAMBDA_TASK_ROOT'])


def add_default_site_directories():
#     Set 'LAMBDA_TASK_ROOT as site directory so that we are able to load all customer .pth files
    site.addsitedir(os.environ["LAMBDA_TASK_ROOT"])
    if not is_pythonpath_set():
        site.addsitedir(get_opt_site_packages_directory())
        site.addsitedir(get_opt_python_directory())

def set_default_pythonpath():
    if not is_pythonpath_set():
#         keep consistent with documentation: https://docs.aws.amazon.com/lambda/latest/dg/lambda-environment-variables.html
        os.environ["PYTHONPATH"] = os.environ["LAMBDA_RUNTIME_DIR"]


def main():
    set_default_sys_path()
    add_default_site_directories()
    set_default_pythonpath()
    awslambdaricmain.main([os.environ["LAMBDA_TASK_ROOT"], os.environ["_HANDLER"]])

if __name__ == '__main__':
    main()

이로서 궁금증이 해결되었음.. 

참고-1) inspect.stack()를 통해 출력된 내용. (보기 좋게 약간의 편집을 함)

 

[
  FrameInfo(
    frame=<frame at 0xffffa9b19630, file '/var/task/lambda_function.py', line 7, code lambda_handler>, 
    filename='/var/task/lambda_function.py', 
    lineno=7, 
    function='lambda_handler', 
    code_context=['    print(inspect.stack())\n'], 
    index=0, 
    positions=Positions(lineno=7, end_lineno=7, col_offset=10, end_col_offset=25)
  ), 

  FrameInfo(
    frame=<frame at 0xffffa9cbedc0, file '/var/lang/lib/python3.13/site-packages/awslambdaric/bootstrap.py', line 177, code handle_event_request>, 
    filename='/var/lang/lib/python3.13/site-packages/awslambdaric/bootstrap.py', 
    lineno=177, 
    function='handle_event_request', 
    code_context=['        response = request_handler(event, lambda_context)\n'], 
    index=0, 
    positions=Positions(lineno=177, end_lineno=177, col_offset=19, end_col_offset=57)
  ), 
  
  FrameInfo(
    frame=<frame at 0xffffa9b2ea40, file '/var/lang/lib/python3.13/site-packages/awslambdaric/bootstrap.py', line 523, code run>, 
    filename='/var/lang/lib/python3.13/site-packages/awslambdaric/bootstrap.py', 
    lineno=523, 
    function='run', 
    code_context=['            handle_event_request(\n'], 
    index=0, 
    positions=Positions(lineno=523, end_lineno=534, col_offset=12, end_col_offset=13)
  ), 
    
  FrameInfo(
    frame=<frame at 0xffffa9afbe60, file '/var/lang/lib/python3.13/site-packages/awslambdaric/__main__.py', line 21, code main>, 
    filename='/var/lang/lib/python3.13/site-packages/awslambdaric/__main__.py', 
    lineno=21, 
    function='main', 
    code_context=['    bootstrap.run(app_root, handler, lambda_runtime_api_addr)\n'], 
    index=0, 
    positions=Positions(lineno=21, end_lineno=21, col_offset=4, end_col_offset=61)
  ), 
      
  FrameInfo(
    frame=<frame at 0xffffa9cd1f00, file '/var/runtime/bootstrap.py', line 60, code main>, 
    filename='/var/runtime/bootstrap.py', 
    lineno=60, 
    function='main', 
    code_context=['    awslambdaricmain.main([os.environ["LAMBDA_TASK_ROOT"], os.environ["_HANDLER"]])\n'], 
    index=0, 
    positions=Positions(lineno=60, end_lineno=60, col_offset=4, end_col_offset=83)
  ), 
        
  FrameInfo(
    frame=<frame at 0xffffa9ba9380, file '/var/runtime/bootstrap.py', line 63, code <module>>, 
    filename='/var/runtime/bootstrap.py', 
    lineno=63, 
    function='<module>', 
    code_context=['    main()\n'], 
    index=0, 
    positions=Positions(lineno=63, end_lineno=63, col_offset=4, end_col_offset=10)
  )
]