himanago

Azure・C#などのMS系技術やLINE関連技術など、好きな技術について書くブログ

LINE Bot を Azure Functions (Node.js) で作る際のオウム返しテンプレ

Azure Functions の Node.js(JavaScript)を使って LINE Bot を作る際、LINE の公式 SDK を使った例がぱっと見つからなかったので、試してみました。

おうむ返しのテンプレ

基本的には公式SDKのサンプルのとおり。

Express に依存した書き方なので、azure-function-express を使えば Azure Functions でそのまま使える。

Clova のときのように req.bodyreq.rawBody で置き換えたりする必要もなし。Durable Functions とかも素直に使えそう。

'use strict';

const line = require('@line/bot-sdk');
const createHandler = require("azure-function-express").createHandler;
const express = require('express');

// create LINE SDK config from env variables
const config = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
  channelSecret: process.env.CHANNEL_SECRET,
};

// create LINE SDK client
const client = new line.Client(config);

const app = express();

app.post('/api/LineBotEndpoint',
    line.middleware(config),
    (req, res) => {
        Promise
            .all(req.body.events.map(e => handleEvent(e, req.context)))
            .then((result) => res.json(result))
            .catch((err) => {
                req.context.log.error(err);
                res.status(500).end();
            });
    }
);

// event handler
function handleEvent(event, context) {
  context.log(event.type);

  if (event.type !== 'message' || event.message.type !== 'text') {
    // ignore non-text-message event
    return Promise.resolve(null);
  }

  // create a echoing text message
  const echo = { type: 'text', text: event.message.text };

  // use reply API
  return client.replyMessage(event.replyToken, echo);
}

module.exports = createHandler(app);

※2022.3 ログ出力に context.log を使用していなかったので修正。

環境変数

CHANNEL_ACCESS_TOKEN とかは Azure ポータルからアプリケーション設定として追加しておく。 f:id:himanago:20200423204848p:plain

まとめ

わざわざ記事にするまでもないレベルですが、Azure Functions でもそのまま動きます、ということで。

Durable Functions を使った Clova スキルを JavaScript で開発する方法

はじめに

Durable Functions は Azure Functions の拡張機能で、持続性のあるステートフルな処理をサーバーレスの環境で実現するものです。
普段は C# で開発しているので使ったことはなかったのですが、Durable Functions は JavaScript でも使えます。

docs.microsoft.com

今回は C# で作っていた Durable Functions を使ったサンプルスキルを JavaScript に移植してみました。
単純にいくかなーと思いきや、ちょっと大変だったのでどう実装したか残しておきます。

移植元の C# 製サンプルスキル

Clova スキル開発用 C# SDK のサンプルとして公開しているものです。

clova-extensions-kit-csharp/clova-extensions-kit-csharp-azure-functions/Durable at master · line-developer-community/clova-extensions-kit-csharp · GitHub

動きとしてはシンプルなもので、起動すると「いくつ数えますか?」と聞いてくるので、ユーザーは秒数を指定。
すると裏で関数オーケストレーションが開始し、指定秒数のカウントを行うアクティビティ関数が実行され、スキルが終了。
再度スキルを起動したとき、オーケストレーターの状態を確認し、カウントが終わっていなければ「まだ数え終わっていません」と言ってくれます。
このような、スキルを終了しても裏でステートフルな処理が動き続けていて、その状態を後からも確認できる…というサンプルです。

JavaScript への移植

先にコード全体を載せます。

Starter(HTTPトリガーの Clova スキルエンドポイント)

const df = require("durable-functions");
const clova = require('@line/clova-cek-sdk-nodejs');
const createHandler = require("azure-function-express").createHandler;
const express = require("express");

// Durable Client 用の変数
let durableClient;

