Перенос зависимостей или сторонней библиотеки в функции Azure

Из этой статьи вы узнаете, как перенести сторонние зависимости в приложения-функции. Примерами сторонних зависимостей являются JSON-файлы, двоичные файлы и модели машинного обучения.

Вы узнаете, как выполнять следующие задачи:

  • Перенести зависимости через проект кода для функций
  • Перенести зависимости через подключение общей папки Azure

Перенести зависимости из каталога проекта

Одним из самых простых способов переноса зависимостей является размещение файлов и артефактов вместе с кодом приложений-функций в структуре каталогов проекта функций. Ниже приведен пример образцов каталогов в проекте функций Python:

<project_root>/
 | - my_first_function/
 | | - __init__.py
 | | - function.json
 | | - example.py
 | - dependencies/
 | | - dependency1
 | - .funcignore
 | - host.json
 | - local.settings.json

Если поместить зависимости в папку в каталоге проекта приложений-функций, папка зависимостей будет развернута вместе с кодом. В результате ваш код функции сможет получить доступ к зависимостям в облаке с через API файловой системы.

Доступ к зависимостям в коде

Ниже приведен пример получения доступа и выполнения зависимости ffmpeg, помещаемой в каталог <project_root>/ffmpeg_lib.

import logging

import azure.functions as func
import subprocess

FFMPEG_RELATIVE_PATH = "../ffmpeg_lib/ffmpeg"

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

    command = req.params.get('command')
    # If no command specified, set the command to help
    if not command:
        command = "-h"

    # context.function_directory returns the current directory in which functions is executed 
    ffmpeg_path = "/".join([str(context.function_directory), FFMPEG_RELATIVE_PATH])

    try:
        byte_output  = subprocess.check_output([ffmpeg_path, command])
        return func.HttpResponse(byte_output.decode('UTF-8').rstrip(),status_code=200)
    except Exception as e:
        return func.HttpResponse("Unexpected exception happened when executing ffmpeg. Error message:" + str(e),status_code=200)

Примечание

Может потребоваться использовать chmod для предоставления прав Execute двоичному файлу FFmpeg в среде Linux.

Одним из самых простых способов переноса зависимостей является размещение файлов и артефактов вместе с кодом приложений-функций в структуре каталогов проекта функций. Ниже приведен пример образцов каталогов в проекте функций Java:

<project_root>/
 | - src/
 | | - main/java/com/function
 | | | - Function.java
 | | - test/java/com/function
 | - artifacts/
 | | - dependency1
 | - host.json
 | - local.settings.json
 | - pom.xml

В частности, для Java необходимо явно включить артефакты в папку сборки или целевого объекта при копировании ресурсов. Ниже приведен пример того, как это сделать в Maven.

