PMTiles icon indicating copy to clipboard operation
PMTiles copied to clipboard

getZxy with Azure Function app entry point

Open tengl opened this issue 2 years ago • 15 comments

This PR adds code that can be used to deploy protomaps on Azure. See README.md for more info.

It is intended to be deployed in an Azure Function App what is exposed though a CDN.

The pmtiles file can be stored at any URL, but if you use Azure then you probably want to deploy it to Azure Blob Storage. Just make sure the function app has access to the container/file.

This exact code has note been tested. I just used it as base for a Function App, which has some modifications that I didn't want to include here.

Most of the code should work for any Source and any back-end, with minor modifications.

tengl avatar Jun 16 '23 11:06 tengl

Thanks, this looks like a good starting point. The two vital features needed are

  1. the azure function needs to be able to authenticate with a private Blob Storage bucket - were you able to accomplish this?
  2. The azure function needs to integrate with an edge-cached CDN, which I think in Azure requires Front Door - or are you using a different CDN?

bdon avatar Jun 16 '23 23:06 bdon

Thanks, this looks like a good starting point. The two vital features needed are

  1. the azure function needs to be able to authenticate with a private Blob Storage bucket - were you able to accomplish this?
  2. The azure function needs to integrate with an edge-cached CDN, which I think in Azure requires Front Door - or are you using a different CDN?
  1. No, I have tried it. I used a public container. But it should be possible to do it with a private container and a SAS token.
  2. I used Azure Classic CDN. It is cheaper and is probably better for static content.

I will try with SAS token next week and get back to you.

tengl avatar Jun 17 '23 06:06 tengl

@tengl the preference is for the authentication between Azure Functions and Blob Storage to be role-based without needing to specify a hardcoded key/secret. in Cloudflare this is done via Workers Bindings and in AWS via IAM roles.

The principle of the AWS and Cloudflare integrations is to present a well-tested and uniform solution for deploying protomaps within a single provider. It is of course possible to mix-and-match - e.g. Azure Functions serving from an AWS S3 Bucket - but the cost model is less efficient and it becomes difficult to debug across providers. So an integrated solution for Azure will be extremely valuable.

bdon avatar Jun 17 '23 23:06 bdon

I see. I guess it should be possible using this tutorial, https://learn.microsoft.com/en-us/azure/app-service/tutorial-connect-app-access-storage-javascript?tabs=azure-portal. I've only browsed through it, so I'm not sure.

We could make an AzureStorageSource and use that instead of the FetchSource. But I will probably not have time to do it this week, we'll see if I can manage next week. Feel free to modify the PR if you have the time.

The SAS token and private container works, as expected. We will go for that solution until we find a way to use the storage blob SDK.

tengl avatar Jun 19 '23 07:06 tengl

@bdon I just pushed some updates. I made an AzureStorageSource that should be able to use managed identities. I haven't tested it properly yet, but I will hopefully do it next week.

It still supports FetchSource.

tengl avatar Nov 17 '23 13:11 tengl

@bdon I pushed some fixes. It seems to work now, at least on one of my pmtiles files.

I've tested on two different pmtiles files and both local debug and on Azure. All combinations work when using PMTILES_PATH and FetchSource and as well as when using AzureStorageSource (Azure Storage managed identities) by specifying the storage details.

A caveat though, it doesn't work if multiple maps are enabled and they mix sources. In that case I get an error saying incorrect header check for one of the pmtiles files. It probably has something to do with caching.

A note, using FetchSource and SAS token URLs seems to be a lot faster than using Azure Managed Identities. I'm sure/hope that it can be optimized, because right now it is too slow.

I consider it done, but there are some fixes for edge cases and optimizations to be made. I won't have the time to continue work on this at the moment.

tengl avatar Nov 20 '23 12:11 tengl

A caveat though, it doesn't work if multiple maps are enabled and they mix sources. In that case I get an error saying incorrect header check for one of the pmtiles files. It probably has something to do with caching.

What does "mix sources" mean? In the AWS and Cloudflare setups, a single instance of the function can serve multiple PMTiles files, so the cache in global memory needs to key portions of each file based on the filename.

bdon avatar Nov 21 '23 01:11 bdon

What does "mix sources" mean? In the AWS and Cloudflare setups, a single instance of the function can serve multiple PMTiles files, so the cache in global memory needs to key portions of each file based on the filename.

