この記事は Retty Advent Calendar Part2の25日目の記事です。 Part1はこちらです。
はじめに
Retty インフラチームの中西です。
Retty のサービスでは主にAWS ECSを利用しており、リリース作業の際は Slack チャンネル上でメッセージを入力するとBot がデプロイを実行するというChatOps での運用を行っています。
このデプロイを行う Slack Bot (deploy bot) には課題、懸念点がありました。
deploy bot の課題
既存のdeploy bot には以下の課題がありました。
- EC2 インスタンスで稼働している
- シングル構成となっているのでインスタンスに不具合があればリリース作業に影響がある
- Slack カスタムインテグレーションでの作成となっており現在は非推奨になっている
- 将来、強制的に廃止される可能性が高い
解決策として、サーバレスでの稼働とカスタムインテグレーションに替わる Slack Api での再作成を行うために Amazon API gateway + Lambda を採用しました。
Amazon API gateway + Lambda の採用に関しては参考記事をはじめ、
設定事例が多かったのが決めてとなりました。
deploy bot で行うこと
既存の deploy bot が行っている処理の踏襲となりますが、要件をまとめました。
- Slack チャンネル上での Bot へのメンション時に特定のメッセージ(コマンド)に反応して以下の処理を行う
- 1.デプロイ対象のコンテナのイメージタグの一覧を表示する
- 2.デプロイ用の コンテナのイメージタグを設定する
- 3.デプロイ用のイメージタグ設定後にECSサービスのアップデートを行う
- 実行前に
Yes
,No
のボタンを表示してデプロイ実行者に確認を促すようにする
- 実行前に
行ったこと
基本的に参考記事と同じとなります。 (非常に懇切丁寧な内容で大変助かりました)
1 . Lambda function の作成
作成後のRole に必要なIAMポリシーを付与します。 今回はECR、ECSの操作権限を付与しました。
またコードは Slack App 作成前に作成をしておきます。 以下は一部となります。
def lambda_handler(event, context): # 受信データをCloud Watchログに出力 logging.info(json.dumps(event)) # SlackのEvent APIの認証 if "challenge" in event: return event["challenge"] # tokenのチェック if not is_verify_token(event): return "OK" # ボットへのメンションでない場合 if not is_app_mention(event): return "OK" channel = event.get("event").get("channel") sgmes = event.get("event").get("text") sguser = event.get("event").get("user") # メッセージ毎に処理を行う if "show-ecr-tags" in sgmes: show_ecr_tags(channel) return 'OK' elif "set-release-version-tag" in sgmes: set_relese_version_tags(channel) retrun 'OK' elif "release-to-ecs-production" in sgmes: slack.post_message_to_button(event.get("event").get("channel")) return 'OK' else: message = "usage..." slack.post_message_to_channel(channel, message) return 'OK'
2. API Gateway の作成
1 で作成した Lambda function からトリガーを設定します。
デフォルトでANYメソッドが作成されていますが、今回は不要です。 ANYメソッドを削除して POSTメソッド単体を作成します。
アクション
メニューからAPIのデプロイ
を行います。
デプロイ後のURLは Slack App 作成時に必要となるので控えておきます。
3. Slack Api の作成
Slack Api にアクセスして Create New App
を行います。
From scratch
を選択します。
App 作成後に Basic Information
を選択して Verification Token の値を控えます
Event Subscriptions
を選択して以下の設定を行います。
- Enable Events を On
- Request URL に API Gateway でデプロイした URLを入力
- Subscribe to bot events で
Add Bot User Event
ボタンからapp_mention
を選択、追加 Save Changes
で保存
OAuth & Permissions
で以下の設定を行います。
- Scopes の
Add an OAuth Scope
ボタンからchat:write
を選択、追加 - Bot User OAuth Token の値を控える
Install to Workspace
又はReinstall to Workspace
ボタンを選択して Slackワークスペースへのインストールを行う
アクセス権限のリクエストを確認して 許可する
ボタンを選択します。
4. Lambda の設定、動作確認
3.の Slack Api 作成時に控えた値を環境背変数として設定します。
1.の Lambda function の作成で予めコードは作成してるので動作確認を行います
これで要件で書いた 1.デプロイ対象のコンテナのイメージタグの一覧を表示する
の処理が確認できました
Slack Interactive Messages の設定
上記 1 - 4 の操作で 要件 1 と 2は実装できるようになりました。
3.デプロイ用のイメージタグ設定後にECSサービスのアップデートを行う
には以下の処理が必要になります
Yes
、No
操作確認を行うボタンを表示する- 押したボタンの内容によって処理を行いレスポンスを返す
ボタンの表示
ボタンの表示はドキュメントを参考に json 内の attachments:
で作成しました。
import json import urllib.request import os import logging def post_message_to_button(channel): url = "https://slack.com/api/chat.postMessage" headers = { "Content-Type": "application/json; charset=UTF-8", "Authorization": "Bearer {0}".format(os.environ["SLACK_BOT_USER_ACCESS_TOKEN"]) } data = { "token": os.environ["SLACK_BOT_VERIFY_TOKEN"], "channel": channel, #"text": message, "attachments": [ { "text": "Do you want to release ? (Yes or No)", "fallback": "no_push_button", "callback_id": "release_button", "color": "#3AA3E3", "attachment_type": "default", "actions": [ { "name": "Yes", "text": "Yes", "type": "button", "value": "Yes-release", "action_id": "button", "confirm": { "title": "reconfirmation", "text": "Do you really want to do it?", "ok_text": "Yes", "dismiss_text": "No" } }, { "name": "No", "text": "No", "type": "button", "value": "No-release", "action_id": "button" }, ] } ] } req = urllib.request.Request(url, data=json.dumps(data).encode("utf-8"), method="POST", headers=headers) urllib.request.urlopen(req)
これで Yes
、No
ボタンが表示されました。
Yes
ボタンを押すと再確認のメニューも表示されてます。
Slack Interactive Component の設定
ボタンを押した後のレスポンスを設定する為に再度作成した Slack Api で Interactive Component の設定を行います。
Interactivity & Shortcuts
を選択して Interactivity を On にします。
ここで Request URL の設定が必要となりますが、このURLにボタンを押された際にリクエストが送信されます。
Request URL 用のAPI Gateway + Lambda の作成
最初に作ったものとは別の Lambda Function を作成します。
また環境変数は SLACK_BOT_VERIFY_TOKEN
を設定します。
API Gateway は同じものを設定して別のリソースを作成します。
注意点として POST メソッド作成時は Lambda proxy
を有効にする必要があります。
(有効にしないとリクエスト内のボタンを押した時のデータが受信できませんでした)
Lambda function での実装
以下の様な処理となります。
- リクエストデータを整形
- 整形したデータから token が一致しているか確認
- ボタンを押した時の値を確認して処理を実施
- 正常時に
http status 200
を返す- 200を返さないと Slack のチャンネル上でエラーが表示された為
以下は一部コードのサンプルです
def lambda_handler(event, context): ## slackからのデータを整形 decode_body = urllib.parse.unquote(json.dumps(event['body'])) data = decode_body[1:-1].replace('payload=',"") dict_data = json.loads(data) response_url = dict_data['response_url'] channel = dict_data['channel']['name'] button_value = dict_data['actions'][0]['value'] token = dict_data['token'] ## slack token を確認 if not is_verify_token(token): return "NG" ## button タップ時の値を取得して処理を行う if "Yes-release" in button_value: ecs_release() message = "OK. ECS release is started." post_message_response_button(response_url,channel, message) return {'statusCode': 200,} elif "No-release" in button_value: message = "ECS release is cancel." post_message_response_button(response_url,channel, message) return {'statusCode': 200,} else: message = "other response" post_message_response_button(response_url,channel, message) return {'statusCode': 200,}
動作確認
これでボタンを押した時にメッセージが表示されるようになりました。
Yes
を選択した場合
No
を選択した場合
残作業としては、ボタン毎にECSデプロイといった任意の処理を実装していけば作成は完了となります。
最後に
deploy bot のリプレースは行おうと考えていたのですが優先順位の問題で後回しになっていました。 アドベントカレンダーの題材として一気に進めることができて良かったです。
最後までお読み頂きありがとうございました。
参考記事
AWS(API Gateway + Lambda(Python)) + Slack APIを使ったBot作成 https://nmmmk.hatenablog.com/entry/2018/10/10/001548