const clovaClient = clova.Client
    .configureSkill()
    .onLaunchRequest(async responseHelper => {
        const status = await durableClient.getStatus(responseHelper.getUser().userId);

        if (status.runtimeStatus === 'ContinuedAsNew' ||
            status.runtimeStatus === 'Pending' ||
            status.runtimeStatus === 'Running')
        {
            responseHelper.setSimpleSpeech(
                clova.SpeechBuilder.createSpeechText('まだ数え終わっていません。')).endSession();
        }
        else
        {
            responseHelper.setSimpleSpeech(
                clova.SpeechBuilder.createSpeechText('いくつ数えますか?'));
        }
    })
    .onIntentRequest(async responseHelper => {
        const intent = responseHelper.getIntentName();

        switch (intent) {
            case 'CountIntent':
                const count = responseHelper.getSlot('count');

                // オーケストレーターを起動
                await durableClient.startNew('CountOrchestrator', responseHelper.getUser().userId, count);
            
                responseHelper.setSimpleSpeech(
                    clova.SpeechBuilder.createSpeechText('数え始めました。しばらくお待ちください。')
                ).endSession();
                break;

            case 'Clova.YesIntent':
                responseHelper.setSimpleSpeech(
                    clova.SpeechBuilder.createSpeechText('はいはい')
                );
                break;
            case 'Clova.NoIntent':
                responseHelper.setSimpleSpeech(
                    clova.SpeechBuilder.createSpeechText('いえいえ')
                );
                break;

            default:
                break;
        }
    })
    .onSessionEndedRequest(responseHelper => {
        // Do something on session end
    });

const clovaMiddleware = clova.Middleware({ applicationId: "YOUR_APPLICATION_ID" });

const app = express();

app.post('/api/ClovaEndpoint', 
    (req, res, next) => { req.body = req.rawBody; next(); },
    clovaMiddleware,
    (req, res, next) => {
        (async () => {
            // Durable Client をセット
            durableClient = df.getClient(req.context);

            // https://github.com/line/clova-cek-sdk-nodejs/blob/master/src/client.ts
            // の handle() で行っている処理
            const ctx = new clova.Context(req.body);
            const requestType = ctx.requestObject.request.type;
            const requestHandler = clovaClient.config.requestHandlers[requestType];
            await requestHandler.call(ctx,ctx);
            res.json(ctx.responseObject);
            
        })().catch(next);
    }
);

module.exports = createHandler(app);

オーケストレーター関数

アクティビティ関数を単純に呼ぶだけのもの。

const df = require("durable-functions");

module.exports = df.orchestrator(function* (context) {
    const count = context.df.getInput();
    yield context.df.callActivity('CountActivity', count);
});

アクティビティ関数

ちょっと無理やりですが指定秒数カウントする関数。カウント(=今回やりたいこと)の処理です。

module.exports = async function (context, count) {
    await (sec => new Promise(resolve => setTimeout(resolve, sec * 1000)))(count);
};

実装のポイント

公式 SDK + azure-function-express の注意点

JavaScript で Clova スキルを実装する場合は、公式の Node.js 用 SDK を使用します。

github.com

この SDK は Express に依存しているので、Azure Functions で使うためには azure-function-express というモジュールを使います。

その際、app.post の書き方に一工夫必要で、第2引数には以下のように

(req, res, next) => { req.body = req.rawBody; next(); },

と書き、Azure Functions によるリクエストボディのパースを打ち消す必要があります。1

Durable Client をハンドラー内に変数経由で引き渡す

ここがちょっと苦戦した点です。

公式SDKリポジトリ にあるサンプルなどでは

const clovaSkillHandler = clova.Client
  .configureSkill()
  .onLaunchRequest(responseHelper => {
    // (略)
  })
  .onIntentRequest(async responseHelper => {
    // (略)
  })
  .onSessionEndedRequest(responseHelper => {
    // (略)
  })
  .handle();

のように handle() して得られたハンドラーを

app.post('/clova', clovaMiddleware, clovaSkillHandler);

app.post に渡して動かしますが、これでは Durable Functions の機能が利用できません。

というのも、Durable Functions の機能を利用するためには df.getClient(req.context) といったかたちでリクエストオブジェクトをもとにして Durable Client を得る必要があるところ、Clova SDK + azure-function-express で作ると Durable Client を使いたくなる箇所である onIntentRequest などから req が参照できないからです。

