Slackbotをserverlessで動かしたかった

## 目的

これはSlackbotをserverlessで動かしたかったのだが、失敗した記録である。
こうやったらできるよなどがあったら教えて欲しい。

## 要件

- Slackのイベント(新規ユーザの追加/チャンネル追加)時にbotがpostして欲しい
    - 将来的には機能を追加したい
- Slackはフリープラン
- bot運用にできるだけコスト（お金）をできるだけかけたくない
- 運用にかかるコスト（OSやパッケージのアップデートなど）をできるだけかけたくない

フリープランでなければWorkflowが選択肢になる。

## 構成

![Slackbot_Serverless.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36587/28a285a9-c02e-da53-d094-3fc15dab2675.png)

SlackとApp間はEvent経由（HTTPS、WebSocketでない）で通信する。
Slack空のHTTPS通信はAPI Gatewayで受け、Lambdaに流す。
Lambdaで応答メッセージを生成し、レスポンスする。

Slack botのフレームワークには[Slack Bolt](https://slack.dev/bolt/ja-jp/tutorial/getting-started)を使用する。何となく公式がいいのではという以上の理由はない。
また、API Gateway / Lambdaの作成にはServerless frameworkを使用する。 `sls deploy` コマンドを実行するだけでAPI Gateway / Lambda / CloudWatch Logsを作ってくれ、アプリをデプロイしてくれる。便利。
GitHubで管理し、masterブランチへのコミット（マージ）時にGitHub ActionsでAWSにデプロイしてくれるという構成。

## 構築

### Slack App／Bolt

新規にSlack Appを作成する。
[Bolt 入門ガイド](https://slack.dev/bolt/ja-jp/tutorial/getting-started) に沿って作業していくとSlack Appの作成から簡単なbotの作成までを一通り学べる。

### API Gateway / Lambda とBoltの連携

[seratch/serverless-slack-bolt-aws](https://github.com/seratch/serverless-slack-bolt-aws) をテンプレートとして使用した。

```shell-session
$ serverless create \
  --template-url https://github.com/seratch/serverless-slack-bolt-aws/tree/master \
  --path hello-bolt
```

を実行するだけでServerless FrameworkでデプロイできるSlack Boltのbotができる。便利。

https://dev.classmethod.jp/report/developers-io-2019-slack-bolt/ の人が作っていたみたい。


Slack AppでEventを有効にし、[channel_created](https://api.slack.com/events/channel_created) と [team_join](https://api.slack.com/events/team_join) を有効にする。

[channel_created](https://api.slack.com/events/channel_created) と [team_join](https://api.slack.com/events/team_join) イベントをハンドルするコードを書いた。

```javascript
app.event('team_join', async ({ event, context }) => {
  const teamJoinNotifyChannel = process.env.TEAM_JOIN_NOTIFY_CHANNEL || '#general';
  try {
    //send welcome message
    const welcomeMessage = await app.client.chat.postMessage({
      token: context.botToken,
      channel: teamJoinNotifyChannel,
      text: `ようこそ <@${event.user.id}>! 🎉 自己紹介をしてみましょう！`
    });
    console.log(welcomeMessage);
  }
  catch (error) {
    console.error(error);
  }
});

app.event('channel_created', async ({ event, context }) => {
  const channelCreatedNotifyChannel = process.env.CHANNEL_CREATED_NOTIFY_CHANNEL || '#general';
  try {
    const result = await app.client.chat.postMessage({
      token: context.botToken,
      channel: channelCreatedNotifyChannel,
      text: `#${event.channel.name} チャンネルが作られたよ!🎉`,
      link_names: true
    });
    console.log(result);
  }
  catch (error) {
    console.error(error);
  }
});
```

### ローカル環境で検証

`sls offline` でローカル実行し、もう一枚のターミナルで `ngrok http 3000` する。
ngrokの画面に表示される `https://1a2b3c4d.ngrok.io` を[Slack Appの設定](https://api.slack.com/apps)の
Event Subscriptions→New Request URL に設定する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36587/4d5075fd-f24b-ebce-f2c0-f436b4d0d01e.png)