...
<execution>
    <id>copy-resources</id>
    <phase>package</phase>
    <goals>
        <goal>copy-resources</goal>
    </goals>
    <configuration>
        <overwrite>true</overwrite>
        <outputDirectory>${stagingDirectory}</outputDirectory>
        <resources>
            <resource>
                <directory>${project.basedir}</directory>
                <includes>
                    <include>host.json</include>
                    <include>local.settings.json</include>
                    <include>artifacts/**</include>
                </includes>
            </resource>
        </resources>
    </configuration>
</execution>
...

Если поместить зависимости в папку в каталоге проекта приложений-функций, папка зависимостей будет развернута вместе с кодом. В результате ваш код функции сможет получить доступ к зависимостям в облаке с через API файловой системы.

Доступ к зависимостям в коде

Ниже приведен пример получения доступа и выполнения зависимости ffmpeg, помещаемой в каталог <project_root>/ffmpeg_lib.

public class Function {
    final static String BASE_PATH = "BASE_PATH";
    final static String FFMPEG_PATH = "/artifacts/ffmpeg/ffmpeg.exe";
    final static String HELP_FLAG = "-h";
    final static String COMMAND_QUERY = "command";

    @FunctionName("HttpExample")
    public HttpResponseMessage run(
            @HttpTrigger(
                name = "req",
                methods = {HttpMethod.GET, HttpMethod.POST},
                authLevel = AuthorizationLevel.ANONYMOUS)
                HttpRequestMessage<Optional<String>> request,
            final ExecutionContext context) throws IOException{
        context.getLogger().info("Java HTTP trigger processed a request.");

        // Parse query parameter
        String flags = request.getQueryParameters().get(COMMAND_QUERY);

        if (flags == null || flags.isBlank()) {
            flags = HELP_FLAG;
        }

        Runtime rt = Runtime.getRuntime();
        String[] commands = { System.getenv(BASE_PATH) + FFMPEG_PATH, flags};
        Process proc = rt.exec(commands);

        BufferedReader stdInput = new BufferedReader(new 
        InputStreamReader(proc.getInputStream()));

        String out = stdInput.lines().collect(Collectors.joining("\n"));
        if(out.isEmpty()) {
            BufferedReader stdError = new BufferedReader(new 
                InputStreamReader(proc.getErrorStream()));
            out = stdError.lines().collect(Collectors.joining("\n"));
        }
        return request.createResponseBuilder(HttpStatus.OK).body(out).build();

    }

Примечание

Чтобы получить этот фрагмент кода для работы в Azure, необходимо указать пользовательский параметр приложения "BASE_PATH" со значением "/home/site/wwwroot".

Перемещение зависимостей путем подключения общей папки

При запуске приложения-функции в Linux существует другой способ подключения сторонних зависимостей. Функции позволяют подключать общую папку, размещенную в службе файлов Azure. Этот подход следует использовать, если требуется разделить зависимости или артефакты из вашего кода приложения.

Сначала нужно создать учетную запись хранения в Azure. В учетной записи также необходимо создать общую папку в службе файлов Azure. Чтобы создать эти ресурсы, следуйте этому руководству.

После создания учетной записи хранения и общей папки используйте команду az webapp config storage-account add, чтобы подключить общую папку к приложению-функции, как показано далее в примере.

az webapp config storage-account add \
  --name < Function-App-Name > \
  --resource-group < Resource-Group > \
  --subscription < Subscription-Id > \
  --custom-id < Unique-Custom-Id > \
  --storage-type AzureFiles \
  --account-name < Storage-Account-Name > \
  --share-name < File-Share-Name >  \
  --access-key < Storage-Account-AccessKey > \
  --mount-path </path/to/mount>
Флаг Значение
Пользовательский идентификатор Любая уникальная строка
Тип хранилища На данный момент поддерживаются только AzureFiles
имя общего ресурса Уже имеющаяся общая папка
путь подключения Путь, по которому общая папка будет доступна внутри контейнера. Значение должно иметь формат /dir-name и не может начинаться с /home

Дополнительные команды для изменения или удаления конфигурации общей папки можно найти здесь.

Отправка зависимостей в службу файлов Azure

Один из вариантов отправки зависимости в службу файлов Azure — с помощью портала Azure. Инструкции по отправке зависимостей с помощью портала см. в этом руководстве. Кроме того, отправить зависимости в службу файлов Azure можно через Azure CLI и PowerShell.

Доступ к зависимостям в коде

После отправки зависимостей в общую папку можно получить доступ к зависимостям из кода. Подключенная общая папка доступна по указанному пути подключения, например /path/to/mount. Доступ к целевому каталогу можно получить с помощью программных интерфейсов файловой системы.

В следующем примере показан код триггера HTTP, обращающийся к библиотеке ffmpeg, которая хранится в подключенной общей папке.

import logging

import azure.functions as func
import subprocess 

FILE_SHARE_MOUNT_PATH = os.environ['FILE_SHARE_MOUNT_PATH']
FFMPEG = "ffmpeg"

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

    command = req.params.get('command')
    # If no command specified, set the command to help
    if not command:
        command = "-h"

    try:
        byte_output  = subprocess.check_output(["/".join(FILE_SHARE_MOUNT_PATH, FFMPEG), command])
        return func.HttpResponse(byte_output.decode('UTF-8').rstrip(),status_code=200)
    except Exception as e:
        return func.HttpResponse("Unexpected exception happened when executing ffmpeg. Error message:" + str(e),status_code=200)

При развертывании этого кода в приложении-функции в Azure необходимо создать параметр приложения с именем ключа FILE_SHARE_MOUNT_PATH и значением пути к подключенной общей папке (в данном примере это /azure-files-share). Чтобы выполнить локальную отладку, необходимо указать для свойства FILE_SHARE_MOUNT_PATH значение пути к файлу, в котором хранятся зависимости на локальном компьютере. Ниже приведен пример для установки FILE_SHARE_MOUNT_PATH с помощью local.settings.json:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "python",
    "FILE_SHARE_MOUNT_PATH" : "PATH_TO_LOCAL_FFMPEG_DIR"
  }
}

Дальнейшие действия