そこで、(ちょっと無理やりなのですが)以下のようにローカル変数経由で Durable Functions 機能を利用できるようにしてみました。

ローカル変数 durableClient を宣言

まず最初に変数を宣言しておきます。

// Durable Client 用の変数
let durableClient;

SDK が用意した handle() を使わない

サンプルのように handle() まで行わず各種ハンドラーの定義までで止め、定数 clovaClient にセットします。

const clovaClient = clova.Client
  .configureSkill()
  .onLaunchRequest(responseHelper => {
    // (略)
  })
  .onIntentRequest(async responseHelper => {
    // (略)
  })
  .onSessionEndedRequest(responseHelper => {
    // (略)
  });

handle() の代替となる関数を自分で用意

app.post のハンドラーを渡す部分を独自の関数に置き換え、この中でローカル変数 durableClient にリクエストから取得した Durable Client をセットします。
それ以外は SDKのhandle() で行っている処理を簡略化して書くだけです。

app.post('/api/ClovaEndpoint', 
    (req, res, next) => { req.body = req.rawBody; next(); },
    clovaMiddleware,
    (req, res, next) => {
        (async () => {
            // Durable Client をセット
            durableClient = df.getClient(req.context);

            // https://github.com/line/clova-cek-sdk-nodejs/blob/master/src/client.ts
            // の handle() で行っている処理
            const ctx = new clova.Context(req.body);
            const requestType = ctx.requestObject.request.type;
            const requestHandler = clovaClient.config.requestHandlers[requestType];
            await requestHandler.call(ctx,ctx);
            res.json(ctx.responseObject);
            
        })().catch(next);
    }
);

このようにして、Azure Functions にデプロイして Starter の URL を Clova スキルに設定すると、ちゃんと動いてくれます。

まとめ

公式 SDK そのままでは Durable Functions が使えなかったのですが、SDK を読み解きつつ必要なものを引き渡していったらなんとか動かせました。せっかく JavaScript でも Durable Functions が使えるので、いろんな場面で使われてほしいな、と思います。

変数経由の受け渡しが無理やり感あるので、もうちょっといいやり方があればいいなとは思いますが、SDK の構造上しょうがないのかな…。req.rawBody で上書きしたりする必要がある点も含め、若干の使いづらさがあるので、Azure Functions で使いやすくなるように公式 SDK に PR 出してもいいのかも…。2


  1. いつも安定のかずきさんの記事を参考にしました。https://blog.okazuki.jp/entry/2018/09/09/183404

  2. 公式SDKには handle() の専用版として lambda()firebase() があるので azureFunction() があってもいいかなとか思ったり。どれだけ需要があるのかはわかりませんが。

XAML Rich Menu Maker を活かして C# で効率的な LINE Bot 開発

はじめに

LINE Bot を作る場合、コミュニティ製 の SDKC# で作ることができますが、「リッチメニュー」も C# で作ることができます。

XAML Rich Menu Maker

下記の記事にあるとおり、Visual Studio拡張機能でリッチメニューを XAML で組めます。

pierre3.hatenablog.com

Bot のバックエンドと同じソリューション内に作れば、リッチメニューの定義も Bot 本体と一緒に管理できるのでおすすめです。

アクションを C# で定義する

XAML Rich Menu Maker は C#XAML で構成されているため、Bot 本体とコードの共有ができます。

たとえば、リッチメニューではタップ時に Bot のバックエンドに Postback イベントとして data を送信することができますが、このデータを Bot 側で使用するモデルクラスと共通化することができ、かなり便利です。

サンプル

リッチメニューで動物の絵を並べ、それぞれ押すとその情報を送るような Bot を考えます。

動物のメニューをタップすると、その動物から鳴き声が返ってくるものです。

モデルクラス

Bot 本体、リッチメニューのプロジェクトのほか、Shared プロジェクトで C# のモデルクラスを用意します。

namespace RichMenuIntegrationSample.Models
{
    public class Animal
    {
        public string Name { get; set; }
        public string Sound { get; set; }
        public string IconFileName { get; set; }
    }
}

