distroless icon indicating copy to clipboard operation
distroless copied to clipboard

[Question] Nodejs20-debian12 with aws-lambda-ric

Open McKnacker opened this issue 11 months ago • 3 comments

Hey, we are currently running into issues while migrating onto an distroless image for our aws lambdas. I was following the docs from aws on how to use a non-AWS base image (https://docs.aws.amazon.com/lambda/latest/dg/nodejs-image.html#nodejs-image-clients).

Since the distroless image has removed package managers and everything else. NPM and NPX are not availabe. But I couldn't find any documentation what I could give as an entrypoint in the dockerfile for the aws-lambda-ric as an alternative. The documentation mentioned above expects the following entrypoint:

# Set runtime interface client as default command for the container runtime
ENTRYPOINT ["/usr/local/bin/npx", "aws-lambda-ric"]
# Pass the name of the function handler as an argument to the runtime
CMD ["index.handler"]

Do any of you have experience in what seems to be an edge case?

McKnacker avatar Jan 10 '25 14:01 McKnacker

I'm a person with very little node knowledge, but could you just run the index.js in aws-lambda-ric directly?

The ENTRYPOINT provided by distroless is node, so don't modify that

and your CMD is ["<wherever aws-lambda-ric is>/index.js", "index.handler"]

loosebazooka avatar Jan 14 '25 14:01 loosebazooka

I've played around with it, and it might not be possible with distroless because it expects /bin/sh to be able to fork a process This is the Dockerfile I've concocted

FROM gcr.io/distroless/nodejs22-debian12:latest
COPY --from=public.ecr.aws/lambda/nodejs:22 /usr/local/bin/aws-lambda-rie /usr/local/bin/aws-lambda-rie
COPY --from=public.ecr.aws/lambda/nodejs:22 /var/runtime /var/runtime
ENV LAMBDA_TASK_ROOT=/var/task
WORKDIR /var/task

COPY index.js ${LAMBDA_TASK_ROOT}
ENTRYPOINT [ "/usr/local/bin/aws-lambda-rie" ]
CMD [ "/nodejs/bin/node", "/var/runtime/index.mjs" "index.handler" ]

Where index.js is

exports.handler = async (event) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify('Hello from Distroless Lambda!'),
  };
  return response;
};

And running it as

docker run --rm -it -p 8080:8080 lambda

And I get the following logs

15 Jan 2025 23:12:54,738 [INFO] (rapid) exec '/bin/sh' (cwd=/var/task, handler=[ "/nodejs/bin/node", "/var/runtime/index.mjs" "index.handler" ])
START RequestId: ff3314bb-03a1-4640-ad58-e4d1a858f824 Version: $LATEST
15 Jan 2025 23:12:57,851 [INFO] (rapid) INIT START(type: on-demand, phase: init)
15 Jan 2025 23:12:57,851 [INFO] (rapid) The extension's directory "/opt/extensions" does not exist, assuming no extensions to be loaded.
15 Jan 2025 23:12:57,851 [INFO] (rapid) Starting runtime without AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN , Expected?: false
15 Jan 2025 23:12:57,851 [WARNING] (rapid) First fatal error stored in appctx: Runtime.InvalidEntrypoint
15 Jan 2025 23:12:57,851 [INFO] (rapid) INIT RTDONE(status: error)
15 Jan 2025 23:12:57,851 [INFO] (rapid) INIT REPORT(durationMs: 0.527000)
15 Jan 2025 23:12:57,851 [ERROR] (rapid) Init failed error=fork/exec /bin/sh: no such file or directory InvokeID=
15 Jan 2025 23:12:57,851 [WARNING] (rapid) Shutdown initiated: spindown
15 Jan 2025 23:12:57,851 [INFO] (rapid) Waiting for runtime domain processes termination
15 Jan 2025 23:12:57,851 [INFO] (rapid) INIT START(type: on-demand, phase: invoke)
15 Jan 2025 23:12:57,851 [INFO] (rapid) The extension's directory "/opt/extensions" does not exist, assuming no extensions to be loaded.
15 Jan 2025 23:12:57,851 [INFO] (rapid) Starting runtime without AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN , Expected?: false
15 Jan 2025 23:12:57,852 [WARNING] (rapid) First fatal error stored in appctx: Runtime.InvalidEntrypoint
15 Jan 2025 23:12:57,852 [INFO] (rapid) INIT RTDONE(status: error)
15 Jan 2025 23:12:57,852 [INFO] (rapid) INIT REPORT(durationMs: 0.909000)
15 Jan 2025 23:12:57,852 [INFO] (rapid) INVOKE START(requestId: edcda402-6476-44be-99cc-57d4d12d17f9)
15 Jan 2025 23:12:57,852 [ERROR] (rapid) Invoke failed error=fork/exec /bin/sh: no such file or directory InvokeID=edcda402-6476-44be-99cc-57d4d12d17f9
15 Jan 2025 23:12:57,852 [ERROR] (rapid) Invoke DONE failed: Runtime.InvalidEntrypoint
15 Jan 2025 23:12:57,852 [WARNING] (rapid) Reset initiated: ReleaseFail
15 Jan 2025 23:12:57,852 [INFO] (rapid) Waiting for runtime domain processes termination

