Эта статья продолжает цикл переводных заметок об 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 " + <size_of_manifest_file> + "" + <contents_of_manifest_file>)
__OW_FILE: Absolute path of manifest file on file system
Здесь OpenWhisk является константной строчкой, а "" — символ 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
Автор: Павел