動物の名前、鳴き声、イラストのファイル名を持つことができるクラスですが、これを Bot 本体とリッチメニューのアクションでの data で共有します。

data の定義

XAML Rich Menu Maker のプロジェクト内で、共有のモデルクラスを使って JSON 化する静的なプロパティを持つクラスを定義します。

using Newtonsoft.Json;
using RichMenuIntegrationSample.Models;

namespace XamlRichMenuMaker.ActionData
{
    public static class AnimalAction
    {
        public static string Cat { get; } = JsonConvert.SerializeObject(new Animal
        {
            Name = "ねこ",
            Sound = "にゃーん",
            IconFileName = "cat.png"           
        });

        public static string Dog { get; } = JsonConvert.SerializeObject(new Animal
        {
            Name = "いぬ",
            Sound = "わんわん",
            IconFileName = "dog.png"
        });
    }
}

リッチメニューの定義

リッチメニューの定義で、この静的プロパティを x:Static でバインドします。

<Rectangle x:Name="area_1"
            Stroke="DarkGray" StrokeThickness="4">
    <local:RichMenuProperties.Action>
        <local:RichMenuAction Type="Postback"
            Label="ねこ"
            Data="{x:Static data:AnimalAction.Cat}"
            Text="ねこさん" />
        </local:RichMenuProperties.Action>
</Rectangle>

Bot のバックエンド

同じモデルクラスを使って、LINE Bot のバックエンドで処理させます。

今回、バックエンドは Azure Functions です(DI で Messaging API SDK を使うテンプレ構成)。

動物とその鳴き声ということで、新機能の icon / nickname switch も使ってみます。

C# SDK の対応と使い方については以下の記事参照↓
qiita.com

Postbackイベントを処理する部分(LineBotApp.cs)

protected override async Task OnPostbackAsync(PostbackEvent ev)
{
    // Postback で送信されたデータを共通のモデルクラスに変換
    var animal = JsonConvert.DeserializeObject<Animal>(ev.Postback.Data);

    // icon / nickname switch を使用して返信
    await LineMessagingClient.ReplyMessageAsync(ev.ReplyToken, new List<ISendMessage>
    {
        new TextMessage(
            animal.Sound, null,
            new Sender(animal.Name, $"{Environment.GetEnvironmentVariable("ImageBaseUrl")}/{animal.IconFileName}"))
    });
}

送られてきたコードをモデルクラスにデシリアライズして、後続の処理を行います。
元々このクラスで定義しているので、C# の型に正しく変換される保証があり、安心です。

動作結果

こんなかんじ!

サンプルコード

動かすときは Azure Functions にデプロイして、環境変数の設定(ChannelAccessToken、ChannelSecret、ImageBaseUrl)をしてください。

github.com

Sender のアイコン画像は Blob Storage にでも置けばOK(公開URLのルートを ImageBaseUrl に設定する)。

また、事前にリッチメニューの登録も済ませておいてください。

まとめ

C# SDK を使えばいろいろ統一できてとても楽です!

Vuetify でインタラクティブではないラベルを作る(マウスオーバーで色が変わらない)

Vuetify での、単純なラベルの作り方を調べたのでメモ。

v-chip がラベル的なものを作るコンポーネントのようですが、カーソルを当てると色が変わります。

<v-chip>ラベル</v-chip>

f:id:himanago:20200307112407g:plain

単純なラベルの場合こういったインタラクティブな変化は不要なので、なくしたい。

issue があがっていましたが、機能としては対応しない模様。

github.com

とはいえ、上記の issue に現状での実現方法が載っていました。

<v-chip :input-value="true">ラベル</v-chip>

こうすればいいみたいです。

f:id:himanago:20200307112504g:plain

LINE API Expert に認定していただきました

本日発表となりましたが、第5期の LINE API Expert に認定いただきました!

engineering.linecorp.com

LINE API Expert とは、「LINEが提供する各種APIに対する深い理解と高い技術力を持ち、コミュニティに影響力を持つエンジニア」を LINE 社が認定するプログラムです。
★参考:https://www.line-community.me/contributors/request

