[Перевод] Бессерверные вычисления на основе OpenWhisk, часть 3

ykjdcbuozxj70sxwrtxebx0l6u8.png

Эта статья продолжает цикл переводных заметок об OpenWhisk от автора Priti Desai. Сегодня рассмотрим примеры развертывания Zip-функций, зависимости GitHub, а также подробнее опишем синхронизацию объектов между клиентом и сервером OpenWhisk.


Zip-функции

OpenWhisk поддерживает создание функции из единственного файла с исходным кодом, как это было [показано ранее](). Он же поддерживает создание функции с использованием нескольких файлов с исходным кодом и набором пакетов, от которых функция зависит. Этот вариант использования функций называется zip-функцией. Давайте попробуем развернуть zip-функцию с помощью wskdeploy.


Шаг первый

Создаем файл-манифест:

packages:
    zipaction:
        actions:
        my-zip-action:
            function: actions/my-zip-action
            runtime: nodejs:6
            inputs:
                name: Amy

Считаем, что my-zip-action имеет такую структуру каталога, содержащую исходный код функции:

$ ls actions/my-zip-action
index.js
package.json

Содержимое файла index.js:

function helloworld(params) {
    var format = require('string-format');
    var name = params.name || 'Stranger';
    payload = format('Hello, {}!', name)
    return { message: payload };
}

exports.main = helloworld;

Содержимое файла package.json:

{
    "name": "my-zip-action",
    "description": "Node OpenWhisk zip action to demo Whisk Deploy",
    "license": "Apache-2.0",
    "version": "1.0.0",
    "main": "index.js",
    "dependencies": {
        "string-format": "0.5.0"
    }
}


Шаг второй

Запускаем npm install для установки string-format:

cd actions/my-action
npm install --production


Шаг третий

Разворачиваем zip-функцию:

./wskdeploy -i -p actions/my-zip-action/
         ____      ___                   _    _ _     _     _
        /\   \    / _ \ _ __   ___ _ __ | |  | | |__ (_)___| | __
   /\  /__\   \  | | | | '_ \ / _ \ '_ \| |  | | '_ \| / __| |/ /
  /  \____ \  /  | |_| | |_) |  __/ | | | |/\| | | | | \__ \   <
  \   \  /  \/    \___/| .__/ \___|_| |_|__/\__|_| |_|_|___/_|\_\
   \___\/              |_|
Packages:
Name: zipaction
    bindings:
  * action: my-zip-action
    bindings:
        - name: name value: Amy
Do you really want to deploy this? (y/N): y
Deployment completed successfully.

Мы развернули zip-функцию my-zip-action с зависимым модулем string-format. При указании каталога в манифесте по ключу function создается zip-архив из этого каталога, а также — функция из этого архива, так что не надо искать его по файловой системе. После развертывания можно работать с новой функцией точно так же, как и с другими функциями.


Include и exclude для файлов в zip-функциях

OpenWhisk позволяет создавать функцию с использованием zip-архива, содержащего любое количество файлов для функции, в т.ч. все ее зависимости. Развертывание поддерживает указание в function каталога с файлами для работы функции. Из содержимого каталога будет создан архив, из которого уже будет развернута функция.


Включение файлов

Ранее была представлена возможность указать ключ include, работающий примерно так же, как import в языках программирования, что позволило, к примеру, нескольким функциям ссылаться на одну общую библиотеку с кодом:

$ cd actions/
$ ls -1 ./
common/
greeting1/
greeting2/
manifest.yaml
$ ls -1 common/
utils.js
$ ls -1 greeting1/
index.js
package.json
$ ls -1 greeting2/
index.js
package.json

Можно видеть файл index.js в каталоге greeting1, а также еще один index.js в каталоге greeting2, и оба ссылаются на utils.js, расположенный в common/.

Содержимое файла index.js, расположенного в actions/greeting1/:

/**
 * Return a simple greeting message for someone.
 *
 * @param name A person's name.
 * @param place Where the person is from.
 */

var common = require('./common/utils.js')

function main(params) {
    var name = params.name || params.payload || 'stranger';
    var place = params.place || 'somewhere';
    var hello = common.hello || 'Good Morning';
    return {payload:  hello + ', ' + name + ' from ' + place + '!'};
}

