ServerlessなOpsを考える

# はじめに

[Serverless Advent Calendar 2018](https://qiita.com/advent-calendar/2018/serverless) の18日目です。

**Serverless** な **Ops** に関連した、ちょっとしたTipsを紹介します。

---

# No Opsな時代を目指して...

## ServerlessなOpsに関する個人的な心得

私は日々のサービス開発で **意地のサーバーレス** をモットーに、主にAWSのFaaSやFully Managed Servicesのみを使ってシステム開発をしているわけですが、他にもこだわりがあって、

**AWSコンソールにログインしたら負け**

というポリシーを持っています。 (※よく負ける)

昔は `sshしたら負け` とかよく言ってましたが、Serverlessをやっているとsshするケースなんてほとんど存在しないため、インスタンスへのsshログインの次はコンソールログインと闘い始めたわけであります。

いつか、 **<font size=4>まだAWSコンソールで疲弊しているの？</font>** と言われないために...
<font size=1>(たぶんずっと言われない)</font>

## コンソールログインは邪悪か？

いいえ、邪悪ではありません。

ただ、管理しているAWSのRootアカウントやIAMユーザーの数が増えてくると、

- ID / Passwordの管理が面倒
- ログイン毎のMFAコードの入力がうざったい
- 会社さんによってはAWSコンソールにログインできる場所や端末が限られている？ (未確認)
- IAMの権限管理が面倒

などなど、色々と課題は出てきます。
また、頻繁にコンソールにログインしているうちは `No Ops` とは遠い状態にいることは確かです。

とは言っても日々のサービス運用でコンソールへのログインが必要になるケースは発生します。
その際に、コンソールでの単純作業の運用手順書を作る...なんて仕事は間違ってもやりたくないですし、何回も同じ事象でコンソールログインしている場合、私は積極的にChatOps化を検討しています。

ここで言うChatOpsは、主にSlackのSlash Commandを使ったOpsだと思ってください。

## ChatOpsの何が楽なのよ？

もちろんコンソールにログインしなくても作業できるので楽ができます。
Slackという **24/7** でAccessibleな、大変ブラックなツールが身近にあるのに、使わない手はないわけです。

コンソールへのログインを無くしてちょっとでも楽をして、より `Less Ops` な状態にし、 `No Ops` に向かって進化したいのです。

## ChatBotに向いているOpsって？

- 日々のOpsでAWSコンソールで行っている単純作業
    - ログ検索
    - IAMの権限管理
    - 各種リソースのパラメータ調整
- 外部からの依頼で行う、ある程度手順が決まった作業
    - テストデータを投入してほしいんだけど、とか

などなど、色々あると思います。

従来はこういった作業を簡単に行うために、Scriptを書いてEC2インスタンスに置いて実行したりしていましたが、なんとサーバーレスな世界ではEC2インスタンスを立てられません！！

結果的にSlackのSlach Commandを使うのが最もお手軽でした。

---

# ServerlessなChatOps

散々ポエムを書きましたが、ここからが本題 (？) です。
適当な課題をServerlessなChatOpsで解決してみます。

## 課題

Serverlessなマイクロサービス群でシステムを構築していて、とある `HogeService` でエラーが発生し、Slackに通知が飛んできました。

![pic01.png](https://qiita-image-store.s3.amazonaws.com/0/39743/c362b3ac-4e97-480e-0afc-04f7c43f7de2.png)

Alarmを読み解くと、
- DBからデータが取得できずにエラーが発生した
- どうやらLambdaで処理したrequestIdは `12345678-aaaa-bbbb-cccc-1234567890ab` のようだ

このAlarmの前後で何が起こっているのかログを調べよう！ということで、

1. AWSコンソールにログイン、
- CloudWatchのサービスページに移動して、
- CloudWatch Logsを開いて、
- 対象のLambda Functionのロググループを探して、
- 対象のログストリームからエラーが発生したrequestIdを検索して、
- 該当のrequestIdが付与された一連のトランザクションのログを表示

..と、これでようやくログの調査ができます。

このOpsを、SlackのSlach Commandで簡単に行えるようにしたいと思います。

## 開発

当然ですが、ChatOpsな機能もServerlessを使って開発します。
何かしらのPaaSのような、アイドル時に課金されてしまうサービスを使うと負けです。

### 構成

1. Slackで、Slash Command (例えば `/logsearch`) とRequest IDを入力する
2. SlackからのコマンドはAPI Gatewayで受ける
3. LambdaをInvokeし、AWS SDKを使って `CloudWatchLogs.filterLogEvents` を使ってログを検索する
4. 結果をLambdaのレスポンスで返す
5. Slackに一連のログが表示される

Bot開発のフレームワークは何でもいいのですが、個人的には [claudia-bot-builder](https://github.com/claudiajs/claudia-bot-builder) が好きでよく使います。

3については、こんな感じの関数を作っておくと便利です。
(細かい説明は割愛します)

```JavaScript
const queryLogs = (lambdaName, utcDate, requestId) => {
  const params = {
    logGroupName: `/aws/lambda/${lambdaName}`,
    filterPattern: `"${requestId}"`,
    logStreamNamePrefix: `${utcDate}`,
    limit: 20
  };

  return new Promise((resolve, reject) => {
    CloudWatchLogs.filterLogEvents(params).promise().then((data) => {
      const rawLogs = data.events;
      _.sortBy(rawLogs, 'timestamp');

      const logs = [];
      for (rawLog of rawLogs) {
        logs.push(rawLog.message);
      }

      resolve(logs);
    }).catch((err) => {
      reject(err);
    });    
  });
};
```

### この構成には問題あり...

この構成で動くこともありますが、ログの検索に少し時間がかかるため、だいたいSlackのResponse時間制限の3秒が経過してしまってSlack側がタイムアウトします。

実運用する際は、最初のLambdaはSlackのコマンドを受け取って非同期タスクを実行し、すぐにResponseを返すべきです。

非同期タスクの実行は、通知の柔軟性を考慮するとSNSを挟むのが良いかと思います。

わざわざ構成図を描く必要はないと思いますが、あえて描くとこんな感じです。

![pic02.png](https://qiita-image-store.s3.amazonaws.com/0/39743/84370409-1458-b8f7-a146-948d35d3b6a3.png)


1. Slackで、Slash Command (例えば `/logsearch`) とRequest IDを入力する
2. SlackからのコマンドはAPI Gatewayで受ける
3. LambdaをInvokeし、SNSのTopicにデータをPublishして、すぐにSlackにResponseを返す
4. SNSにAsyncTask用のLambdaをSubscribeさせておき、AWS SDKを使って `CloudWatchLogs.filterLogEvents` を使ってログを検索する
5. 結果をSlackに送信する
6. Slackに一連のログが表示される

結果的に、Slack上でこんな感じの結果が得られます。

#### Slash Commandの例
```
/logsearch 12345678
```

#### 結果

![pic04.png](https://qiita-image-store.s3.amazonaws.com/0/39743/84feb208-4546-b0ea-a500-c8f934b88a74.png)

## まとめ

どうでしょう？

##### Before

```
1. AWSコンソールにログイン、
2. CloudWatchのサービスページに移動して、
3. CloudWatch Logsを開いて、
4. 対象のLambda Functionのロググループを探して、
5. 対象のログストリームからエラーが発生したrequestIdを検索して、
6. 該当のrequestIdが付与された一連のトランザクションのログを表示
```

##### After
```
1. /logsearch ${requestId} と、Slackにコマンドを打つだけ
```

<br>
コンソールへのログインを省略したおかげで、ずいぶんと楽になった気にならないでしょうか？？

こんな感じで `Less Ops` 化を進め、 `No Ops` な時代に向けて少しずつ進んでいきたい次第でございます。

---


投稿内容は個人の意見であり、所属企業の意見を代表するものではありません。