Clova スキルや Bot を作ってコンテストに出たり、登壇等で情報発信したりしてきたことを評価いただけ、たいへん嬉しいです。ありがとうございました。

昨年11月の Microsoft MVP 受賞に引き続きということで、今後 Azure × LINE API でおもしろいことをもっとやっていきたいなと思います。

LINE 社のみなさま、コミュニティのみなさま、そして API Expert の先輩方、今後ともよろしくお願いします!

Ignite The Tour Osaka: OPS20「インシデントに対応する」フォローアップ~内容・デモ解説編その2~

間が空いてしまいましたがその2です。。

f:id:himanago:20200131235952p:plain

1/23(木)・24(金) 、インテックス大阪にて開催された Microsoft の大型カンファレンス「Ignite The Tour Osaka」で登壇した「インシデントに対応する」セッションのフォロー記事の続きです。

★前回:Ignite The Tour Osaka: OPS20「インシデントに対応する」フォローアップ~内容・デモ解説編その1~ - himanago

前回の内容を受けてのデモ

f:id:himanago:20200207154452p:plain

Tailwind Traders の課題がいくつかありましたが、

f:id:himanago:20200131201244p:plain

これに対して、まず「インシデントトラッキング」を行うための「ユニークなチャネル」を作ることで解決しようというデモです。

内容と登場技術

コミュニケーションに関する課題に対処するために、エンジニアがインシデントに関する議論を行うユニークなチャネルを作成する必要があります。

これはそのインシデントにのみ「固有の」チャネルであることが必要で、インシデント対応に集中したり、あとから分析したりするためにも普段使用しているチャネルとは分けるべきです。

そして今回は

  • Azure Boards(Azure DevOps)への issue 作成&オンコールなエンジニアのアサイ
  • Microsoft Teams へそのインシデントに関するチャネルの作成
  • オンコールなエンジニアへの連絡

を Azure Logic Apps で自動化します。

f:id:himanago:20200207155437p:plain

デモ

ざざっと画面キャプチャで流れを見ていきます。

Logic Apps フロー

ライブで作った部分も含め、見ていきます。

全体像

このような流れです。

f:id:himanago:20200207155853p:plain

Azure Monitor から連携することを想定していて、インシデント情報は JSON を HTTP で受け取るようにつくります(デモでは Postman で JSON を投げました)。

Azure Boards への issue 作成

f:id:himanago:20200207160257p:plain

まずは Azure DevOps へ issue を作ります。タイトルに JSON で飛んできたインシデント内容を入れておきます。Logic Apps では JSON の内容を項目(動的なコンテンツ)としてマウスクリックのみで扱えるのでとても楽ですね。

説明(内容)はひとまず空欄にしておきます。

Teams へのチャネル作成

インシデント対応チーム用の、このインシデント専用のチャネルを作ります。

f:id:himanago:20200207160555p:plain

ひとつ前のステップで作った issue の番号(ID)とランダムな数値をチャネル名に入れているのがポイントです。

Logic Apps ではステップ間の連動もこのように簡単にできます。

issue に Teams チャネルへのリンクを張る

f:id:himanago:20200207160817p:plain

チャネルのリンク(ひとつ前のステップで得られるチャネル ID を使って作る)を issue の説明に載せます。

オンコールなエンジニアを見つける

f:id:himanago:20200207160959p:plain

オンコールなエンジニアの情報は、今回は Azure Table Storage に入っているのでそこから取得します。

ちなみに Storage Explorer で見ると、このようになっています。

f:id:himanago:20200207161337p:plain

Table Storage から得た情報は JSON 形式なので、パースします。

f:id:himanago:20200207161436p:plain

パースした情報を使い、メールアドレスを issue の担当者として設定します。

f:id:himanago:20200207161516p:plain

インシデントチャネルに情報を投稿

最後のステップとして、インシデントの内容および issue へのリンクを含んだメッセージを作成したチャネルに投稿します。

