雑なA型によるクラウドとモバイルと運営と

大阪でInnovation EGGを運営している人のBLOGです。時々更新します〜

【cloudpack 大阪 BLOG】MSPのシステム化について Google Apps ScriptからPagerDutyに通知する

 

現在cloudpackではPagerDutyを試験的に導入していってます。

PagerDutyとは何ぞや?と言う方への説明や導入方法を記載すべきところですが

それはおいおいしていくとして(置いといてよいのかw)、

今回は「Google Apps ScriptからPagerDutyに通知する」を試します。

※ほとんど備忘録です

 

まずは通知先のPD側の設定を行います。下記画面で設定します。

f:id:unioce:20150603161550p:plain

 

そして設定した画面のService API Keyの情報をメモします。

f:id:unioce:20150603161742p:plain

 

でGASのプログラミング部分は下記になります

function sendToPagerDuty(service_key) {
    var url = "https://events.pagerduty.com/generic/2010-04-15/create_event.json";
    var data = {
        "service_key": service_key,
        "event_type": "trigger",
        "description": "FAILURE for production/HTTP on machine srv01.acme.com",
        "client": "Sample Monitoring Service",
        "client_url": "https://monitoring.service.com",
        "details": {
            "ping time": "1500ms",
            "load avg": 0.75
        },
        "contexts":[
            {
                "type": "link",
                "href": "http://acme.pagerduty.com",
                "text": "View the incident on PagerDuty"
            },{
                "type": "image",
                "src": "https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1"
            }
        ]
    };
    var payload = JSON.stringify(data);
    var headers = {"Accept":"application/json",
        "Content-Type":"application/json",
        "Authorization":"Basic _authcode_"
    };
    var options = { "method" : "POST",
        "contentType" : "application/json",
        "headers" : headers,
        "payload" : payload
    };
    try {
        var response = UrlFetchApp.fetch(url, options);
        var json = JSON.parse(response.getContentText());
        Logger.log("status:" + json.status + " message:" + json.message + " incident_key:" + json.incident_key);
    } catch(e){
        var error = e;
        Logger.log("message:" + error.message + "fileName:" + error.fileName + "\nlineNumber:" + error.lineNumber + "\nstack:" + error.stack);
    }
}

※サンプルコードをそのままGASに移植していますので内容が気になる方は

PagerDuty Developer

を参考にしてください。

 

上記を実行すると

f:id:unioce:20150603163144p:plain

のような画面がPDの画面にインシデントとして表示されます。

Google Apps Scriptから”インシデントとしての”の登録はまず無いとは思いますが

PDのインシデント管理機能とエスカレーション機能を使って

作業をその日の当番の方に明示的に通知とかも出来るので、

計画作業(Googleカレンダーに登録されている作業)や日次・週次的な作業の通知&

状態管理なんかに使いたいと思っています(今までHubotに任せていた状態管理部分をPDへ)。

 

基本的な使い方ではなく、いきなり応用編になってしまいましたが、PagerDutyは

スケジュール作成の部分に癖?(シンプルすぎるので一手間必要でここをどう対応したかはまた今度)もありますが、試す分には非常に簡単ですので試してみてください。

 

【cloudpack 大阪 BLOG】MSPのシステム化について GMailとGoogle スプレッドシートとslack連携

 

本来なら

「MSPのシステム化について Hubotでの状態管理(mysql)とGoogleカレンダーとslack連携 その2」

なのですが、自身のHubot熱が急速に冷めて行っている&JAWS-UG大阪で廣山さんが

発表した資料

ニイヨンサンロクゴ

の中に記載されているPagerDutyの導入を比企のほうで進める事になり、

Hubot&MYSQLで対応している部分をPagerDutyに置き換えよう熱が高まったので

その2は延期?させて貰います汗。

※HubotのNode.jsのコールバック地獄がMSPしながらだと

※どうしても手軽に実現できない事情もあります(単純に開発だけしてたら良い時期は

※今考えたら楽でしたねw)

 

今回はGMailGoogle スプレッドシートとslack連携について

お題は

①Backlogの担当者が未設定のものをslackに通知する

