Несколько недель назад я написал (Мониторинг активности OpenWhisk) о том, как вы можете отслеживать свою активность OpenWhisk. Одна из вещей, на которую я обратил внимание, это то, что было бы неплохо иметь систему предупреждений, в которой я мог бы указать, что если определенное действие начинает выполняться плохо, я могу получить предупреждение. Слово бедный довольно расплывчатое, но сегодня я работал над небольшой демонстрацией, которой хочу поделиться. Он использует сам OpenWhisk для мониторинга ваших действий OpenWhisk, но в целом меня больше беспокоит сбой моего кода или сбой сторонних API, чем сам OpenWhisk.

Я начал с создания базового действия, которое возвращало бы true, если бы действие срабатывало. Позвольте мне поделиться кодом, а затем я объясню, как он работает.

/*
For an action X
Taking all the activations I can get in one pull
Up from optional time Y
If the success rate is below Z% (default 100), 
return true
else return false

action: action to check
from: ms since epoch to start from, defaults to forever
rate: numeric value 0-100, defaults to 100
max: total # of activations, defaults to MAX_LIMIT
*/

const openwhisk = require('openwhisk');
// last time i checked, this was 200
const MAX_LIMIT = 200;

function main(args) {
    const ow = openwhisk();

    if(!args.action) {
        return {
            error:'Action argument not specified.'
        };
    }

    if(!args.from) {
        args.from = 0;
    }

    if(!args.rate) {
        args.rate = 100;
    }

    if(!args.max) {
        args.max = MAX_LIMIT;
    }

    console.log(`Checking action ${args.action} starting at time ${args.from} and expecting a success rate of ${args.rate}%`);

    let status = false;

    return new Promise( (resolve, reject) => {
        ow.activations.list({
            name:args.action,
            limit:args.max,
            since:args.from,
            docs:true
        }).then((results) => {
            //early out
            if(results.length === 0) resolve({status:status, action:args.action});
            let total = results.length;

            let totalGood = results.reduce( (acc, val) => {
                if(val.response.success) return ++acc;
                return acc;
            },0);
            let successRate = Math.floor(totalGood/total*100);
            console.log(`${total} activations, ${totalGood} good, for a rate of ${successRate}%`);
            if(successRate < args.rate) status = true;
            resolve({status:status, action:args.action});
        }).catch(err => {
            console.log('error', JSON.stringify(err));
            reject({error:err});
        });
    });

}

Итак, действие ожидает следующие аргументы:

  • Прежде всего, действия по проверке. Обратите внимание, что код использует активации для отслеживания работоспособности действий, и в последний раз, когда я проверял, была ошибка с получением активаций для действия в пакете. Очевидно, что если это все еще ошибка, она будет исправлена, но имейте это в виду при использовании этого кода.
  • Аргумент from — это количество миллисекунд от эпохи, используемое в качестве фильтра для выборки данных.
  • Аргумент max — это общее количество предыдущих активаций для проверки. В общем, я ожидаю, что люди будут использовать это поверх from, кроме того, значение, которое вы используете здесь, будет полностью зависеть от вашего использования. Так, например, если у меня есть действие, которое выполняется каждый час, и если я хочу проверять работоспособность каждый день в триггере CRON, я бы установил максимальное значение 24.
  • Аргумент rate представляет собой процентное значение, представляющее здоровое состояние. По умолчанию он равен 100%, что, вероятно, слишком много, но опять же, это зависит от вашего конкретного использования.

Я использую пакет OpenWhisk npm. Обратите внимание, что мне не нужно указывать информацию для аутентификации, так как я создаю ее экземпляр в самой функции main. Это означает, что он подберет значения аутентификации для учетной записи. Затем я получаю активации, используя фильтры, как описано выше. Обратите внимание, что существует недокументированное ограничение в 200 активаций. Вы можете получить больше (я документирую это здесь), но, поскольку вы контролируете, как часто выполняется эта проверка, вы можете указать расписание, которое гарантирует, что вы не столкнетесь с этой проблемой.

Как только у меня есть активация, я перебираю, подсчитываю хорошие, а затем использую немного математики, чтобы получить процент. Если у вас слишком низко, то мы ошибаемся. Я назвал это действие isBad, и я знаю, что использовать подобное отрицание нехорошо. В этом случае истинный статус этого действия подразумевает, что у нас есть проблема, и это вроде как имеет смысл для меня, но я также мог бы представить это как isGood и обратить результаты. Часть меня, «Чистый код», расстроилась из-за этого, но часть меня, которая «сделает все», сказала ей закрыть входную дверь.

Обратите внимание, что я возвращаю статус и запрошенное действие. Я мог бы также вернуть общие и общие хорошие значения.

Так что в теории… в основном это так. Человек, использующий это, объединит это с другим действием, чтобы обработать результат и что-то сделать. Итак, для моего первого примера я объединил его с простым действием электронной почты:

/*
requires:
    email (will be used for to/from)
    action (name)
    sgkey (sendgrid key)

I send a hard coded message saying args.action has failed

Note - since we can't do conditional actions in sequences, this
action leaves early if status:false
*/

const helper = require('sendgrid').mail;

function main(args) {

    if(!args.status) return {result:0};

    let SG_KEY = args.SG_KEY;

    let from_email = new helper.Email('[email protected]');
    let to_email = new helper.Email(args.email);
    let subject = 'Failure Rate Alert: '+args.action;

    let mainTemplate = `
The action, ${args.action}, has fallen beneath its desired success rate.
Take action!
`;

    let mailContent = new helper.Content('text/html', mainTemplate);
    let mail = new helper.Mail(from_email, subject, to_email, mailContent);

    let sg = require('sendgrid')(SG_KEY);
    
    var request = sg.emptyRequest({
        method: 'POST',
        path: '/v3/mail/send',
        body: mail.toJSON()
    });

    return new Promise((resolve, reject) => {
        sg.API(request, function(error, response) {
            if(error) {
                console.log('error in sg', error.response.body);
                reject({error:error.message}) 
            } else {
                resolve({result:1});
            }
        });

    });

}

В этом действии я просто использую SendGrid для отправки электронной почты. Шаблон невероятно прост. А вот это при выполнении (опечатку с it/it's я исправил позже):

А потом, черт возьми, я написал действие Twilio, чтобы я мог отправить SMS:

/*
as args, needs:
accountSid
authToken
to 
from

*/
const twilio = require('twilio');

function main(args) {

    var client = new twilio(args.accountSid, args.authToken);
    let body = `The action, ${args.action}, has fallen beneath its desired success rate. Take action!`;

    return new Promise((resolve, reject) => {
        client.messages.create({
            body: body,
            to: args.to,  
            from: args.from
        })
        .then((message) => {
            resolve({result:1});
        })
        .catch(err => {
            console.log(err);
            reject({error:err});
        });
        
    });
}

Для этого требуется информация для аутентификации вашего приложения Twilio и номер для отправки, а также номер для отправки. А вот в действии:

И это в основном все. Чтобы сделать это живым, я бы настроил триггер CRON и выяснил, как я хочу получать уведомления, но, поскольку мой код идеален, мне не нужно об этом беспокоиться. Уот! Вы можете найти исходники всех трех действий здесь: https://github.com/cfjedimaster/Serverless-Examples/tree/master/alert

Первоначально опубликовано на сайте www.raymondcamden.com 27 июня 2017 г.