今回は Adaptive Cards という形式でメッセージを送ってみます(オリジナルのセッションでは HTML 形式。どちらも「フローボット」という連携方法で送ります)。

{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.0",
    "body": [
        {
            "type": "Image",
            "url": "https://globaleventcdn.blob.core.windows.net/assets/ops/ops20/screenshots/alert.gif",
            "size": "Small",
            "spacing": "None"
        },
        {
            "type": "TextBlock",
            "text": "[AlertType]: [SeverityDescription]"
        },
        {
            "type": "TextBlock",
            "text": "Issue を見る:"
        }
    ],
    "actions": [
        {
            "type": "Action.OpenUrl",
            "title": "Incident [ID]",
            "url": "https://xxxxxxxxx.visualstudio.com/xxxxxxx/_workitems/edit/[ID]/"
        }
    ]
}

ちなみに Adaptive Cards は VS Code 用の拡張機能もあり、プレビューを見ることもできます(コマンドパレットから「Adaptive Card: Preview」)。

f:id:himanago:20200207162707p:plain

f:id:himanago:20200207162635p:plain

Logic App は以下のようになります。動的なコンテンツをいくつか埋め込みます。 f:id:himanago:20200207162044p:plain

実際の動き

Postman から JSON を飛ばすと、インシデントの「28」issue が作成され、次々に自動で行われていきます。

f:id:himanago:20200207173458g:plain

このあと、Teams でコミュニケーションをとりながらインシデント対応をしていきます。

補足

なお、当日はできませんでしたが、オリジナルではさらにオンコールなエンジニアへの DM も作っています。

さらに以下のようなステップを追加すれば、

f:id:himanago:20200207164618p:plain

このようなメッセージが飛んでくるので、安心ですね。

f:id:himanago:20200207164750p:plain

Ignite The Tour Osaka: OPS20「インシデントに対応する」フォローアップ~内容・デモ解説編その1~

はじめに

f:id:himanago:20200131235952p:plain

1/23(木)・24(金) 、インテックス大阪にて開催された Microsoft の大型カンファレンス「Ignite The Tour Osaka」にて「インシデントに対応する」というセッションで登壇しました。

Microsoft Events

Azure 上で動くシステムでインシデントが発生した場合の対応方法に関して、45分でインシデント対応に関する一般的なお話に加え、Azure や Teams を使ったデモを3つ行うという盛りだくさんのセッションです。

時間が短めの中でかなり速く進めたので、終了後に質問いただいた方や、アンケートでも「デモが短い」「時間が足りない」という声がありましたので、少しフォローアップとして記事を書きます。

しゃべった身としても、せっかくのいろんな要素が詰まった完成度の高いデモの面白さを十分に伝えられていないのはもったいない&オリジナルの作者様に申し訳がたたないので、なんとか、この記事が届いてくれることを祈ります。

なお、この記事とは別に環境の作り方やハマったところなども続けて記事化する予定です。

セッションの概要と参考リソース

このセッションは「最新の運用プラクティスによる信頼性の向上」というラーニングパスの2番目に位置づけられるものでした。 OPS10 ~ OPS50 までの計5つのセッションを続けて聞くと体系的に理解できるという設計になっています。

ラーニングパスセッションは、アメリカで行われたセッションを「Tour」各地でローカライズして行うもので、オリジナルのスライド・コンテンツの内容は変えずに行うという制約がありました(翻訳はOK)。

各地のスピーカーや、そのセッションを聴いた方が自分でも試してみたい場合に備え、デモ環境作成方法やプレゼンテーション内容・アメリカのオリジナル版のセッション動画などすべて公開されています。

OPS20「インシデントに対応する」に関するリソースは以下です。

セッション動画&リンク

techcommunity.microsoft.com

GitHub リポジトリ

https://github.com/microsoft/ignite-learning-paths-training-ops/tree/master/ops20github.com

なお、環境作成やデモに関してはリポジトリに書いてあるとおりに勧めれば基本的にはいけるのですが、一部ハマりポイント?がありちょっと苦戦したので、そのあたりは別途記事を上げる予定です(+本家にフィードバック予定)。