②監視サーバーからのアラートメールに状況(Google スプレッドシートに記載しているものをマッチング)を送付してslackに通知する

です。

 

まず①ですが、処理する方法は

Gmailにアクセスする(1/7):初心者のためのGoogle Apps Scriptプログラミング入門

を見て頂ければ処理の仕方はわかるので、上記を参照して頂ければと思いますが

引っかかった部分としてgmailAPIを個別にCALLにしてしまうと、

メールが少ない場合は問題ないのですが、②も含め、cloudpackはメールの量が多いので

GASの処理可能時間を簡単に消費して24時間経たないと処理が再実行されない状況になります。

var thds = GmailApp.search('検索条件');
var thd = thds[n];
var msgs = thd.getMessages();
var msg = msgs[m];
var from = msg.getFrom();
var to = msg.getTo();
var date = msg.getDate();
var subject = msg.getSubject();
var body = msg.getBody();

などは処理上仕方ないのですが

msg.markRead();
GmailApp.moveMessageToTrash(msg);

などメールの個別のAPIをコールせずに

GmailApp.markThreadsRead(thds);
GmailApp.moveThreadsToTrash(thds);

などのスレッドに対するAPIで処理をまとめて行うようなAPIをコールして処理を行わないと処理が回らなくなりました。

また、処理の実行間隔のタイミングはGASは1分・5分・10分・15分しか設定できないので、

var d1 = new Date();
if((d1.getMinutes()%2) != 0) return; 

 関数の先頭で、上記コードで2分間隔ぐらいで実施するように修正しました。

※地味だけど本当に処理時間に気をつけないといけないので汗

 

またこれも処理時間ネタですが②に関してもGoogle スプレッドシート

スプレッドシート利用の基本(1/5):初心者のためのGoogle Apps Scriptプログラミング入門

のサンプルで実装するととりあえず動くものは簡単に出来るのですが、

これもGoogle スプレッドシートの処理APIを毎回コールすると、処理の量が多いと

すぐにあふれてしまうので、

var data = getMainSheet().getRange(取得開始業,取得開始列,取得終了行,取得終了列).getValues();

スプレッドシートの値を範囲指定で取得し

data[取得行][取得列] 

な配列でアクセスして処理時間を短くする必要があります。
※処理時間は関数単位ではなく、アカウント全体での時間になるので自分が作った関数だけちょっと重いけどまあいいやなんて
※感じでその場限りで対応すると後でトラブった時に詰んでしまいますので処理時間は必ずスクリプトのメニューの表示→実行トランスクリプト
※処理時間を見ながら計算してください。

 

そしてslackへの投稿部分ですが

f:id:unioce:20150601190343p:plain

でslack側で出力先のチャンネル名を設定し、登録後のURLをメモしておき 

 UrlFetchApp.fetchを使って下記コードでslackへ送信します。

function sendToSlack(channel,body) {
    var r;
    try {
        var url = "メモしたslackのURL";
        var data = {"channel": channel, "username": "bot名称", "text": body, "icon_emoji": ":smiling_imp:"};
        var payload = JSON.stringify(data);
        var headers = {"Accept":"application/json",
        "Content-Type":"application/json",
        "Authorization":"Basic _authcode_"
    };
    var options = { "method" : "POST",
        "contentType" : "application/json",
        "headers" : headers,
        "payload" : payload
    };
    var response = UrlFetchApp.fetch(url, options);
    } catch(e){
        var error = e;
        Logger.log("message:" + error.message + "\nfileName:" + error.fileName + "\nlineNumber:" + error.lineNumber + "\nstack:" + error.stack);
        r = null
        return false;
    }
    return true;
}

 

なお現在ですが

Hubotによる自動化の推進は止めて、PagerDutyの運用を24365の

MSP業務に合わせる為に導入設計・導入を対応していますが、

PagerDutyの導入が一段落したらPagerDutyをベースに既存機能の置き換えや

PagerDutyによるMSP業務の見える化を実施する予定です

うまく今あるサービスを活用し、足りない部分を実装して運用の負荷を減らして

いきたいと思います。

PagerDutyの解説(運用方法)もニーズがあれば投稿します

 

