How to deploy Moleculer to AWS Lambda? #1117
-
Hi, just wondering if anyone has done Moleculer in Serverless architecture? I am wondering if anyone could walk me through how do I configure and deploy to AWS Lambda with SQS please? Thanks. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
PrefaceFirst, let's address the fact that AWS Lambda is a type of Serverless computing but is not the only type. ECS Fargate makes very quick work of deploying containerized solutions in what is essentially a serverless architecture since you are not responsible for maintaining the underlying infrastructure. Developing and containerizing Moleculer microservices is straight forward and is documented elsewhere, so let's focus on AWS Lambda as you stated in the subject of the discussion. Lambda provides us with a "Function as a Service" which, in my opinion, aligns the closest with Moleculer service actions. Given a Moleculer system, a service provides a collection of actions and event handlers to the system which may be invoked by calling (actions) or due to an event in the system. AWS Lambda supports "Applications" which essentially group functions and could be considered similar in way to the Moleculer service. Given appropriate permissions lambda functions may be invoked by other internal AWS resource (such as an EC2 instance, ECS task, another lambda, etc.) as well as external resources using the AWS SDK or CLI. Additionally, various types of triggers may be configured for lambdas from services such as API Gateway, CloudWatch, and EventBridge to name a few. The AWS services consumed to architect the solution basically handle what could be considered functionally equivalent to Moleculer service brokers. With that baseline set, I think it's important to consider why you may want to use Moleculer in AWS Lambda. If you do not have an existing Moleculer system you may be over complicating the situation when you could just as easily leave Moleculer out of the equation, implement lambda in the language of your choice, and architect function integration (etc.) using the AWS services/resources available to you. Frameworks like Serverless actually make this extremely approachable and support exposing API gateways and several other features out of the box. However, I do think there are situations where this might be useful. Exploring this concept is something that's been on my back burner for some time but I haven't gotten around to building anything. In my team's case, we have an existing Moleculer system at the core of our platform with an NATS cluster providing the system's transport. This was originally developed to avoid some level of CSP lock-in so all of our services are deployed via "packages" (see this discussion for more detail on that) which each run in highly available ECS Service Tasks. So exploring a Lambda integration for us has a very specific use case; essentially, exposing an easily authenticated/authorized (using IAM) non-HTTP interface (AWS Lambda API/SDK) for integrating pure AWS solutions with our Moleculer system. This interface could then be used primarily for concerns such as:
Given that you can simply provision the appropriate IAM permissions to anything else running virtually anywhere, it would seem unnecessary to use Moleculer in Lambda for the purposes of providing existing services with a means to "do something in AWS" as this could simply be implemented in the existing Moleculer system. In other words, Moleculer in Lambda is probably very niche and mostly about exposing the existing system's functionality to solutions running purely in AWS. AWS Solution Invokes Lambda exposing Moleculer System graph TD;
AWS_Solution-->Lambda;
Lambda-->Moleculer_Broker;
Moleculer_Broker-->Moleculer_Service;
Moleculer Service Consumes AWS graph TD;
Moleculer_Service-->AWS_SDK;
If the use case is right then definitely move ahead, but be sure you are not over complicating things trying to use something "different" just for the sake of it; think carefully about why you want to use Moleculer in Lambda. Moving forward I'm going to assume that you have an existing system that you are connecting to for integration purposes. ConfigurationThere are a few things to keep in mind as far as configuration is concerned:
LambdaWith the import { ServiceBroker } from 'moleculer';
// Note: The config file must also be ESM formatted to be loaded this way
import * as config from './moleculer.config.js';
const exampleServiceSchema = {
name: 'example-lambda-service',
version: 1,
actions: {
getFunctionEnvironment: {
handler(ctx) {
console.log(`lambda function environment details requested from ${ctx.caller}`);
return this.collectEnvironment();
}
}
},
methods: {
collectEnvironment() {
return {
// – The handler location configured on the function.
_HANDLER: process.env._HANDLER,
// – The X-Ray tracing header.
_X_AMZN_TRACE_ID: process.env._X_AMZN_TRACE_ID,
// – The AWS Region where the Lambda function is executed.
AWS_REGION: process.env.AWS_REGION,
// – The runtime identifier, prefixed by AWS_Lambda_ (for example, AWS_Lambda_java8). This environment variable
// is not defined for custom runtimes (for example, runtimes that use the provided or provided.al2 identifiers).
AWS_EXECUTION_ENV: process.env.AWS_EXECUTION_ENV,
// – The name of the function.
AWS_LAMBDA_FUNCTION_NAME: process.env.AWS_LAMBDA_FUNCTION_NAME,
// – The amount of memory available to the function in MB.
AWS_LAMBDA_FUNCTION_MEMORY_SIZE: process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE,
// – The version of the function being executed.
AWS_LAMBDA_FUNCTION_VERSION: process.env.AWS_LAMBDA_FUNCTION_VERSION,
// – The initialization type of the function, which is either on-demand or provisioned-concurrency. For
// information, see Configuring provisioned concurrency.
AWS_LAMBDA_INITIALIZATION_TYPE: process.env.AWS_LAMBDA_INITIALIZATION_TYPE,
// – The name of the Amazon CloudWatch Logs group and stream for the function.
AWS_LAMBDA_LOG_GROUP_NAME: process.env.AWS_LAMBDA_LOG_GROUP_NAME,
AWS_LAMBDA_LOG_STREAM_NAME: process.env.AWS_LAMBDA_LOG_STREAM_NAME,
// DANGER!! – The access keys obtained from the function's execution role. DANGER!!
AWS_ACCESS_KEY: process.env.AWS_ACCESS_KEY,
AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY,
AWS_SESSION_TOKEN: process.env.AWS_SESSION_TOKEN,
// – (Custom runtime) The host and port of the runtime API.
AWS_LAMBDA_RUNTIME_API: process.env.AWS_LAMBDA_RUNTIME_API,
// – The path to your Lambda function code.
LAMBDA_TASK_ROOT: process.env.LAMBDA_TASK_ROOT,
// – The path to runtime libraries.
LAMBDA_RUNTIME_DIR: process.env.LAMBDA_RUNTIME_DIR,
// – The environment's time zone (UTC). The execution environment uses NTP to synchronize the system clock.
TZ: process.env.TZ,
}
}
}
};
let broker, service;
try {
broker = new ServiceBroker(config);
service = broker.createService(exampleServiceSchema);
await broker.start();
} catch (error) {
throw error;
}
// Call service.action using an unnamed function exported as a const
export const callServiceAction = async function(event, context) {
try {
return await broker.call('service.action', {});
} catch (err) {
throw err;
}
}
// Emit an event using an arrow function exported as a const
export const emitMyEvent = async (event, context) => {
try {
return await broker.emit('my.event.topic', {});
} catch (err) {
throw err;
}
}
// Broadcast an event using an exported, asynchronous, named function
export async function broadcastMyEvent(event, context) {
try {
return await broker.broadcast('my.event.topic', {});
} catch (err) {
throw err;
}
}
// Call an action for the service created locally within the lambda execution
export async function callLocalServiceAction(event, context) {
try {
return await service.actions.getFunctionEnvironment({});
} catch (err) {
throw err;
}
} Here we have a very simple lambda function code which exposes four handlers, thus this code can be used for four different lambda functions. Each handler uses a slightly different type of function and use either the We load We then export each handler for lambda to invoke and include calls to the broker to provide a few examples. Theoretically, when invoked, a node should be created along with a service and when started, connect to your configured transport, then start the included service(s). Other services in the Moleculer system should be able to call the provided action and the lambda handlers should work as expected. The node would stay connected to the system along with any service(s) running on it until the lambda is terminated. Of course, you can look into how to keep the lambda warm which should keep the node running and connected. SQSYou also mentioned use of SQS. Given there is no transport support for SQS I can only assume you either want to trigger your lambda using SQS or consume SQS via the AWS SDK directly in your lambda function. For details on approaching the former, start with this documentation. It's pretty straight forward and highly customizable. For the latter, you simply need to amend the above code to import the For example: import { SQS } from 'aws-sdk';
const SQSClient = new SQS();
const sendMessageResponse = await SQS.sendMessage({
MessageBody: 'example',
// Assuming this is set in the lambda process environment...
QueueUrl: process.env.SQS_QUEUE_URL,
}).promise(); Perhaps you want to push messages into the queue when actions are called; simply toss the above in an action handler and populate the message with the content from the If you want the lambda to be a consumer of the queue, it's relatively straight forward to read directly from the queue using the LastlyAs I said, this is mostly just my thoughts. I have not fully tested this idea but conceptually I do believe this is accurate and ultimately possible. Take care with how you approach this, be certain that you are doing it for the right reasons, and do research to cover your bases. There is a lot more that can be done here but as with anything in the cloud, take caution not to accidentally cause unnecessary spending. Let me know if you have any follow-up questions, need clarification, etc. and happy coding! |
Beta Was this translation helpful? Give feedback.
@Susros
Preface
First, let's address the fact that AWS Lambda is a type of Serverless computing but is not the only type. ECS Fargate makes very quick work of deploying containerized solutions in what is essentially a serverless architecture since you are not responsible for maintaining the underlying infrastructure. Developing and containerizing Moleculer microservices is straight forward and is documented elsewhere, so let's focus on AWS Lambda as you stated in the subject of the discussion.
Lambda provides us with a "Function as a Service" which, in my opinion, aligns the closest with Moleculer service actions. Given a Moleculer system, a service provides a collection of actions and ev…