I made it a bit more flexible, so that the code can accept any kind of source. You can have one pmtiles files served by a FetchSource and another served by a AzureStorageSource. The file is resolved by appending {map_name} to the parameter. If I only use one type of source for multiple pmtiles files, it works, but if I have one FetchSource and one AzureStorageSource I get the error. I've tried both ResolvedValueCache and SharedPromiseCache, so the problem should not be related to the caches.

tengl avatar Nov 21 '23 07:11 tengl

@bdon I fixed it now. It was something wrong with the buffer. I don't really understand what, but if you look at commit https://github.com/protomaps/PMTiles/pull/197/commits/6dd2e6109dd7a54ed3bb845290f0f31b264818d0 maybe you can figure it out.

Before that change, the data.buffer.length would always be 8192, even if data.length was 1905.

tengl avatar Nov 22 '23 07:11 tengl

Finally revisiting this, some thoughts:

  • It looks like since SAS tokens are the only efficient way to authenticate requests to a storage account / container, we might as well use raw Fetch. The FetchSource from the pmtiles library is, as of v3.0.0, specific to browsers, so we probably want our own implementation that just does something like this:
  async getBytes(
    offset: number,
    length: number,
    signal?: AbortSignal,
    etag?: string
  ): Promise<RangeResponse> {
    const requestHeaders = new Headers();
    requestHeaders.set("range", `bytes=${offset}-${offset + length - 1}`);
    if (etag) requestHeaders.set("if-match", etag);

    const url = `https://${storageAccountName}.blob.core.windows.net/${containerName}/${this.archiveName}${sasToken}`;

    let resp = await fetch(url, {
      signal: signal,
      headers: requestHeaders,
    });

The SAS token can be scoped to a container's read operations, but it looks like the way to use them is to set an expiration date far into the future. Is there any alternative SAS token generation feature equivalent to AWS Roles?

bdon avatar Feb 09 '24 10:02 bdon

Also, ideally we can deploy the code as-is using az functionapp deployment source config-zip -g RESOURCE_GROUP -n FUNCTIONAPP_NAME --src ZIPFILE.zip - it seems like it expects a certain zip file structure for NodeJS apps, were you able to deploy using the CLI or are you using some IDE integration?

bdon avatar Feb 09 '24 10:02 bdon

  • It looks like since SAS tokens are the only efficient way to authenticate requests to a storage account / container, we might as well use raw Fetch.

Yes, we use SAS tokens and it's working great so far.

The FetchSource from the pmtiles library is, as of v3.0.0, specific to browsers,

I didn't realize that :-/. We used the FetchSource and it's working fine.

so we probably want our own implementation that just does something like this:

Yes, that might be good enough. The only thing (I think) specific to Azure Functions is the entry point, every thing else should be generic.

I think the only parameter to the function should be the URL, which includes the SAS token because that's how you copy it from Azure Portal. No need to split it up in multiple parameters. It also makes it easier to support other hosting scenarios, only the entry points needs to be customized as long as the pmtiles file is hosted via http.

The SAS token can be scoped to a container's read operations, but it looks like the way to use them is to set an expiration date far into the future. Is there any alternative SAS token generation feature equivalent to AWS Roles?

Not that I am aware of, apart from Managed Identities.

tengl avatar Feb 09 '24 15:02 tengl

Also, ideally we can deploy the code as-is using az functionapp deployment source config-zip -g RESOURCE_GROUP -n FUNCTIONAPP_NAME --src ZIPFILE.zip - it seems like it expects a certain zip file structure for NodeJS apps, were you able to deploy using the CLI or are you using some IDE integration?

I used VS Code integration. In my experience that is the best way if you want to make changes to the code. But I'm no expert in Azure Functions, I just dot the minimal to get the job done :-)

If we want to deploy directly to Azure the CLI is probably the best way. Maybe just create a bash and ps1 script for that?

tengl avatar Feb 09 '24 15:02 tengl

I didn't realize that :-/. We used the FetchSource and it's working fine.

It should work fine, but has some logic that is irrelevant for non-browser usage, and won't have efficient ETag handling that we just added to AWS / Cloudflare.

Another option for Azure is to use Azure Container Apps and not use Functions at all, but I don't use Azure enough to get a sense of which is better supported. Azure Functions seems like a mess with multiple runtimes, versions and upload methods, but Containers might have slow start times.

bdon avatar Feb 09 '24 17:02 bdon

FYI, I have been mostly working on the Google Cloud integration which uses a Docker container on Docker Registry.

https://docs.protomaps.com/deploy/google-cloud

If this deployment method works scale-to-zero on Azure Containers, that is preferable over a custom Function App because it's simply less code. But I haven't succeeded in deploying this (or getting a scale-to-zero config). Anyone with Azure insight want to try it out?

bdon avatar Mar 18 '24 11:03 bdon

Azure Service Connectors are the solution for connecting Container Apps and Blob Storage:

https://learn.microsoft.com/en-us/azure/service-connector/quickstart-portal-container-apps?tabs=SMI

I have a basic POC somewhat working, but evaluating how the DX and latency is.

bdon avatar Aug 28 '24 14:08 bdon

Great, sounds promising.

Sorry, I had to drop this, our current solution works great for our needs and I needed to focus on other things.

Edit; I had a quick look and it seems that service connectors was not supported for Azure Functions in our region. Maybe it is for Container Apps, but they are more expensive.

tengl avatar Aug 29 '24 06:08 tengl

How do Cold Starts look for your Azure Functions deployment?

For my basic Container Apps pmtiles deployment I see about 7 seconds scaling from 0 to 1.

https://github.com/microsoft/azure-container-apps/issues/997

That might be a dealbreaker, but it looks like there's also a billing model where scale=1 is minimum with idle pricing - not true scale to 0 like Function Apps though.

bdon avatar Aug 30 '24 09:08 bdon

I'm going to close this PR in favor of moving forward with Azure Container Apps, which don't require us to maintain another PMTiles decoding implementation against Azure APIs, just the same Docker Image used for Google Cloud.

https://github.com/protomaps/docs/pull/52/files

Would appreciate if anyone using Azure could try out the instructions on that PR and leave feedback.

Thank you for the contributions here.

bdon avatar Sep 02 '24 05:09 bdon

How do Cold Starts look for your Azure Functions deployment?

For my basic Container Apps pmtiles deployment I see about 7 seconds scaling from 0 to 1.

microsoft/azure-container-apps#997

That might be a dealbreaker, but it looks like there's also a billing model where scale=1 is minimum with idle pricing - not true scale to 0 like Function Apps though.

I haven't noticed any issues with cold starts in Azure Functions. When I looked at it, Container Apps would have been a lot more expensive if we were to have scale=1. But there might be more and better options now.

I'm going to close this PR in favor of moving forward with Azure Container Apps, which don't require us to maintain another PMTiles decoding implementation against Azure APIs, just the same Docker Image used for Google Cloud.

https://github.com/protomaps/docs/pull/52/files

Would appreciate if anyone using Azure could try out the instructions on that PR and leave feedback.

Thank you for the contributions here.

For now, I will continue to use Azure Functions but I might try Container Apps later. Thanks for a great project!

tengl avatar Sep 02 '24 06:09 tengl

We added support for ETag detection in https://github.com/protomaps/go-pmtiles/releases/tag/v1.21.0

so https://docs.protomaps.com/deploy/azure with Azure Blob Storage now supports updating a pmtiles file in-place on cloud storage.

bdon avatar Sep 09 '24 14:09 bdon

@tengl Hello, Would you be able to provide the link to the last stable version of the Azure Function? I have tried using the 'go-pmtiles' image for the Azure Container App but have reached a dead-end with the container app not working and a microsoft tech support suggesting not to use 'Service Connector' as it is in 'Preview' and unstable.

I understand that this is not a recommended way of using in the Azure environment by @bdon. However, I am stuck with the Azure environment and I am unable to make the Azure container app to work. Thanks, Kiran

kbatchu avatar May 30 '25 15:05 kbatchu

@tengl Hello, Would you be able to provide the link to the last stable version of the Azure Function?

Sure, I made a new repo as the one we use has some specific solutions. You might need to do some adjustments. It only has code for Azure Functions and is intended to be used behind a CDN.

https://github.com/tengl/pmtiles-func

tengl avatar Jun 09 '25 10:06 tengl

Thank you. Apologize for the late reply.

kbatchu avatar Aug 02 '25 13:08 kbatchu