運用縛りのテーマ「オペレーションじょうず」を開催しました

5月23日(土)ですが、テーマを運用縛りにした「オペレーションじょうず」を主催しました。

 

毎回開催のテーマに関してはあまり悩まない(だいたいニーズがあるものは読める)

のですが、今回はちょっとどうなるのかと自問自答しながらの運営でした。

 

ただ、自分が去年の4月にcloupackに入り、6月から構築/運用で24/365のチームに

配属されてから運用に入った時にイメージと現実が隔離している事に愕然として

DevOpsだよね〜なんて軽口叩いていた開発から運用への自分の勘違いをテーマに

AWS Summit Tokyo 2014内部のJAWS-UGでLTして懺悔?なんかもしてたのですが、
もっと踏み込んで運用をテーマにしたいと思い、今年に入って
Software Design 2015年2月号『なぜ「運用でカバー」がダメなのか』の
運用設計ラボの波田野さんの記事を読んで、我が意を得たりという感じで、
波田野さんに連絡し、「オペレーションじょうず」の開催を決めました。
 
他の講師が集まらないなら波田野さんと比企だけでもやってやるって意気込みでしたが
気がついたらたくさんの講師の方が賛同してくれ、講師達の内容にたくさんの参加者が
エントリーして頂けました。
 
今回もMOTEXさんに会場提供をして頂きました。

f:id:unioce:20150525172047j:plain

会場に関してはコミュニティが勉強会を開催する時に最もネックな部分なので
MOTEXさんのようなコミュニティを支援してくれる企業さんがどんどん増えてきてほしいですね。

f:id:unioce:20150525172504j:plain

会場の様子。映画館のような施設。いつ来てもスゴいなーと思います。
 

f:id:unioce:20150525172650j:plain

そして人が入った状態。160名弱の方が参加して頂きました。
 

f:id:unioce:20150525172834j:plain

比企として今回非常に喋りたかったのですが、諸事情によりオープニングとクロージングのみ。第二弾では是非話そうと思います。
 

f:id:unioce:20150525174333j:plain

金春さんがAWS re:Invent 2015の関西からのツアーの説明を無茶ぶりされたようで急遽し説明。
 
 

f:id:unioce:20150525173224j:plain

TOPバッターはもちろん波田野さん。

資料には記載できてない話があるのですが、そこがオペレーションじょうずの

裏テーマだったんですが自重らしくUP資料からは割愛されていますw

こういうケースもあるので、是非皆様現地にお越し下さいw

 

f:id:unioce:20150525173230j:plain

東急ハンズの運用削減事例と方針」をハンズラボ株式会社の田部井さん

 

f:id:unioce:20150525173430j:plain

「CG映像制作におけるAWSの活用(スポットインスタンスによるCGレンダリング)」
を株式会社石田大成社の戸崎さん。

創業の100年の老舗がAWSレンダリングでさらにスポットインスタンス

資料の公開が待たれますが、サミットでもお話するようで、資料はそれ以降ですね

 

f:id:unioce:20150525174526j:plain

チャットワーク藤原さん。
 

f:id:unioce:20150525174534j:plain

吉田姓の為に登壇するcloudpack吉田ヒロカズさんw

 

f:id:unioce:20150525174546j:plain

開発者でもインフラ屋でも仕事の進め方は同じと語るcloudpackの廣山さん
 

f:id:unioce:20150525174706j:plain

サーバーワークスの柳瀬さんは「AWSAPIを使うときに気をつけていること」

 

f:id:unioce:20150525183924j:plain

f:id:unioce:20150525183942j:plain

JAWSUG大阪の森さんの登壇後に波田野さんが森さんの内容についてトークする瞬間。

 

f:id:unioce:20150525174719j:plain

とりはNRIネットコム株式会社の佐藤さん。 
スライドシェア公開後にいきなり10000Overしたようです。
 
全体の雰囲気は

でも記載されているので、資料とまとめを見て頂ければと思います。

※開催中に色々と調整する必要があって解説できるほど聴けなかった汗
※時間が出来た時に資料を追いますがまずは会場の雰囲気などを
※伝えたかったので様子だけ記載。
 
 そして無事の終わって懇親会。会場は50名が限界だったのですが無理矢理51名。