日本語スライド

大阪での登壇で使用したセッション資料です。

www.slideshare.net

こちら、今回の大阪のために翻訳しました。
気を付けた点としては、

  • スペースが許す限りなるべくオリジナルの英語記述を残した(誤訳怖い)
  • 用語の訳し方を一部『入門 監視』に合わせた(特に前半)。

あたりです。

書籍『入門 監視』はセッション内でも紹介しましたが、役割の部分など訳に困った部分で本の前半に非常に似た章があり、助かりました。

www.oreilly.co.jp

セッション構成と内容

オリジナルのリポジトリにも時間配分は書いてありますが、以下のような感じです。

  • Section1
    • 導入&インシデント対応の基礎(16分)
    • Demo1(7分)
  • Section2
    • 修復の改善(6分)
    • Demo2(5分)
  • Section3
    • TTR 短縮のためのツール(1分)
    • Demo3(7分)
  • Section4
    • まとめ・クロージング(2分)

いやー、正直時間足りないです…(笑)
説明もパワポのノート欄にびっしり台本が書いてあって、ほんとにこれ時間通りに終わるの?っていう盛りだくさんな内容。
前日までに何度も通しでしゃべって、なんとか切り詰めた…ってかんじです。

では、以下ざっくりと内容です。

Section1:導入&インシデント対応の基礎

  • しゃべったこと:ラーニングパスのひとつ前・OPS10 からのつながりとインシデント対応に関する前提知識。
  • デモ:インシデント発生をトリガーとしたかんばんチケット・コミュニケーションチャネル作成の自動化
  • 技術要素:Azure Logic Apps, Azure Boards, Azure Table Storage, Microsoft Teams, Adaptive Cards

f:id:himanago:20200131195833p:plain

毎年、「DevOps Research and Assessment」という組織が「State of DevOps」というレポートを出していて、最新の2019年のレポートで紹介された数字について。

1時間以内にサービスの中断を検出、対応、および修正できるチームを「エリート/ハイパフォーマー」として分類。
真ん中のレベルに分類されるチームは、24時間以内にインシデントから回復し、「ローパフォーマー」は、サービスの中断から回復するまでに1週間から1か月かかる。

f:id:himanago:20200131195541p:plain

エリート/ハイパフォーマーは、ローパフォーマーよりも2,600倍以上速くインシデントから回復していて、本番環境へのデプロイも200倍以上多く行っているというデータが出ています。

インシデントとは

今回のセッションはインシデント対応がメインのテーマですが、まずインシデントについて考えてみます。

f:id:himanago:20200131200235p:plain

インシデントが「サービスの中断」であることは問題ないですが、ユーザーが依存しているサービスが止まるとユーザーの仕事や生活に影響してしまいます。

「恐れられ回避される」とは

  • 停止の重要性を軽視
  • 意図的にラベルを違うものに
  • 責任逃れのためサービス中断を報告しない

などがあり、きちんとした体制がない場合こういったことが起こりえますが、最近は DevOps や SRE といった考え方から、こういったことが起こらないよう、インシデントについて改めて考えるようになってきています。

またインシデントは「主観的」なとらえ方をされがちで、さまざまな組織や業界によって、何がインシデントなのかという認識はばらばらです。顧客が影響を受けたときだけなのか、そうではないシステム障害も含むのか。主観的に扱われることは、インシデントの重大度レベルを識別する際にも問題となります。

最後に、エンジニアが行うことのほとんどは計画された仕事ですが、インシデント対応は違います。インシデントに関する作業は予定外です。多くの場合、悪いことと見なされがちですが、ちょっと立ち止まって考えてみると、これはエンドユーザーに価値を提供するための「投資」でもあると捉えられるのかもしれません。

インシデントはシステムの "鼓動"

f:id:himanago:20200131200946p:plain

インシデントは「システムの鼓動・心拍」と考えるとよい、といわれ、心拍たるインシデントは、システムについて多くのことを教えてくれます。