exports.main = main;

Содержимое файла index.js, расположенного в actions/greeting2/:

/**
 * Return a simple greeting message for someone.
 *
 * @param name A person's name.
 * @param place Where the person is from.
 */

var common = require('./common/utils.js')

function main(params) {
    var name = params.name || params.payload || 'stranger';
    var place = params.place || 'somewhere';
    var hello = common.hello || 'Good Evening';
    return {payload:  hello + ', ' + name + ' from ' + place + '!'};
}

exports.main = main;

Внутри ключа include содержится список файлов или каталогов, которые должны быть включены в функцию. Каждый элемент этого списка должен иметь source и\или destination, к примеру:

include:
    - [source]
    - [source, destination]

Примечания:


  • source содержит относительный путь от каталога, содержащего manimanifest.yaml. destination подразумевает относительный путь от каталога с функцией, к примеру actions/greeting1 и actions/greeting2 в следующем примере.
  • Если параметр destination не задается — считается что он будет такой же, как и source.

Содержимое файла-манифеста:

packages:
    zipactionwithinclude:
        actions:
            greeting1:
                function: actions/greeting1
                runtime: nodejs:6
                include:
                    - ["actions/common/utils.js", "common/utils.js"]
            greeting2:
                function: actions/greeting2
                runtime: nodejs:6
                include:
                    - ["actions/common/utils.js", "common/utils.js"]

include работает с различными сочетаниями source и destination:


  • просто source:
include:
    - [actions/common/utils.js]

При такой записи utils.js будет скопирован в actions/greeting/actions/common/utils.js, а index.js может сослаться на него так:

var utils = require('./actions/common/utils.js')


  • include с переименованием:
include:
    - ["actions/common/utils.js", "./common/myUtils.js"]

С таким определением utils.js будет помещен по пути actions/greeting/common/myUtils.js, а index.js будет ссылаться на него так:

var utils = require('./common/myUtils.js')


  • include с другим путём:
include:
    - ["actions/common/utils.js", "./common/utility/utils.js"]

В таком случае utils.js будет скопирован в actions/greeting/common/utility/utils.js, со ссылкой из index.js:

var utils = require('./common/utility/utils.js')


  • include с символом *:
include:
    - ["actions/common/*.js", "./common/"]

В этом варианте utils.js вместе с другими файлами с расширением .js будет скопирован в каталог actions/greeting/common/, а в index.js эти файлы будут подключаться так:

var utils = require('./common/utils.js')


Включение каталогов

В include можно прописать каталог, который будет рекурсивно скопирован в указанное место перед включением в архив. Например libs`` содержит список библиотек, на которые ссылаетсяindex.jsв подкаталогеgreeting3/```:

$ cd actions/
$ ls -1
libs/
greeting3/
manifest.yaml
$ ls -1 libs/
lib1/
lib2/
lib3/
$ ls -1 libs/lib1/
utils.js
$ ls -1 libs/lib2/
utils.js
$ ls -1 libs/lib3/
utils.js
$ ls -1 greeting3/
index.js
package.json

Содержимое index.js в каталоге actions/greeting3/:

/**
 * Return a simple greeting message for someone.
 *
 * @param name A person's name.
 * @param place Where the person is from.
 */

var lib1 = require('./libs/lib1/utils.js')
var lib2 = require('./libs/lib2/utils.js')
var lib3 = require('./libs/lib3/utils.js')

function main(params) {
    var name = params.name || params.payload || 'stranger';
    var place = params.place || 'somewhere';
    var hello = lib1.hello || lib2.hello || lib3.hello || 'Hello';
    return {payload:  hello + ', ' + name + ' from ' + place + '!'};
}

exports.main = main;

Содержимое файла-манифеста:

packages:
    zipactionwithinclude:
        actions:
            greeting3:
                function: actions/greeting3
                runtime: nodejs:6
                include:
                    - ["actions/libs/*", "libs/"]

В этом примере каталог libs целиком рекурсивно копируется в actions/greeting3/libs/.

Подключение каталогов с символом *:


  • пример 1:
include:
    - ["actions/libs/*/utils.js", "libs/"]

При таком написании будут скопированы из libs все подкаталоги, содержащие utils.js. Ссылки из index.js будут выглядеть так:

var lib1 = require('./libs/lib1/utils.js')
var lib2 = require('./libs/lib2/utils.js')
var lib3 = require('./libs/lib3/utils.js')


  • пример 2:
include:
    - ["actions/*/*/utils.js"]

При такой записи будут скопированы все подкаталоги, подходящие под маску и содержащие utils.js. Доступ из index.js будет таким:

var lib1 = require('./actions/libs/lib1/utils.js')
var lib2 = require('./actions/libs/lib2/utils.js')
var lib3 = require('./actions/libs/lib3/utils.js')


  • пример 3:
include:
    - ["actions/*/*/utils.js", "actions/"]

В этом примере явно указано, куда все скопируется. Доступ из index.js будет таким же, как и в предыдущем примере.


Исключение

Ключевое слово exclude может использоваться в виде списка файлов и каталогов, разрешено использовать маску в виде символа *. Пример использования:

exclude:
    - actions/common/*.js
    - actions/libs/*/utils.js

Общий пример совместного использования include и exclude:

packages:
    zipactionwithexclude:
        actions:
            greeting1:
                function: actions
                runtime: nodejs:6
                exclude:
                    - actions/*
                include:
                    - ["actions/common/utils.js", "common/utils.js"]
                    - ["actions/index.js", "index.js"]
                    - ["actions/package.json", "package.json"]


Функции с GitHub зависимостями

OpenWhisk поддерживает зависимости, так что можно описать другие пакеты OpenWhisk, от которых зависит наш проект. При наличии таких зависимостей OpenWhisk автоматически развернет и зависимые пакеты. Любой пакет с manifest.yaml и\или deployment.yaml можно рассматривать как зависимый пакет, который может быть указан в манифесте нашего проекта. Можно описать такую зависимость в манифесте в разделе dependencies:

packages:
    RootProject:
        dependencies:
            helloworlds:
                location: github.com/apache/incubator-openwhisk-test/packages/helloworlds
        triggers:
            trigger1:
            trigger2:
        rules:
            rule1:
                trigger: trigger1
                action: helloworlds/hello-js
            rule2:
                trigger: trigger2
                action: helloworlds/helloworld-js

В этом примере helloworlds является внешним пакетом, размещенным в репозитории GitHub по адресу https://github.com/apache/incubator-openwhisk-test. Пакет helloworlds будет развернут, исходя из его файлов для развертывания в каталоге packages/helloworlds — при развертывании нашего проекта RootProject. Также есть возможность смены имени зависимого пакета, например вместо helloworlds задать ChildProject:

packages:
    RootProject:
        dependencies:
            ChildProject:
                location: github.com/apache/incubator-openwhisk-test/packages/helloworlds
        triggers:
            trigger1:
            trigger2:
        rules:
            rule1:
                trigger: trigger1
                action: ChildProject/hello-js
            rule2:
                trigger: trigger2
                action: ChildProject/helloworld-js

Можно добавить несколько зависимостей к нескольким пакетам:

packages:
    RootProject:
        dependencies:
            ChildProject1:
                location: github.com/apache/incubator-openwhisk-test/packages/helloworlds
            ChildProject2:
                location: github.com/apache/incubator-openwhisk-test/packages/hellowhisk
        sequences:
            ChildProject1-series:
                actions: ChildProject1/hello-js, ChildProject1/helloworld-js
            ChildProject2-series:
                actions: ChildProject2/greeting, ChildProject2/httpGet
        triggers:
            trigger1:
            trigger2:
        rules:
            rule1:
                trigger: trigger1
                action: ChildProject1-series
            rule2:
                trigger: trigger2
                action: ChildProject2-series


Как работает синхронизация проектов OpenWhisk между клиентом и сервером

Для ответа надо запустить развертывание в режиме managed deployment. В этом режиме OpenWhisk производит развертывание всех объектов из манифеста, а также присоединяет к каждому из них скрытое описание, т.н. managed. Это описание выглядит так:

managed:
    __OW_PROJECT_NAME: MyProjectName
    __OW_PROJECT_HASH: SHA1("OpenWhisk " +  + "\0" + )
    __OW_FILE: Absolute path of manifest file on file system

Здесь OpenWhisk является константной строчкой, а »\0» — символ NULL. size_of_manifest_file и contents_of_manifest_file зависят от файла. Последовательность развертываний одного и того же проекта в режиме managed deployment выполняет расчет нового __OW_PROJECT_HASH на клиенте для каждого объекта и сравнивает его с __OW_PROJECT_HASH объекта с этого же проекта на сервере. Дальше есть несколько вариантов.

Вариант 1: Если __OW_PROJECT_HASH совпадает на клиенте и сервере, т.е. если нет изменений проекта на клиентской стороне, то проект на сервере остается как есть, за исключением развертывания через wskdeploy новых объектов из манифеста для получения любых изменений в файле deployment.yaml.

Вариант 2: Если __OW_PROJECT_HASH не совпадает, т.е. имеются изменения на стороне клиента, то wskdeploy выполняет развертывание всех объектов из манифеста, а затем обновляет их __OW_PROJECT_HASH на сервере. Также wskdeploy выполняет поиск всех объектов: включая функции, последовательности и условные срабатывания, у которых такой же __OW_PROJECT_NAME, — т.е. принадлежащих тому же проекту, но имеющих другой __OW_PROJECT_HASH, поскольку они могли быть удалены из манифеста на клиенте. Имя проекта в манифесте является обязательным для синхронизации проекта между клиентом и сервером:

project:
    name: MyProjectName
    packages:
        package1:
            ....

Объекты в OpenWhisk, являющиеся частью проекта, но для которых выполняется развертывание с использованием других инструментов или средств автоматизации, остаются неизменными при их удалении из проекта. Они считаются внешними объектами и поэтому не перезаписываются. Развернуть такой проект можно с помощью wskdeploy, направляя его на файл манифеста, в котором есть только имя проекта:

project:
    name: MyProjectName

Давайте посмотрим на пример проекта для понимания managed deployment. В этом репозитории можно посмотреть проект с различными манифестами, которые показывают managed deployment.


Шаг первый

Выполняем развертывание MyFirstManagedProject, используя режим managed deployment:

$wskdeploy -m tests/src/integration/managed-deployment/manifest.yaml --managed
Deployment completed successfully.

axbe_koglnzos01ssevcmmk9g_u.png
Список объектов, развернутых на сервере OpenWhisk

tioaqetzsdjcqc12la0vuesuwqe.png
Описание объекта


Шаг второй

Синхронизируем клиент и сервер — удаляем ManagedPackage-2:

./wskdeploy -m tests/src/integration/managed-deployment/00-manifest-minus-second-package.yaml --managed
Deployment completed successfully.

o6_p92dnflbyzpvss9b4-4gh_os.png
Список объектов после удаления в MyFirstManagedProject


Шаг третий

Синхронизируем клиент и сервер — удаляем последовательность ManagedSequence-2:

./wskdeploy -m tests/src/integration/managed-deployment/01-manifest-minus-sequence-2.yaml --managed
Deployment completed successfully.

kcekw6zgqnyymwsfgujvqpchbdo.png
Список объектов после удаления


Шаг четвертый

Удаляем функцию Helloworld-3:

./wskdeploy -m tests/src/integration/managed-deployment/02-manifest-minus-action-3.yaml --managed
Deployment completed successfully.

yp_kmmd9xzftrdtcedme75zqcyg.png
Список объектов после удаления


Шаг пятый

Удаляем ManagedPackage-1:

./wskdeploy -m tests/src/integration/managed-deployment/04-manifest-minus-package.yaml --managed
Deployment completed successfully.

fri9hlmidofria2oa5aj_5immg8.png
Остальные объекты в MyFirstManagedProject


Другие статьи цикла

Бессерверные вычисления на основе OpenWhisk, часть 1
Бессерверные вычисления на основе OpenWhisk, часть 2
Бессерверные вычисления на основе OpenWhisk, часть 3
Бессерверные вычисления на основе OpenWhisk, часть 4

© Habrahabr.ru