他にも何名か来そうでしたが、残念ながら会場はオンプレミスなので汗

f:id:unioce:20150525184805j:plain

f:id:unioce:20150525184809j:plain

f:id:unioce:20150525184813j:plain

 
 そして懇親会二次会。ここでも21名のかたが参加

f:id:unioce:20150525185331j:plain

 
 今回ですが、前回までの興味や学習という関心ではなく、自分達の現場に直結している
内容だったようで、参加者の皆様がいつにも増して真剣でかつTwitter上でもつぶやいて頂き、主催者としても開催してよかったなと思いました。
 
DevOpsと言う言葉はありますが、開発だけしていても運用の気持ちはわからないと言うのを、cloudpackに入ってからしみじみと思っていて、その気持ちを表現したのが
今回の「オペレーションじょうず」。
ニーズも大きいようですので第二弾も企画したいと思います(是非他の拠点のJAWSUGでも実施してほしいです)。

f:id:unioce:20150525190335j:plain

 
あと今回は運用縛りってとこではないですが、
前回に引き続き運営の運用コストも最低限にしました。
今回も大型の勉強会になりますが、メイン1名で当日スタッフ数名。
こちらの想いに関しては次のブログで語りたいと思います。
 

【cloudpack 大阪 BLOG】MSPのシステム化について Hubotでの状態管理(mysql)とGoogleカレンダーとslack連携 その1

 

一年間寝かし(放置してしまった汗)ておいた

innovationegg.doorkeeper.jp

をようやく実施できるようになりました汗。

クラウドユーザーグループ7つ+αによる合同勉強会。

去年の2月のXEggはJAWS-UGとJAZUGのみで、他のコミュニティの方にも

ご協力いただきましたが、今回はクラウドユーザーグループメインでの実施が

可能になりました。

1年でたくさんのクラウドユーザーグループが産まれたのはうれしいですね。

 

f:id:unioce:20150406155925p:plain

 

今回はHUBOTとmysqlgoogle カレンダーとgoogle apps script(以降GAS)とslackの連携となります。

今回実施したかった事は、計画的作業の見える化です。

cloudpackでは計画的な時刻指定の作業は、

backlogやgoogle カレンダーに記載して対応していましたが、

担当者の作業忘れや作業を取りかかっているかどうかが他の人(cloudpack依頼者)から

見えないなど、結果的には出来ていたのですが、もやっとした部分があったので

日次処理のつぶやきのように、slackへのカレンダーの通知(作業着手URL付き)と、

作業着手を受け付けたのを表示するつぶやきと、作業着手できない場合は、

定期的に出来ていない事を通知する機能を実装する事にしました。

 

今回はgoogleカレンダーの連携にGASを利用しました。

hubotからgoogleカレンダーAPIを呼ぶのも出来るようですが、

自身の理解力が無いのか、情報が古いのかわかりませんが、

少しやってうまくいかなかったので、GASを使って

googleカレンダーの連携を行います。

 

GASでの方法は、単純で、定期的にgoogleカレンダーにアクセスして

つぶやきたい時間の範囲のカレンダーのデータを取得し、hubotに投げるだけです。

※以降の処理はhubotでさせる。

 

GASでカレンダーに連携するのは下記のURLを参考にさせて頂きました。

Googleカレンダーにアクセスする(1/5):初心者のためのGoogle Apps Scriptプログラミング入門

※GASですが、非常に便利ですが、色々と罠(仕様)や不具合が潜んでいます。

※簡単お手軽で、サンプルを張ってちょっと修正して動いちゃうのですごく良いのですが

※そこだけを見ていると痛い目に遭いますので注意(今回の範囲ではなかったので記載しません)

 