ここでSlackのチャンネルを作成するとbotが通知してくれる。やったね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36587/a57780d0-8d15-431c-8995-10b9fee43b4e.png)

### AWSにデプロイ

AWSにデプロイする。
せっかくなので、GitHub Actionsを使ってmasterブランチにマージされたタイミングでデプロイする。

こんな感じで GitHub Actionsの設定を追加。

```yaml:.github/main.yml
name: CI

on:
  push:
    branches:
      - master
jobs:
  deploy:
    name: deploy
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1
    - name: install node.js
      uses: actions/setup-node@v1
      with:
        node-version: '12.x'
    - run: npm install
    - run: npx serverless deploy --conceal --stage prd
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
        SLACK_SIGNING_SECRET: ${{ secrets.SLACK_SIGNING_SECRET }}
        CHANNEL_CREATED_NOTIFY_CHANNEL: ${{ secrets.CHANNEL_CREATED_NOTIFY_CHANNEL }}
        TEAM_JOIN_NOTIFY_CHANNEL: ${{ secrets.CHANNEL_CREATED_NOTIFY_CHANNEL }}
```

GitHubのリポジトリ→Settings→Secretsに環境変数を指定する。

- AWS_ACCESS_KEY_ID	
- AWS_SECRET_ACCESS_KEY	
- CHANNEL_CREATED_NOTIFY_CHANNEL	
- SLACK_BOT_TOKEN	
- SLACK_SIGNING_SECRET	
- TEAM_JOIN_NOTIFY_CHANNEL

AWSは専用のユーザを作成し、アクセスキーを払い出した。
ユーザにattachするpolicyは以下を参考にした。
[Customize the Serverless IAM Policy
](https://serverless-stack.com/chapters/customize-the-serverless-iam-policy.html)
"A simple IAM Policy template" セクションを見たら、IAMの全権限を許可してて、やってますなーという気分になるので、"An advanced IAM Policy template" を参考に変数部分を置換するのがおすすめ。

GitHub Actionsに慣れてないのでちょっと迷いながらもデプロイのフローを作れた。

ここまでは何とか進めた。

### API Gateway経由でBotを実行

結論から言うと、ここがうまくいかなかった。
チャンネルを作成してもうんともすんとも言わないのだ。

SlackはAppを実行してから3秒以内にackを返さないとエラーとなる。
[Handling user interaction in your Slack apps](https://api.slack.com/interactivity/handling#acknowledgment_response)

Boltを紹介するスライドに書いてあったが、どうにかなるだろうと思ったらどうにもならなかった。
また、レスポンスはIncoming Webhookを使用するというworkaroundもあるのだが、Slack Appで払い出せるWebhookのURLはChannelを指定できない。[channel_created](https://api.slack.com/events/channel_created) と [team_join](https://api.slack.com/events/team_join)であれば問題はないが、将来的に任意のpostに対してreplyしたいのでこの制限は辛い。

また、チャンネルを指定できるLegacyなWebhookのURLを払い出せるのだが、できれば1つのAppで完結したいのでこの案はボツにした。

やりたいことは大体検証できたし、他の方法を試してみたいと言うのもある。

## 次の案

[Hubot](https://hubot.github.com/)を使用し、FargateなどのDocker環境に構築する予定。仕事でHubot使ってるので感じはつかめてるし、WebSocketのクライアントなのでインターネットにつながればどうにかなるはず。Fargateはまだ手をつけてないのでやってみたい。
[Savings Plans](https://dev.classmethod.jp/cloud/aws/savings-plans-fargate/)や[Fargate Spot](https://dev.classmethod.jp/cloud/aws/einvent2019-new-feature-aws-fargate-spot-support/)も試してみたい。

## ソース

https://github.com/kangaechu/bolt-bot-serverless