強力な監視基盤がある場合(やシステムで何が起こっているか詳細を知っている場合)には、多くのアラート、インシデント、および対応の機会を生成しますが、そうすると、常にシステムで何が起こっているのかがわかるようになります。

そのすべては、インシデントの最初のフェーズである検知・検出の一部です。

f:id:himanago:20200131201129p:plain

これはひとつ前の OPS10 で詳しく説明があった箇所ですが、このセッションは、アラートを受信したあとの、対応と修復が主題です。

Tailwind Traiders の課題

架空のオンライン小売業者 Tailwind Traders は、Azure 上に構築されたかっこいい EC サイトを持っていますが、インシデントに関して次のような課題に直面しています。

f:id:himanago:20200131201244p:plain

2つ目の原文に「reactionary」とあり、これは訳に迷ったのですが(スライドには直訳を記載)、ニュアンス的には「反射的」とか「ただ反応するだけ」とかの意味になります。

これらは、Tailwind Traders に限らず、私たちもよく直面する課題ではないでしょうか?

インシデント対応の体制・役割・ローテーション

体制と役割

インシデント対応のチームは、必ず複数のエンジニアで構成します。誰かひとりに責任が集中したりしてはいけません。

チームは、以下のような役割のエンジニアで構成します。

f:id:himanago:20200131233505p:plain

プライマリ・レスポンダー

いつでも連絡がとれる、「オンコール」なエンジニア。

セカンダリ・レスポンダー

プライマリ・レスポンダーのバックアップです。
プライマリ・レスポンダーに連絡がつかない場合や、そのサポートを行います。

専門家(SME

専門家は常に連絡が取れるべきというわけではありませんが、問題の分野ごとに特定しておく必要はあります。
たとえばデータベースの専門家、フロントエンドの専門家など。
プライマリレスポンダーとセカンダリレスポンダーが自分で問題を診断・解決できない場合に連絡できる人がいると安心できます。

現場指揮官(インシデント・コマンダー

大規模な停止が発生した場合に活躍する統括役。多くの異なるコンポーネント、異なるシステム、チーム間での調整が必要です。 エンジニアが問題解決に集中し、人々がぶつかったり、作業を中断したりしないようにします。 そのためインシデント・コマンダーは何が起こっているのか、誰が何をしているのかを把握しなければなりません。

書記(スクライブ)

会話をできる限り詳細に文書化する役割です。
インシデント対応では、電話やビデオチャットを使用して問題解決のためのコミュニケーションを行うことがありますが、文字に起こさない限り何を言って何をしていたかを詳細に追うことができません(録音でも追いづらいので文書化必須でしょう)。
対応中に振り返ったり、対応後に分析したりすることはインシデント対応において重要です。

コミュニケーション・コーディネーター

インシデント・コマンダーと連携してより多くの情報を共有する人です。インシデント対応を行うエンジニアが持つ以上の情報を、顧客や、カスタマーサポート、販売チーム、マーケティングチームなど外部の人々がもつことがあります。
インシデントやシステムを取り巻く状況や状況を認識するため、コミュニケーションの管理を担当し、他の利害関係者が何が起こっているのか、何が行われているのかを確実に把握できるようにします。

ローテーション

f:id:himanago:20200131234423p:plain

ローテーションは、シフトで管理されます。
エンジニアは、特定の定期的なローテーションに対して「オンコール」の責任を負います。

作成できるシフトにはさまざまな種類があり、「24時間 x 7日間(全曜日)」をローテーションするものや、大きな会社なら "太陽のシフト" に従うローテーションシフトを組めます。これは異なるタイムゾーンでエンジニアが引き継ぐことで、自分の勤務時間だけオンコールであればよくなる、というものです。そうはいっても週末に関しては柔軟性を求められるので、うまくシフトのカスタマイズをすることが必要です。

このあたりは書籍『入門 監視』の前半に詳しいので、あわせて読んでみていただくとよいかと思います。


長くなったので、いったんここで切ります。

次の記事では、Section 1 のデモ(アラートをトリガーとして動く Logic Apps と Teams の連携)について書いていきます。