//カレンダー連携
function googlecalender() {

    var calendarId = "抽出したいカレンダーのID";
    var cal = CalendarApp.getCalendarById(calendarId);
    var urlstr = "連携先のhubotのurl";

    // 現在時刻を生成
    var date = new Date();
    var d1 = new Date();
    d1.setMinutes(d1.getMinutes());
    var d2 = new Date;
    d2.setMinutes(d2.getMinutes() + 15);
    var evts = cal.getEvents(d1,d2);
    var body = "";

    if (evts.length > 0){
        for (var i in evts){
            var evt = evts[i];
            if(!evt.isAllDayEvent()){ //終日では無い場合
                var creators = evt.getCreators();
                var responsible = "@" + creators[0].slice(0,creators[0].indexOf("@")) + " (イベント作成者)";
var guests = evt.getGuestsStatus();

                if (guests.length >= 1){//招待者(担当)が登録されていたら招待者を担当者として上書き
responsible = guests[0].getEmail()
responsible = "@" + responsible.slice(0,responsible.indexOf("@")) + " (招待者)"
                }
                var Id = evt.getId()
                var Title = evt.getTitle()
                var Description = evt.getDescription()
                var StartTime = Utilities.formatDate( evt.getStartTime(), 'JST', 'yyyy年M月d日HH時間mm分')
                var EndTime = Utilities.formatDate( evt.getEndTime(), 'JST', 'yyyy年M月d日HH時間mm分')
                var StartTimeInt = evt.getStartTime();
                StartTimeInt.setMinutes(StartTimeInt.getMinutes() - 3);//処理時間の誤差を許容する為に
                StartTimeInt = Utilities.formatDate( StartTimeInt, 'JST', 'yyyyMMddHHmm')
                var EndTimeInt = Utilities.formatDate( evt.getEndTime(), 'JST', 'yyyyMMddHHmm')

                var query =
                {
                "Id" : Id,
                "Title" : Title,
                "Description" : Description,
                "StartTime" : StartTime,
                "EndTime" : EndTime,
                "StartTimeInt" : StartTimeInt,
                "responsible" : responsible,
                "EndTimeInt" : EndTimeInt
                };

                var options =
                {
                "method" : "post",
                "payload" : query
                };
                UrlFetchApp.fetch(urlstr, options);
            }
        }
    }
}

 な感じで、GASは実装させて頂きました。

 

実装の詳細ですが

var calendarId = "抽出したいカレンダーのID";
var cal = CalendarApp.getCalendarById(calendarId);

で、抽出したいカレンダーにアクセスします。


var date = new Date();
var d1 = new Date();
d1.setMinutes(d1.getMinutes());
var d2 = new Date;
d2.setMinutes(d2.getMinutes() + 15);

取得したい期間を作成し

var evts = cal.getEvents(d1,d2);

その期間のカレンダーデータを取得します。

evts.length

カレンダーのイベントがあるのかを確認し

