Azure Function and Azure Batch

Sebastian Pacheco 181 Reputation points
2024-08-22T19:09:26.7166667+00:00

I have created a function that creates a node in a batch Azure account. The node has certain characteristics and adds an .exe plus a Python script to it from the storage account to run.

Everything was fine until then, the node was created and everything was installed, but I found it necessary to link a vnet to the node since I need it to communicate with the azure key vault. Since I don't know much about code to create functions, I have been helping myself with chatgpt but I still can't find the solution.

This is my Function:

import azure.functions as func
import logging
import azure.batch as batch
import azure.batch.models as batch_models
from azure.identity import DefaultAzureCredential

def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')

    try:
        create_batch_pool()
        return func.HttpResponse("Pool creado correctamente.", status_code=200)
    except Exception as e:
        return func.HttpResponse(f"Error al crear el pool: {e}", status_code=500)

def create_batch_pool():
    account_url = 'https://<URL>.<location>.batch.azure.com'

    try:
        # Usar DefaultAzureCredential para autenticación con AAD
        credentials = DefaultAzureCredential()
        batch_client = batch.BatchServiceClient(credentials, batch_url=account_url)

        pool_id = 'homa_nodos_batch'
        vm_size = 'STANDARD_D2S_V3'
        node_count = 1

        # Configuración de la red virtual y subred
        vnet_id = '/subscriptions/<id-subscriptions>/resourceGroups/<resources-group>/providers/Microsoft.Network/virtualNetworks/vnet-nodos-batch'
        subnet_id = f'{vnet_id}/subnets/default'

        new_pool = batch_models.PoolAddParameter(
            id=pool_id,
            display_name='homa_nodos_batch',
            vm_size=vm_size,
            virtual_machine_configuration=batch_models.VirtualMachineConfiguration(
                image_reference=batch_models.ImageReference(
                    publisher='microsoftwindowsserver',
                    offer='windowsserver',
                    sku='2019-datacenter',
                    version='latest'
                ),
                node_agent_sku_id='batch.node.windows amd64',
                os_disk=batch_models.OSDisk(
                    caching='none',
                    managed_disk=batch_models.ManagedDisk(
                        storage_account_type='premium_lrs'
                    )
                ),
                node_placement_configuration=batch_models.NodePlacementConfiguration(
                    policy='Regional'
                )
            ),
            resize_timeout='PT15M',
            target_dedicated_nodes=node_count,
            target_low_priority_nodes=0,
            enable_auto_scale=False,
            enable_inter_node_communication=False,
            network_configuration=batch_models.NetworkConfiguration(
                subnet_id=subnet_id,
                public_ip_address_configuration=batch_models.PublicIPAddressConfiguration(
                    provision='batchmanaged'
                )
            ),
            start_task=batch_models.StartTask(
                command_line='cmd /c "python-3.12.4-amd64.exe /quiet InstallAllUsers=1 PrependPath=1 Include_test=0 && pip install pymysql psycopg2-binary sqlalchemy pandas mysql-connector-python azure-identity azure-keyvault-secrets && powershell.exe -ExecutionPolicy Bypass -File variables.ps1"',
                resource_files=[
                    batch_models.ResourceFile(
                        http_url='https://<blobname>.blob.core.windows.net/script/python-3.12.4-amd64.exe<SAS>',
                        file_path='python-3.12.4-amd64.exe'
                    ),
                    batch_models.ResourceFile(
                        http_url='https://<blobname>.blob.core.windows.net/script/variables_entorno.ps1<SAS>',
                        file_path='variables.ps1'
                    )
                ],
                user_identity=batch_models.UserIdentity(
                    auto_user=batch_models.AutoUserSpecification(
                        scope='pool',
                        elevation_level='admin'
                    )
                ),
                max_task_retry_count=3,
                wait_for_success=True
            ),
            task_slots_per_node=1,
            task_scheduling_policy=batch_models.TaskSchedulingPolicy(
                node_fill_type='Pack'
            ),
            target_node_communication_mode='default'
        )

        batch_client.pool.add(new_pool)
        logging.info(f"Pool {pool_id} creado con {node_count} nodos de tamaño {vm_size}.")
    except batch_models.BatchErrorException as e:
        logging.error(f"Error al crear el pool: {e}")
        raise
    except Exception as e:
        logging.error(f"Error inesperado: {e}")
        raise

I have activated the "Identity" (System assigned) in Azure Function and I gave this identity the ROLE of "Contributor" in Azure Batch and also to the "vnet-nodes-batch".

