Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify Lambda Runtime using Runtime API #5306

Merged
merged 48 commits into from Apr 1, 2022

Conversation

dfangl
Copy link
Member

@dfangl dfangl commented Jan 19, 2022

In this PR, we will create a re-implementation of the Lambda executors, to properly support multiple architectures as well as new runtimes, with the use of the Lambda Runtime API (https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html).

This is a draft PR for the progress.

To activate the new provider add PROVIDER_OVERRIDE_LAMBDA=asf (note that it is currently not feature complete).

This PR will automatically download the specified release of https://github.com/localstack/lambda-runtime-init as "Runtime Init".

Configuration options:

  • LAMBDA_PREBUILD_IMAGES=1 (defaults to 0) toggles the building of a lambda version specific container image that includes the code and init before any invoke happens instead of copying them into the container before each invoke. This is actually fairly simillar to the AWS behavior since they also have some initial "optimization" of Lambdas when creating a function. Unfortunately it involves a significant overhead in initial total execution time (from create to an invoke result), so for many single-use lambdas this heavily reduces performance.
sequenceDiagram
    participant LS as LocalStack Endpoint
    participant RAPID as Runtime Init
    participant RIC as Runtime Interface Client
    LS-)RAPID: Start Container
    RAPID-)RIC: Start RIC
    alt Init success
        RIC->>RAPID: next()
        RAPID->>LS: Done
    else
        RIC->>RAPID: InitError
        RAPID->>LS: DoneError
    end
    loop per invocation
        LS-)RAPID: Invoke(id)

        RAPID-->>+RIC: next: Invocation(id)
        alt Invocation Success
            RIC->>RAPID: InvocationResult(id)
        else
            RIC->>RAPID: InvocationError(id)
        end
        par Rapid to LocalStack
            alt Invocation Success
                RAPID->>LS: Response(id)
            else
                RAPID->>LS: ErrorResponse(id)
            end
        and RIC to Rapid
            RIC->>-RAPID: next()
        end
    end
flowchart TD
    API[Lambda API]
    SERVICE[Lambda Service]
    REPO[Repository]
    VM[Version Manager]
    ENV[Environment]
    EXEC[Executor]
    EXEC-CONN[Executor Connection]
    subgraph CONTAINER
        RAPID
    end
    API --> SERVICE
    SERVICE --> REPO
    SERVICE --> VM
    VM --> ENV
    ENV --> EXEC
    EXEC <--> EXEC-CONN
    EXEC-CONN -- invokes --> RAPID
    RAPID -- result/status --> EXEC-CONN
    EXEC -- starts/stops --> CONTAINER

@dfangl dfangl temporarily deployed to localstack-ext-tests Jan 19, 2022 Inactive
@github-actions
Copy link

github-actions bot commented Jan 19, 2022

LocalStack integration with Pro

       3 files  ±  0         3 suites  ±0   51m 12s ⏱️ - 5m 26s
   950 tests +  7     910 ✔️ +  5  40 💤 +2  0 ±0 
1 163 runs  +18  1 095 ✔️ +11  68 💤 +7  0 ±0 

Results for commit be7b4e8. ± Comparison against base commit 7aa69c7.

♻️ This comment has been updated with latest results.

@coveralls
Copy link

coveralls commented Jan 19, 2022

Coverage Status

Coverage decreased (-4.0%) to 84.715% when pulling 8b0b0ab on lambda-runtime-unification into 4118650 on master.

@dfangl dfangl force-pushed the lambda-runtime-unification branch from 0b36615 to bda1d72 Compare Feb 1, 2022
@dfangl dfangl had a problem deploying to localstack-ext-tests Feb 1, 2022 Failure
@dfangl dfangl had a problem deploying to localstack-ext-tests Feb 2, 2022 Failure
@dfangl dfangl had a problem deploying to localstack-ext-tests Feb 2, 2022 Failure
@dominikschubert dominikschubert force-pushed the lambda-runtime-unification branch from 305edca to cc3ca2e Compare Feb 3, 2022
@dominikschubert dominikschubert temporarily deployed to localstack-ext-tests Feb 3, 2022 Inactive
@dfangl dfangl temporarily deployed to localstack-ext-tests Feb 4, 2022 Inactive
@alexrashed alexrashed added the area: asf AWS Server Framework label Feb 8, 2022
@dfangl dfangl force-pushed the lambda-runtime-unification branch from 51b9b74 to 22f2fca Compare Feb 8, 2022
@dfangl dfangl temporarily deployed to localstack-ext-tests Feb 8, 2022 Inactive
@dfangl dfangl temporarily deployed to localstack-ext-tests Feb 9, 2022 Inactive
@dfangl dfangl temporarily deployed to localstack-ext-tests Feb 9, 2022 Inactive
@dominikschubert dominikschubert temporarily deployed to localstack-ext-tests Feb 10, 2022 Inactive
@dfangl dfangl temporarily deployed to localstack-ext-tests Feb 10, 2022 Inactive
@dfangl dfangl force-pushed the lambda-runtime-unification branch from 08634c7 to 955aced Compare Feb 18, 2022
@dfangl dfangl had a problem deploying to localstack-ext-tests Feb 18, 2022 Failure
@dfangl dfangl force-pushed the lambda-runtime-unification branch from 955aced to bd36f00 Compare Feb 18, 2022
@dfangl dfangl had a problem deploying to localstack-ext-tests Feb 18, 2022 Failure
@dfangl dfangl temporarily deployed to localstack-ext-tests Feb 21, 2022 Inactive
@dfangl dfangl force-pushed the lambda-runtime-unification branch from 1b2bfa3 to da655aa Compare Feb 23, 2022
@dfangl dfangl had a problem deploying to localstack-ext-tests Feb 23, 2022 Error
@dfangl dfangl temporarily deployed to localstack-ext-tests Feb 23, 2022 Inactive
@dfangl dfangl temporarily deployed to localstack-ext-tests Feb 23, 2022 Inactive
@dfangl dfangl temporarily deployed to localstack-ext-tests Feb 23, 2022 Inactive
@dfangl dfangl temporarily deployed to localstack-ext-tests Feb 24, 2022 Inactive
@dominikschubert dominikschubert force-pushed the lambda-runtime-unification branch from 680c0e9 to bafbcc0 Compare Apr 1, 2022
@dominikschubert dominikschubert temporarily deployed to localstack-ext-tests Apr 1, 2022 Inactive
@dominikschubert dominikschubert temporarily deployed to localstack-ext-tests Apr 1, 2022 Inactive
@dominikschubert dominikschubert temporarily deployed to localstack-ext-tests Apr 1, 2022 Inactive
thrau
thrau approved these changes Apr 1, 2022
Copy link
Member