for (var i in evts){

データがあればでループさせ、

var evt = evts[i];

一つ一つのカレンダー情報にアクセスします。

evt.isAllDayEvent()

カレンダーが終日かを確認し、終日でなければ

var creators = evt.getCreators();


var EndTimeInt = Utilities.formatDate( evt.getEndTime(), 'JST', 'yyyyMMddHHmm')

hubotに投げる情報を作ります。

そして

var query =
{
"Id" : Id,
"Title" : Title,
"Description" : Description,
"StartTime" : StartTime,
"EndTime" : EndTime,
"StartTimeInt" : StartTimeInt,
"responsible" : responsible,
"EndTimeInt" : EndTimeInt
};
var options =
{
"method" : "post",
"payload" : query
};
UrlFetchApp.fetch(urlstr, options);

 

でHubotのURLにカレンダー情報を投げます。

 

今回は③と④の情報を記載しました。

GASの"UrlFetchApp.fetch"での連携は非常に便利ですが

getとpostのみget/post/put/deleteの対応でpatchメソッド非対応なので使う方はちょっと検討する必要があります。

Google Apps Script Documentation - UrlFech Services - Class UrlFechApp

 

 

【cloudpack 大阪 BLOG】MSPのシステム化について(不定期連載) Hubotに定期的につぶやかせる&作業の管理をさせる

 

JAWS DAYS2015でマクロスを使ったLTしたら

なんて記事が出ていて、時代の先を行っているな俺とかわけのわからん優越感に

浸っていますw

 

今回は<Hubotに日次作業を定期的につぶやかせる&作業の管理をさせる>です

f:id:unioce:20150328104852p:plain

 

 

①と②のやり取りの部分は前回で基本は出来るようになります•

そしてよくHubotの最初に実装されている機能で、Slackにpingと入力したらpongと

返したりするのを少し楽しみますが・・・これからどう活用するの?で

PCの電源をそっとOFFにして、Heroku上には実装を待ち続ける、野良Hubotが

5万といるのではないかと推測されます・・・汗

 

とりあえず日次業務をつぶやかせるには、Hubotの機能だけでは無理なので

状態を管理する機能と、時間になったらHubotをつぶかす機能が必要になります。

 

今回状態を管理させるのは、Redisを使用し、

時間によるトリガーはnode.jsのモジュールである、cronモジュールを使います。

redisのインストールと設定は

sudo yum --enablerepo=epel install npm redis
sudo /etc/init.d/redis start
sudo chkconfig redis on

で、行いcronのインストールは

npm install cron

で、行いました。

これで下準備はOKでつぶやかせるには○の所に時間を入れて

cron = require('cron').CronJob
client = require("redis").createClient()

module.exports = (robot) ->

    new cron('0 0 ○ * * *', () ->
        robot.messageRoom つぶやかしたいルーム名, "MSPの皆様おはようございます。本日の日次処理担当の方、○時になりましたので日次処理の対応よろしくお願いします"
    , null, true, 'Asia/Tokyo').start()

 で出来て、redisへの値のセットは

client.set "○", 1

な感じで(○はキー名として時間で”1”はセットしたい値です)簡単に値のセットが可能です・・・が、hubotに対する指示(ここでは作業を着手した)を、毎回キーボードで

入力してって言ったら、間違いなく使ってくれない・・・と言う事で、

HubotがURLを表示して、クリックされたらHubotが受け付けてつぶやく事をやめるような、お手軽なUIの実装を行います。

Hubotはnode.jsなので

request = require 'request'

    module.exports = (robot) ->
        robot.router.get "/hubot/daily", (req, res) ->

な形で、WEBサーバーとしてURLを受け付ける事も可能ですが、

 SlackでURLを表示してしまうと、もれなくSlackのプレビュー機能で、

そのURLがコールされるというかなり残念な状況になってしまい、

表示=URLを叩かれた状態で処理が勝手に完了して、

全く無意味な状態になるので、コード上でユーザーエージェントを確認し

if req.headers['user-agent'] == 'Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)'
    return

slackからのプレビュー表示なら、処理を抜けるような実装をしてあげる必要があります。

で後は

client.get "○",(err, val) ->
    if err
        return
    # エラーが無ければデータを取得
    if val == 1
        client.set "○", 0
        robot.messageRoom "ルーム名","*○時の日時処理の受付完了しました*"

な感じで、redisから値を取得して状態を見て、問題なければつぶやくような実装をすれば

f:id:unioce:20150327193420p:plain

な感じでつぶやいてくれるようになります。

 

なお今回の機能を実装するにあたり下記のページを参考にさせて頂きました

私のブログは実装側を目的としているのではなく、

課題に対するアプローチがメインなので、ざっくりとした形で抜粋させてもらっていますので、実装は色んなBLOGを参考にして頂ければと思います。

 

今回の実装の仕方は、Hubotが初めてでもあったのと、見たサンプルを元に

実装していったために、作りが固定で汎用性が無いものになっていますが

とりあえず24/365を目標にずっと稼働しているので、今のところは目的を果たしていますし、担当者が作業を忘れていても、Slackを見ている他の人が気がつくようになっています。

クリックしないと、10分おき(時間が遅くなると5分おき)につぶやくので、

Slack上でのやり取りを中断してしまう?って話もありましたが、

気がつけばすぐにクリックと、作業漏れや作業着手の遅れが可視化されたので、

とりあえずはよしかなと思っていますが、よりMSPや他のメンバーにわかりやすくできる案が頭の中では出来たので、いつのタイミングかで実装/投入したいと思います。

 

次回はこの機能の実装を汎用化させた、

googleカレンダーgoogle app scriptとhubotとslackの連携を書きます。