The error I am getting is:
Error inesperado: 'DefaultAzureCredential' object has no attribute 'signed_session'

Try the managed identity and give the roles to batch and the vnet. I expected the function to create the node with the associated vnet.

Azure Functions
Azure Functions
An Azure service that provides an event-driven serverless compute platform.
4,913 questions
Azure Batch
Azure Batch
An Azure service that provides cloud-scale job scheduling and compute management.
330 questions
0 comments No comments
{count} votes

5 answers

Sort by: Most helpful
  1. Sina Salam 10,176 Reputation points
    2024-08-22T21:05:00.2566667+00:00

    Hello Sebastian Pacheco,

    Welcome to the Microsoft Q&A and thank you for posting your questions here.

    Problem

    I understand that you are having Error inesperado: 'DefaultAzureCredential' object has no attribute 'signed_session' in your Azure batch pool function.

    Solution

    You need to understand that Azure Batch SDK does not use this credential adapter out of the box, that's what caused the error because the Azure Batch SDK expects credentials that provide a different API than what DefaultAzureCredential can provides. https://learn.microsoft.com/en-us/python/api/overview/azure/batch?view=azure-python and https://learn.microsoft.com/en-us/python/api/azure-batch/azure.batch.batch_auth.sharedkeycredentials?view=azure-python

    So, as a best practices Azure Batch traditionally uses shared key credentials for authentication and this is to ensure compatibility with the Azure Batch SDK, I will advise you to use SharedKeyCredentials instead of DefaultAzureCredential.

    Though, you might find an option to wrap the DefaultAzureCredential using an adapter that provides the expected API for the Azure Batch client as I have seen some developers do, but I am not sure the later end issue. Although, default credential capable of handling most Azure SDK authentication scenarios but here https://learn.microsoft.com/en-us/python/api/azure-identity/azure.identity.defaultazurecredential?view=azure-python explains its class and intended usage

    Now! So far that you can make sure that the subnet you specify is properly set up and that the Batch pool can communicate with the Azure Key Vault through the network and the Azure Function’s managed identity or any service principal you use has the necessary roles assigned for example, Contributor Role on the Azure Batch account and Network Contributor Role on the virtual network and subnet. You're good.

    See the below modified version of your code to create_batch_pool function to use shared key authentication as I said earlier:

    import azure.batch as batch
    import azure.batch.models as batch_models
    from azure.batch.batch_auth import SharedKeyCredentials
    def create_batch_pool():
        account_url = 'https://<URL>.<location>.batch.azure.com'
        account_name = '<your_batch_account_name>'
        account_key = '<your_batch_account_key>'
        try:
            # Use SharedKeyCredentials for authentication with Azure Batch
            credentials = SharedKeyCredentials(account_name, account_key)
            batch_client = batch.BatchServiceClient(credentials, batch_url=account_url)
            pool_id = 'homa_nodos_batch'
            vm_size = 'STANDARD_D2S_V3'
            node_count = 1
            # Configuration of the virtual network and subnet
            vnet_id = '/subscriptions/<id-subscriptions>/resourceGroups/<resources-group>/providers/Microsoft.Network/virtualNetworks/vnet-nodos-batch'
            subnet_id = f'{vnet_id}/subnets/default'
            new_pool = batch_models.PoolAddParameter(
                id=pool_id,
                display_name='homa_nodos_batch',
                vm_size=vm_size,
                virtual_machine_configuration=batch_models.VirtualMachineConfiguration(
                    image_reference=batch_models.ImageReference(
                        publisher='microsoftwindowsserver',
                        offer='windowsserver',
                        sku='2019-datacenter',
                        version='latest'
                    ),
                    node_agent_sku_id='batch.node.windows amd64',
                    os_disk=batch_models.OSDisk(
                        caching='none',
                        managed_disk=batch_models.ManagedDisk(
                            storage_account_type='premium_lrs'
                        )
                    ),
                    node_placement_configuration=batch_models.NodePlacementConfiguration(
                        policy='Regional'
                    )
                ),
                resize_timeout='PT15M',
                target_dedicated_nodes=node_count,
                target_low_priority_nodes=0,
                enable_auto_scale=False,
                enable_inter_node_communication=False,
                network_configuration=batch_models.NetworkConfiguration(
                    subnet_id=subnet_id,
                    public_ip_address_configuration=batch_models.PublicIPAddressConfiguration(
                        provision='batchmanaged'
                    )
                ),
                start_task=batch_models.StartTask(
                    command_line='cmd /c "python-3.12.4-amd64.exe /quiet InstallAllUsers=1 PrependPath=1 Include_test=0 && pip install pymysql psycopg2-binary sqlalchemy pandas mysql-connector-python azure-identity azure-keyvault-secrets && powershell.exe -ExecutionPolicy Bypass -File variables.ps1"',
                    resource_files=[
                        batch_models.ResourceFile(
                            http_url='https://<blobname>.blob.core.windows.net/script/python-3.12.4-amd64.exe<SAS>',
                            file_path='python-3.12.4-amd64.exe'
                        ),
                        batch_models.ResourceFile(
                            http_url='https://<blobname>.blob.core.windows.net/script/variables_entorno.ps1<SAS>',
                            file_path='variables.ps1'
                        )
                    ],
                    user_identity=batch_models.UserIdentity(
                        auto_user=batch_models.AutoUserSpecification(
                            scope='pool',
                            elevation_level='admin'
                        )
                    ),
                    max_task_retry_count=3,
                    wait_for_success=True
                ),
                task_slots_per_node=1,
                task_scheduling_policy=batch_models.TaskSchedulingPolicy(
                    node_fill_type='Pack'
                ),
                target_node_communication_mode='default'
            )
            batch_client.pool.add(new_pool)
            logging.info(f"Pool {pool_id} created with {node_count} nodes of size {vm_size}.")
        except batch_models.BatchErrorException as e:
            logging.error(f"Error creating the pool: {e}")
            raise
        except Exception as e:
            logging.error(f"Unexpected error: {e}")
            raise
    

    With the above, test the function it should create the Batch pool as expected and should be correctly associated with the Virtual Network.

    Accept Answer

    I hope this is helpful! Do not hesitate to let me know if you have any other questions.

    ** Please don't forget to close up the thread here by upvoting and accept it as an answer if it is helpful ** so that others in the community facing similar issues can easily find the solution.

    Best Regards,

    Sina Salam

    0 comments No comments

  2. Sebastian Pacheco 181 Reputation points
    2024-08-23T13:53:08.83+00:00

    hello @Sina Salam thanks for you time!
    I tried previously with "SharedKeyCredentials" and then got to "Identity - Assigned System". =(

    The code you gave me was missing the "main" section so I added it to make the function work:

    import azure.functions as func
    import logging
    import azure.batch as batch
    import azure.batch.models as batch_models
    from azure.batch.batch_auth import SharedKeyCredentials
    
    def create_batch_pool():
        account_url = 'https://<URL>.<location>.batch.azure.com'
        account_name = '<your_batch_account_name>'
        account_key = '<your_batch_account_key>'
        try:
            credentials = SharedKeyCredentials(account_name, account_key)
            batch_client = batch.BatchServiceClient(credentials, batch_url=account_url)
            pool_id = 'homa_nodos_batch'
            vm_size = 'STANDARD_D2S_V3'
            node_count = 1
            # Use SharedKeyCredentials for authentication with Azure Batch
            vnet_id = '/subscriptions/<id-suscrip>/resourceGroups/<resource-group>/providers/Microsoft.Network/virtualNetworks/vnet-nodos-batch'
            subnet_id = f'{vnet_id}/subnets/default'
    
            new_pool = batch_models.PoolAddParameter(
                id=pool_id,
                display_name='homa_nodos_batch',
                vm_size=vm_size,
                virtual_machine_configuration=batch_models.VirtualMachineConfiguration(
                    image_reference=batch_models.ImageReference(
                        publisher='microsoftwindowsserver',
                        offer='windowsserver',
                        sku='2019-datacenter',
                        version='latest'
                    ),
                    node_agent_sku_id='batch.node.windows amd64',
                    os_disk=batch_models.OSDisk(
                        caching='none',
                        managed_disk=batch_models.ManagedDisk(
                            storage_account_type='premium_lrs'
                        )
                    ),
                    node_placement_configuration=batch_models.NodePlacementConfiguration(
                        policy='Regional'
                    )
                ),
                resize_timeout='PT15M',
                target_dedicated_nodes=node_count,
                target_low_priority_nodes=0,
                enable_auto_scale=False,
                enable_inter_node_communication=False,
                network_configuration=batch_models.NetworkConfiguration(
                    subnet_id=subnet_id,
                    public_ip_address_configuration=batch_models.PublicIPAddressConfiguration(
                        provision='batchmanaged'
                    )
                ),
                start_task=batch_models.StartTask(
                    command_line='cmd /c "python-3.12.4-amd64.exe /quiet InstallAllUsers=1 PrependPath=1 Include_test=0 && pip install pymysql psycopg2-binary sqlalchemy pandas mysql-connector-python azure-identity azure-keyvault-secrets && powershell.exe -ExecutionPolicy Bypass -File variables.ps1"',
                    resource_files=[
                        batch_models.ResourceFile(
                            http_url='https://<name>.blob.core.windows.net/script/python-3.12.4-amd64.exe<SAS>',
                            file_path='python-3.12.4-amd64.exe'
                        ),
                        batch_models.ResourceFile(
                            http_url='https://<name>.blob.core.windows.net/script/variables_entorno.ps1<SAS>',
                            file_path='variables.ps1'
                        )
                    ],
                    user_identity=batch_models.UserIdentity(
                        auto_user=batch_models.AutoUserSpecification(
                            scope='pool',
                            elevation_level='admin'
                        )
                    ),
                    max_task_retry_count=3,
                    wait_for_success=True
                ),
                task_slots_per_node=1,
                task_scheduling_policy=batch_models.TaskSchedulingPolicy(
                    node_fill_type='Pack'
                ),
                target_node_communication_mode='default'
            )
            batch_client.pool.add(new_pool)
            logging.info(f"Pool {pool_id} created with {node_count} nodes of size {vm_size}.")
        except batch_models.BatchErrorException as e:
            logging.error(f"Error creating the pool: {e}")
            raise
        except Exception as e:
            logging.error(f"Unexpected error: {e}")
            raise
    
    def main(req: func.HttpRequest) -> func.HttpResponse:
        logging.info('Processing request to create batch pool.')
    
        create_batch_pool()
    
        return func.HttpResponse(
            "Batch pool created successfully.",
            status_code=200
        )
    

    Adding the "main" and also what you told me (SharedKeyCredentials) and running the function gives me this error:

    [2024-08-23T13:10:44.222Z] Error creating the pool: Request encountered an exception.
    [2024-08-23T13:10:44.227Z] Code: AuthenticationFailed
    [2024-08-23T13:10:44.227Z] Message: {'additional_properties': {}, 'lang': 'en-US', 'value': 'Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.\nRequestId:90b7c728-4488-466b-8424-10b314c2878c\nTime:2024-08-23T13:10:44.1403123Z'}
    [2024-08-23T13:10:44.227Z] AuthenticationErrorDetail: The specified type of authentication SharedKey is not allowed when external resources of type Network are linked.
    

    Apparently this type of authentication is not supported when using a virtual network (VNet) and I must use another authentication method. My question is which one?

    Try Azure Function System Managed Identity and adding it with the Contributor role in Azure Batch... I'm not sure how I would have to modify my code to make it work with this type of authentication and I also don't know if this type of authentication will work work if I am requesting from my batch function to create a node with an associated vnet.

    I will continue investigating and testing.... if you can help me a little I would appreciate it.

    Thanks!


  3. Pinaki Ghatak 3,980 Reputation points Microsoft Employee
    2024-08-26T08:30:05.34+00:00

    Hello @Sebastian Pacheco

    One possible solution is to use Managed Identity instead of DefaultAzureCredential. You have already activated the Identity (System assigned) in Azure Function, so you can use it to authenticate with AAD. To use Managed Identity, you need to modify your code to use ManagedIdentityCredential instead of DefaultAzureCredential. Here is an example of how to modify your code:

    from azure.identity import ManagedIdentityCredential
    def create_batch_pool(): 
    account_url = 'https://..batch.azure.com'
    try: 
    # Use ManagedIdentityCredential for authentication with AAD 
    credentials = ManagedIdentityCredential() 
    batch_client = batch.BatchServiceClient(credentials, batch_url=account_url)
    # rest of the code
    

    Make sure that you have given the Managed Identity the necessary roles to access Azure Batch and the VNet. You can assign the "Contributor" role to the Managed Identity at the subscription or resource group level.

    Once you have modified your code and assigned the necessary roles to the Managed Identity, you should be able to create the node with the associated VNet.


    I hope that this response has addressed your query and helped you overcome your challenges. If so, please mark this response as Answered. This will not only acknowledge our efforts, but also assist other community members who may be looking for similar solutions.

    0 comments No comments

  4. Pinaki Ghatak 3,980 Reputation points Microsoft Employee
    2024-08-26T20:21:58.4733333+00:00

    Hi again, @Sebastian Pacheco

    Here are a couple of ways to resolve this issue:

    Use msrestazure.azure_active_directory.MSIAuthentication: The WebSiteManagementClient and some other Azure SDK clients expect an authentication class from msrestazure. You can replace ManagedIdentityCredential with MSIAuthentication from msrestazure.azure_active_directory.

    from msrestazure.azure_active_directory import MSIAuthentication
    from azure.mgmt.web import WebSiteManagementClient
    
    credentials = MSIAuthentication()
    web_app_client = WebSiteManagementClient(credentials, subscription_id)
    

    Wrap ManagedIdentityCredential with AzureIdentityCredentialWrapper: If you prefer to use ManagedIdentityCredential, you can wrap it with AzureIdentityCredentialWrapper from azure-identity.

    
    from azure.identity import ManagedIdentityCredential
    from azure.mgmt.web import WebSiteManagementClient
    from msrest.authentication import AzureIdentityCredentialWrapper
    
    credentials = ManagedIdentityCredential()
    wrapped_credentials = AzureIdentityCredentialWrapper(credentials)
    web_app_client = WebSiteManagementClient(wrapped_credentials, subscription_id)
    

    These solutions should help you bypass the signed_session attribute error as it is shown and talked about here


    Does this help?

    0 comments No comments

  5. Sebastian Pacheco 181 Reputation points
    2024-08-26T20:12:24.3433333+00:00

    Hi , @Pinaki Ghatak
    add the lines you indicated:

    import azure.functions as func
    import logging
    import azure.batch as batch
    import azure.batch.batch_auth as batch_auth
    import azure.batch.models as batch_models
    from azure.identity import ManagedIdentityCredential
    
    def main(req: func.HttpRequest) -> func.HttpResponse:
        logging.info('Python HTTP trigger function processed a request.')
    
        try:
            create_batch_pool()
            return func.HttpResponse("Pool creado correctamente.", status_code=200)
        except Exception as e:
            return func.HttpResponse(f"Error al crear el pool: {e}", status_code=500)
    
    def create_batch_pool():
        account_url = 'https://<name>.<location>.batch.azure.com'
    
        try:
            credentials = ManagedIdentityCredential() 
            batch_client = batch.BatchServiceClient(credentials, batch_url=account_url)
    
            pool_id = 'homa_nodos_batch'
            vm_size = 'STANDARD_D2S_V3'
    # rest of the code
    

    But when I execute the "func start" command locally it gives me my 2 functions and when I execute the one to create a node I get this error:

    (.venv) xxxxxx@function:~/Function/xxxxxxx$ func start 
    Found Python version 3.10.12 (python3).
    
    Azure Functions Core Tools
    Core Tools Version:       4.0.5907 Commit hash: N/A +807e89766a92b14fd07b9f0bc2bea1d8777ab209 (64-bit)
    Function Runtime Version: 4.834.3.22875
    
    [2024-08-26T20:01:05.287Z] Worker process started and initialized.
    
    Functions:
    
    	crear_nodos: [GET,POST] http://localhost:7071/api/crear_nodos
    
    	eliminar_nodos: [GET,POST] http://localhost:7071/api/eliminar_nodos
    
    For detailed output, run func with --verbose flag.
    [2024-08-26T20:01:12.002Z] Host lock lease acquired by instance ID '000000000000000000000000A4756CEF'.
    [2024-08-26T20:02:47.658Z] Executing 'Functions.crear_nodos' (Reason='This function was programmatically called via the host APIs.', Id=0bb5e67a-6c6d-495b-80fe-4e9f349f1cc7)
    [2024-08-26T20:02:47.755Z] Python HTTP trigger function processed a request.
    [2024-08-26T20:02:47.756Z] Error inesperado: 'ManagedIdentityCredential' object has no attribute 'signed_session'[2024-08-26T20:02:47.756Z] ManagedIdentityCredential will use IMDS
    
    [2024-08-26T20:02:47.866Z] Executed 'Functions.crear_nodos' (Succeeded, Id=0bb5e67a-6c6d-495b-80fe-4e9f349f1cc7, Duration=247ms) 
    

    [2024-08-26T20:02:47.756Z] Error inesperado: 'ManagedIdentityCredential' object has no attribute 'signed_session'[2024-08-26T20:02:47.756Z] ManagedIdentityCredential will use IMDS

    1.- my identity in azure function
    Captura desde 2024-08-26 16-07-00

    2.- my identity permissions in azure batch and vnet
    Captura desde 2024-08-26 16-09-23

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.