Open ejizba opened 6 months ago
Originally posted by @edgehero in https://github.com/Azure/azure-functions-nodejs-library/issues/201#issuecomment-2117810294
Do you know any workarounds? because i'm struggling too, not using this libary and using function.json brings in a whole batch of new problems ( no job functions founds etc ) it does not seem that microsoft takes this major bug seriously which makes using azure for serious projects not a option
Another way using bundling like esbuild, I've already added the package suggested above @azure/functions-core, as the external dependency, however, deploying to Azure, it'still got the error, I don't have any error right now, or you can see in issue https://github.com/Azure/azure-functions-nodejs-library/issues/256#issue-2292563826
I've give up on monorepo or bunding Azure Function node.js runtime. so, if someone has error message please provide some error, it might be benefit.
@mildronize please expand on the exact error you're getting after you list the core package as external. For example, you could provide a sample app that hits this problem, or follow these instructions to share your app name and time of issue.
@ejizba Thank you so much for reorganizing into the proper place. However, I am curious about monorepo & bundling, and it's quite challenging for me.
I tried to reproduce the error I encountered earlier (I think since the v4 alpha tag). It has been quite a while, so I don't remember exactly what was going on at that time.
Going back to my initial discussion about how to support monorepo, as mentioned in the issue description above, publishing a monorepo without bundling is quite challenging for me.
Today, I tested the esbuild bundling tool with a standalone setup (without monorepo yet), and I was surprised that everything worked without any errors on Azure at runtime 🎉. I think if I can bundle it, the monorepo should also be possible to work with.
I've attached my project setup used for the esbuild bundle: https://github.com/mildronize/az-func-nodejs-v4-monorepo/tree/89f03d95ae51cae1cad124dfb4baad3200868726/projects/standalone-bundle
However, I'll test on 2 kinds of monorepo: Nx-styled and workspace style (like turborepo/npm/yarn/pnpm). I'll let you know later. In the meantime, I'll try to find which use cases might cause runtime errors.
@ejizba Thank you so much for reorganizing into the proper place. However, I am curious about monorepo & bundling, and it's quite challenging for me.
I tried to reproduce the error I encountered earlier (I think since the v4 alpha tag). It has been quite a while, so I don't remember exactly what was going on at that time.
Going back to my initial discussion about how to support monorepo, as mentioned in the issue description above, publishing a monorepo without bundling is quite challenging for me.
Today, I tested the esbuild bundling tool with a standalone setup (without monorepo yet), and I was surprised that everything worked without any errors on Azure at runtime 🎉. I think if I can bundle it, the monorepo should also be possible to work with.
I've attached my project setup used for the esbuild bundle: https://github.com/mildronize/az-func-nodejs-v4-monorepo/tree/89f03d95ae51cae1cad124dfb4baad3200868726/projects/standalone-bundle
However, I'll test on 2 kinds of monorepo: Nx-styled and workspace style (like turborepo/npm/yarn/pnpm). I'll let you know later. In the meantime, I'll try to find which use cases might cause runtime errors.
In your project you set "@azure/functions-core" as external, so you exclude this from the bundling process. when deploying this into blob storage and creating a azure function from this blob code. are the function http triggers stil runnable? for me they do not show up. meaning, "@azure/functions-core" can not be excluded from the bundling process
@edgehero Nice test cases, i’ll check with blob storage trigger or input/output.
@edgehero Nice test cases, i’ll check with blob storage trigger or input/output.
@mildronize wondering if that works for you. because for me it does not
@edgehero (the code has been tested)) I've been test on http trigger with blob input/output that azure functions on azure storage blob with consumption plan (You can see the create infra script, deploy script in my repo), it's work me.
I've deploy into Azure Functions URL: https://msdocs-serverless-function-12342.azurewebsites.net I'll keep this in a couple day, then I'll destroy.
However, I'll test with monorepo for my real world use cases, I'll let you know later. @ejizba
P.S. I've capture the wrong code, here is the blob code run
import { app, input } from "@azure/functions";
const blobInput = input.storageBlob({
connection: "AzureWebJobsStorage",
path: "demo-input/xxx.txt",
});
const blobOutput = input.storageBlob({
connection: "AzureWebJobsStorage",
path: "demo-output/xxx-{rand-guid}.txt",
});
export const copyBlob = app.http("copyBlob", {
methods: ["GET"],
authLevel: "function",
extraInputs: [blobInput],
extraOutputs: [blobOutput],
handler: async (request, context) => {
context.log(`Http function processed request for url "${request.url}"`);
const blobData = context.extraInputs.get(blobInput);
context.log(`Blob data: ${blobData}`);
context.extraOutputs.set(blobOutput, blobData);
return { body: "Copy Blob Completed" };
},
});
@edgehero (the code has been tested)) I've been test on http trigger with blob input/output that azure functions on azure storage blob with consumption plan (You can see the create infra script, deploy script in my repo), it's work me.
I've deploy into Azure Functions URL: https://msdocs-serverless-function-12342.azurewebsites.net I'll keep this in a couple day, then I'll destroy.
However, I'll test with monorepo for my real world use cases, I'll let you know later. @ejizba
P.S. I've capture the wrong code, here is the blob code run
import { app, input } from "@azure/functions"; const blobInput = input.storageBlob({ connection: "AzureWebJobsStorage", path: "demo-input/xxx.txt", }); const blobOutput = input.storageBlob({ connection: "AzureWebJobsStorage", path: "demo-output/xxx-{rand-guid}.txt", }); export const copyBlob = app.http("copyBlob", { methods: ["GET"], authLevel: "function", extraInputs: [blobInput], extraOutputs: [blobOutput], handler: async (request, context) => { context.log(`Http function processed request for url "${request.url}"`); const blobData = context.extraInputs.get(blobInput); context.log(`Blob data: ${blobData}`); context.extraOutputs.set(blobOutput, blobData); return { body: "Copy Blob Completed" }; }, });
thanks for documentation, really appreciate this :) , trying to set something simular up with pulumi, nx and esbuild :)
tried every step correctly and still do not get any functions in azure. everything works locally but on azure itself no functions get shown
@edgehero Could you please share the error log on App Insights (Log Analytic Workspace)
can not find anything wierd there. the files in the function app service editor are there and the main.js is readable and visible in the dist folder.
but still no functions are found by azure function
@edgehero Can I see package.json, index.ts
package.json was the cause :) for some reason you need to add "main": "dist/main.js", so the functions show up now. yet they are not runnable. did you get past this part? @mildronize
Yes, you need to setup the entrypoint "main": "dist/main.js"
in package.json for Azure Function Host Runtime. The UI that you've captured cannot debug the function host run,
You need to setup host.json
, logLevel: Trace
for logging into Azure App Insights,
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
},
"logLevel": {
"default": "Trace"
}
}
}
In order to debug Azure Function Host Runtime use to open Azure Log Analytics Workspace from Azure App Insights and open the table trace
, you will see azure functions host runtime log.
Getting no logs, can not test the function or even get the function url because that ends in a internalserver error
@mildronize could you spot any errors in this infra configurations? i use the framework pulumi to create the infra, but it pretty much does the same as your script.
`import as pulumi from "@pulumi/pulumi"; import as azure from "@pulumi/azure-native";
// Import the program's configuration settings. const config = new pulumi.Config(); const appPath = config.get("appPath") || "./apps/mqtt-broker";
// Create a resource group for the website. const resourceGroup = new azure.resources.ResourceGroup("rob-group", { location: "WestEurope", });
// Create a blob storage account. const account = new azure.storage.StorageAccount("robaccount", { resourceGroupName: resourceGroup.name, kind: azure.storage.Kind.StorageV2, sku: { name: azure.storage.SkuName.Standard_LRS, }, }, { dependsOn: [resourceGroup] });
// Create a storage container for the serverless app. const appContainer = new azure.storage.BlobContainer("rob-app-container", { accountName: account.name, resourceGroupName: resourceGroup.name, publicAccess: azure.storage.PublicAccess.None, }, { dependsOn: [account] });
// Upload the serverless app to the storage container. const appBlob = new azure.storage.Blob("rob-app-blob", { accountName: account.name, resourceGroupName: resourceGroup.name, containerName: appContainer.name, source: new pulumi.asset.FileArchive(appPath), }, { dependsOn: [appContainer] });
// Create a shared access signature to give the Function App access to the code.
const signature = azure.storage.listStorageAccountServiceSASOutput({
resourceGroupName: resourceGroup.name,
accountName: account.name,
protocols: azure.storage.HttpProtocol.Https,
sharedAccessStartTime: "2022-01-01",
sharedAccessExpiryTime: "2030-01-01",
resource: azure.storage.SignedResource.C,
permissions: azure.storage.Permissions.R,
contentType: "application/json",
cacheControl: "max-age=5",
contentDisposition: "inline",
contentEncoding: "deflate",
canonicalizedResource: pulumi.interpolate/blob/${account.name}/${appContainer.name}
,
});
// Create an App Service plan for the Function App. const plan = new azure.web.AppServicePlan("plan", { resourceGroupName: resourceGroup.name, sku: { name: "Y1", tier: "Dynamic", }, }, { dependsOn: [appContainer] });
const dummyVariable = new Date().toISOString();
// Create the Function App.
const functionApp = new azure.web.WebApp("rob-function-app", {
resourceGroupName: resourceGroup.name,
serverFarmId: plan.id,
kind: "FunctionApp",
siteConfig: {
appSettings: [
{
name: "FUNCTIONS_WORKER_RUNTIME",
value: "node",
},
{
name: "WEBSITE_NODE_DEFAULT_VERSION",
value: "~20",
},
{
name: "FUNCTIONS_EXTENSION_VERSION",
value: "~4",
},
{
name: "WEBSITE_RUN_FROM_PACKAGE",
value: pulumi.all([account.name, appContainer.name, appBlob.name, signature])
.apply(([accountName, containerName, blobName, signature]) => https://${accountName}.blob.core.windows.net/${containerName}/${blobName}?${signature.serviceSasToken}
),
},
{
name: "UPDATE_HASH", // This is a dummy variable to trigger an update
value: dummyVariable,
},
],
cors: {
allowedOrigins: [
"*"
],
},
},
}, { dependsOn: [appBlob, plan] });
export const apiURL = pulumi.interpolatehttps://${functionApp.defaultHostName}/api
;`
@edgehero, I believe we need to break down the testing to identify the root cause.
The problem might arise from:
However, focusing specifically on the issue of using a monorepo with a bundling method, I suggest testing by creating Azure Functions on the Azure Portal and then publishing the dist code via the official deployment CLI like func azure functionapp publish <your-func-name>
.
From my investigation, when you use func azure functionapp publish ...
, it automatically syncs the triggers to Azure Functions on the Azure Cloud infrastructure. This command performs a couple of steps:
In your Pulumi code, I've not observed the second step (Sync triggers to Azure Cloud
). I don't know how to manually perform this without using the func
CLI (Azure Function Host CLI). This might not be related to the issue with Azure Functions Node.js v4.
Let's break down how Azure Functions sync their triggers to the Azure Cloud (based on my understanding, without official references, so confirmation from Azure would be helpful):
In Azure Functions v3, the process relied on a function.json
file at the project root. When you deploy an Azure Function to the Azure Cloud, it reads metadata from these function.json
files and registers it into global triggers. These global triggers handle all bindings and triggers for all Azure Functions on the platform, forwarding requests to your Azure Functions server to trigger the registered event.
However, for v4 (the current version in this issue), the handling of function.json
files has changed. From my reading, it seems that when you publish code via the func
CLI, it runs your v4 code once to send a request directly to the Azure Cloud infrastructure instead of reading metadata from function.json
.
Note: Most of the above explanation is based on my understanding from using Azure Functions and is not officially referenced. Please consider this information carefully.
Note to Azure Team @ejizba: If there are any inaccuracies in my understanding, please feel free to correct me. Your input is highly appreciated.
fix that i found is to deploy it on linux `import as pulumi from "@pulumi/pulumi"; import as azure from "@pulumi/azure-native";
// Import the program's configuration settings. const config = new pulumi.Config(); const appPath = config.get("appPath") || "../";
// Create a resource group for the website. const resourceGroup = new azure.resources.ResourceGroup("rob-group", { location: "WestEurope", });
// Create a blob storage account. const account = new azure.storage.StorageAccount("robaccount", { resourceGroupName: resourceGroup.name, kind: azure.storage.Kind.StorageV2, sku: { name: azure.storage.SkuName.Standard_LRS, }, minimumTlsVersion: "TLS1_2", }, { dependsOn: [resourceGroup] });
// Create a storage container for the serverless app. const appContainer = new azure.storage.BlobContainer("rob-app-container", { accountName: account.name, resourceGroupName: resourceGroup.name, publicAccess: azure.storage.PublicAccess.None, }, { dependsOn: [account] });
// Upload the serverless app to the storage container. const appBlob = new azure.storage.Blob("rob-app-blob", { accountName: account.name, resourceGroupName: resourceGroup.name, containerName: appContainer.name, source: new pulumi.asset.FileArchive(appPath), }, { dependsOn: [appContainer] });
// Create a shared access signature to give the Function App access to the code.
const signature = azure.storage.listStorageAccountServiceSASOutput({
resourceGroupName: resourceGroup.name,
accountName: account.name,
protocols: azure.storage.HttpProtocol.Https,
sharedAccessStartTime: "2022-01-01",
sharedAccessExpiryTime: "2030-01-01",
resource: azure.storage.SignedResource.C,
permissions: azure.storage.Permissions.R,
canonicalizedResource: pulumi.interpolate/blob/${account.name}/${appContainer.name}
,
contentType: "application/json",
cacheControl: "max-age=5",
contentDisposition: "inline",
contentEncoding: "deflate",
});
// Create an App Service plan for the Function App. const plan = new azure.web.AppServicePlan("plan", { resourceGroupName: resourceGroup.name, kind: "linux", reserved: true, sku: { name: "Y1", tier: "Dynamic", }, }, { dependsOn: [appContainer] });
function getConnectionString(
resourceGroupName: pulumi.Input
// Build the connection string to the storage account.
return pulumi.interpolateDefaultEndpointsProtocol=https;AccountName=${accountName};AccountKey=${primaryStorageKey}
;
}
const dummyVariable = new Date().toISOString();
// Create the Function App.
const functionApp = new azure.web.WebApp("rob-function-app", {
resourceGroupName: resourceGroup.name,
serverFarmId: plan.id,
kind: "functionapp,linux",
siteConfig: {
appSettings: [
{
name: "FUNCTIONS_WORKER_RUNTIME",
value: "node",
},
{
name: "WEBSITE_NODE_DEFAULT_VERSION",
value: "~20",
},
{
name: "FUNCTIONS_EXTENSION_VERSION",
value: "~4",
},
{
name: "WEBSITE_RUN_FROM_PACKAGE",
value: pulumi.all([account.name, appContainer.name, appBlob.name, signature])
.apply(([accountName, containerName, blobName, signature]) => https://${accountName}.blob.core.windows.net/${containerName}/${blobName}?${signature.serviceSasToken}
),
},
{
name: "AzureWebJobsStorage",
value: pulumi.all([resourceGroup.name, account.name])
.apply(([resourceGroupName, accountName]) => getConnectionString(resourceGroupName, accountName)),
},
{
name: "UPDATE_HASH", // This is a dummy variable to trigger an update
value: dummyVariable,
},
],
cors: {
allowedOrigins: [
"*"
],
},
linuxFxVersion: "Node|20",
nodeVersion: "~20",
},
}, { dependsOn: [appBlob, plan] });
export const apiURL = pulumi.interpolatehttps://${functionApp.defaultHostName}/api
;
`
I also encountered the same phenomenon. I was able to deploy by copying essential files such as package.json to a different folder.
#!/bin/bash
BUILD_DIR=build
rimraf $BUILD_DIR
mkdir $BUILD_DIR
cp -r dist $BUILD_DIR
cp host.json $BUILD_DIR
cp local.settings.json $BUILD_DIR
jq 'del(.devDependencies)' package.json > temp.json && mv temp.json $BUILD_DIR/package.json
cd $BUILD_DIR && npm install --omit=dev \
&& func azure functionapp publish $APP_NAME
Originally posted by @mildronize in https://github.com/Azure/azure-functions-nodejs-library/issues/201#issuecomment-2110788561
I've been stuck many kind of monorepo tools such as Nx, or pnpm workspace, or even npm/yarn workspace as well, bundling code with esbuild make the error when deploying into the Azure Functions on Azure.
Let's say for most cases of monorepo and bundling @ejizba, you just simple setup the basic monorepo calling the library across the project you will got the error without sending the issue, in the other hands, if it already support monorepo or bundling code, could you please give me some example how to make it possible?
In my experience, Azure Functions Node.js Runtime is hardest project to setup monorepo,
Without bundling in the monrepo
Deploying Azure Functions without bundling in the monrepo, you need some extra script to move the packages in the node_modules when you deploy from the subdirectory of the monrepo, for example, you need some extra script to copy from node_modules from the root project into
azure-func-project-1
orazure-func-project-2
, this structure come from Nx Integrated monorepo feature,for most common monorepo pattern, they most use pnpm/npm/yarn workspaces, you need to prevent linked mode in the monorepo tool for workspace because modern monorepo tool mostly use the linked to safe space in the machine, I'm don't familiar with workspace configuration, i've tried many options but it's won't work. Another problem when
azure-func-project-1
want to callmy-shared-libs
, you need to bundling the lib and copy into the node_modules, and when your monorepo go large, it quite complex task to maintain build process.or if you want deploy it from the root of the project you'll also need multiple
package.json
file that hasmain
field which located in the entrypoint of each project in the same monorepo, For examples, you will need to handle the complexity of node_modules above method and setuppackage.json
separately in the root of the monorepoHowever, I've never do following my thought, my guess is quite challenge to setup monorepo without custom magic script.
With Bundling
Another way using bundling like
esbuild
, I've already added the package suggested above@azure/functions-core
, as the external dependency, however, deploying to Azure, it'still got the error, I don't have any error right now, or you can see in issue https://github.com/Azure/azure-functions-nodejs-library/issues/256#issue-2292563826I've give up on monorepo or bunding Azure Function node.js runtime. so, if someone has error message please provide some error, it might be benefit.
I don't know the direction of the Azure Functions (Node.js Runtime), that's I've been stuck this issue so long, so, I've decided the moved out from the node.js runtime to custom handler instead, which is I've try to proof of concept in Nammatham v3
for now, in my design
I can build, bundling with monorepo and deploy successfully result below:
Github Actions E2E Results
However, if the Azure Functions Node.js runtime has decided the too fix this issue, I hope it most benefit for community. and that's I don't have any reason to continue develop Nammatham v3
Originally posted by @mildronize in https://github.com/Azure/azure-functions-nodejs-library/issues/201#issuecomment-2110788561