@thrau thrau left a comment

This looks fantastic guys! Really well done 🙌 Clean abstractions and mostly easy to understand. Can't wait to give this a try.

I just have a few really minor nits for this PR. :-)

localstack/config.py Show resolved Hide resolved

class InvokeSendError(Exception):
def __init__(self, invocation_id: str, payload: Optional[bytes]):
message = f"Error while trying to send invocation to RAPID for id {invocation_id}. Response: {payload}"
Copy link
Member

@thrau thrau Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible that we're printing control characters to stderr like this?

Copy link
Member Author

@dfangl dfangl Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Payload as well as invocation ids should be strings, so I see a slim chance of that happening here.

executor_endpoint = Flask(f"executor_endpoint_{self.port}")

@executor_endpoint.route("/invocations/<req_id>/response", methods=["POST"])
def invocation_response(req_id: str) -> ResponseReturnValue:
result = InvocationResult(req_id, request.data)
self.service_endpoint.invocation_result(invoke_id=req_id, invocation_result=result)
return Response(status=HTTPStatus.ACCEPTED)
Copy link
Member

@thrau thrau Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a very creative solution! i can see how flask enabled the encapsulation of the HTTP server here. i think we could probably improve the code here by using our own Router implementation together with a werkzeug server. The inline methods are clearly necessary with flask, because the method decorators are bound to the flask app being dynamically created, but we could pull them out into methods of the ExecutorEndpoint with the Router class (or even using flask blueprints)

I would generally like to separate the HTTP endpoint implementation from the Server instance that spawns the HTTP server, for better composability and separation of concerns.

Copy link
Member Author

@dfangl dfangl Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be done in a follow-up PR! 👍

def do_run(self) -> None:
endpoint = self._create_endpoint()
LOG.debug("Running executor endpoint API on %s:%s", self.host, self.port)
endpoint.run(self.host, self.port)
Copy link
Member

@thrau thrau Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're starting a new flask app for every lambda, i think this is also a good argument against flask. Using something more low-level like Router directly with werkzeug will likely reduce resource usage.

This is definitely out of scope for this revision though :-)

@dfangl dfangl had a problem deploying to localstack-ext-tests Apr 1, 2022 Failure
@dfangl dfangl had a problem deploying to localstack-ext-tests Apr 1, 2022 Failure
@dfangl dfangl temporarily deployed to localstack-ext-tests Apr 1, 2022 Inactive
@dfangl dfangl temporarily deployed to localstack-ext-tests Apr 1, 2022 Inactive
@dominikschubert dominikschubert marked this pull request as ready for review Apr 1, 2022
@dominikschubert dominikschubert requested a review from alexrashed as a code owner Apr 1, 2022
whummer
whummer approved these changes Apr 1, 2022
Copy link
Member

@whummer whummer left a comment

Fantastic set of changes, also can't wait to give this a try and see single-digit millisecond Lambda invocation times!! 🚀 😄

Agree with the comments around Flask usage for the individual executor endpoints, there we maybe have an opportunity for further improvement in future iterations.. 👍 Otherwise only two minor comments, but shouldn't hold back the merge. Great job!

qualified_arn = function_version.id.qualified_arn()
version_manager = self.lambda_version_managers.get(qualified_arn)
if version_manager:
raise Exception("Version '%s' already created", qualified_arn)
Copy link
Member

@whummer whummer Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
raise Exception("Version '%s' already created", qualified_arn)
raise Exception(f"Version '{qualified_arn}' already created")

Copy link
Member

@dominikschubert dominikschubert Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will pick these up in later stages 👍


def start(self) -> None:
try:
invocation_thread = Thread(target=self.invocation_loop)
Copy link
Member

@whummer whummer Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we could set invocation_thread.daemon = True, to ensure the thread is terminated on process teardown (probably already covered by the logic around self.shutdown_event, but generally a good pattern to be on the safe side)

localstack/services/generic_proxy.py Show resolved Hide resolved
@dominikschubert dominikschubert merged commit 0b9f90a into master Apr 1, 2022
15 checks passed
@viren-nadkarni viren-nadkarni deleted the lambda-runtime-unification branch Apr 3, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: asf AWS Server Framework
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants