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

Эта статья продолжает цикл переводных заметок об 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.

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

Описание объекта
Шаг второй
Синхронизируем клиент и сервер — удаляем ManagedPackage-2:
./wskdeploy -m tests/src/integration/managed-deployment/00-manifest-minus-second-package.yaml --managed
Deployment completed successfully.

Список объектов после удаления в MyFirstManagedProject
Шаг третий
Синхронизируем клиент и сервер — удаляем последовательность ManagedSequence-2:
./wskdeploy -m tests/src/integration/managed-deployment/01-manifest-minus-sequence-2.yaml --managed
Deployment completed successfully.

Список объектов после удаления
Шаг четвертый
Удаляем функцию Helloworld-3:
./wskdeploy -m tests/src/integration/managed-deployment/02-manifest-minus-action-3.yaml --managed
Deployment completed successfully.

Список объектов после удаления
Шаг пятый
Удаляем ManagedPackage-1:
./wskdeploy -m tests/src/integration/managed-deployment/04-manifest-minus-package.yaml --managed
Deployment completed successfully.

Остальные объекты в MyFirstManagedProject
Другие статьи цикла
Бессерверные вычисления на основе OpenWhisk, часть 1
Бессерверные вычисления на основе OpenWhisk, часть 2
Бессерверные вычисления на основе OpenWhisk, часть 3
Бессерверные вычисления на основе OpenWhisk, часть 4