If I somehow manage to sneak in /bin/sh, the following error comes

15 Jan 2025 23:09:28,724 [INFO] (rapid) exec '/bin/sh' (cwd=/var/task, handler=[ "/nodejs/bin/node", "/var/runtime/index.mjs" "index.handler" ])
START RequestId: 6bc08f86-e292-4168-b43d-41a00dd0bc3c Version: $LATEST
15 Jan 2025 23:09:33,008 [INFO] (rapid) INIT START(type: on-demand, phase: init)
15 Jan 2025 23:09:33,008 [INFO] (rapid) The extension's directory "/opt/extensions" does not exist, assuming no extensions to be loaded.
15 Jan 2025 23:09:33,008 [INFO] (rapid) Starting runtime without AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN , Expected?: false
sh: /var/runtime/index.mjs: unknown operand
15 Jan 2025 23:09:33,009 [WARNING] (rapid) First fatal error stored in appctx: Runtime.ExitError
15 Jan 2025 23:09:33,009 [WARNING] (rapid) Process runtime-1 exited: exit status 2
15 Jan 2025 23:09:33,009 [INFO] (rapid) INIT RTDONE(status: error)
15 Jan 2025 23:09:33,009 [INFO] (rapid) INIT REPORT(durationMs: 1.441000)
15 Jan 2025 23:09:33,009 [ERROR] (rapid) Init failed error=Runtime exited with error: exit status 2 InvokeID=
15 Jan 2025 23:09:33,009 [WARNING] (rapid) Shutdown initiated: spindown
15 Jan 2025 23:09:33,009 [INFO] (rapid) Waiting for runtime domain processes termination
15 Jan 2025 23:09:33,009 [INFO] (rapid) INIT START(type: on-demand, phase: invoke)
15 Jan 2025 23:09:33,009 [INFO] (rapid) The extension's directory "/opt/extensions" does not exist, assuming no extensions to be loaded.
15 Jan 2025 23:09:33,009 [INFO] (rapid) INIT REPORT(durationMs: 0.267000)
15 Jan 2025 23:09:33,009 [INFO] (rapid) INVOKE START(requestId: d7a3f34f-d788-4801-a36b-2561b6028c22)
15 Jan 2025 23:09:33,009 [ERROR] (rapid) Invoke failed error=Runtime exited with error: exit status 2 InvokeID=d7a3f34f-d788-4801-a36b-2561b6028c22
15 Jan 2025 23:09:33,009 [ERROR] (rapid) Invoke DONE failed: Sandbox.Failure
15 Jan 2025 23:09:33,009 [WARNING] (rapid) Reset initiated: ReleaseFail
15 Jan 2025 23:09:33,009 [INFO] (rapid) Waiting for runtime domain processes termination

If I use the :debug variant and run this to enter the shell

docker run --rm -it -p 8080:8080 --entrypoint /busybox/sh lambda

And then run this inside the container

/usr/local/bin/aws-lambda-rie /nodejs/bin/node /var/runtime/index.mjs index.handler

I can actually curl it and get a response (takes a few tries, not sure if that's how it works, not familiar with AWS lambda).

❯ curl "http://localhost:8080/2015-03-31/functions/function/invocations" -d '{}'
{"statusCode":200,"body":"\"Hello from Distroless Lambda!\""}

omBratteng avatar Jan 16 '25 00:01 omBratteng

With some extra digging, I've come up with this

FROM gcr.io/distroless/nodejs22-debian12:debug
COPY --from=public.ecr.aws/lambda/nodejs:22 /usr/local/bin/aws-lambda-rie /usr/local/bin/aws-lambda-rie
COPY --from=public.ecr.aws/lambda/nodejs:22 /var/runtime /var/runtime
ENV LAMBDA_TASK_ROOT=/var/task
WORKDIR /var/task

COPY index.js ${LAMBDA_TASK_ROOT}
COPY start.sh ${LAMBDA_TASK_ROOT}

ENTRYPOINT [ "/busybox/sh" ]
CMD [ "/var/task/start.sh" ]

start.sh file

/busybox/sh -c "/usr/local/bin/aws-lambda-rie /nodejs/bin/node /var/runtime/index.mjs index.handler"

I can now start the container

docker run --rm -it -p 8080:8080 lambda

And curl it

❯ curl "http://localhost:8080/2015-03-31/functions/function/invocations" -d '{}'
{"statusCode":200,"body":"\"Hello from Distroless Lambda!\""}

omBratteng avatar Jan 16 '25 00:01 omBratteng