この記事はiRidge Advent Calendar 2019 10日目の記事です。
先日AWS Chatbotを利用してSlackからAWSコマンドを発行する機能のβ版が発表されました。
- AWS Chatbot now supports running commands from Slack (beta)
- Running AWS commands from Slack using AWS Chatbot
もともとAWS Chatbotを利用してCost ExplorerのBudgets AlertをSlackに流すくらいのことはやっていたのですが、コマンドが実行できることでやれることがより増えそうだなと思い、今回はよく業務で行っていたALBのログ検索を行い結果を取得するのをSlackで完結できるかを試してみました。
今回やること
- AWS Chatbotを設定する
-
Lambdaを作る
- SlackからAWS Chatbot経由でLambdaを呼び出す
- Lambdaでテーブルに対してクエリを実行
- S3に出力された結果ファイルのオブジェクトを取得するためのPresigned URLを生成
- 生成されたPresigned URLを返却
- Lambdaをデプロイする
- SlackからLambdaを呼び出す
- まとめ
前提
Athenaのテーブル作成、Partitionの作成は完了している前提で書いてます。テーブルの作成等はAWSの下記のドキュメントが参考になると思います。
AWS Chatbotを設定する
まずはChatbotの設定をしていきます。
-
workspaceの設定をします。Chat clientをSlackにしてConfigure clientを押すとworkspaceにアクセスするための権限を求めてくるので、問題なければ許可します。
-
Chatbotを利用するchannelを選びます。
-
Chatbotが利用するIAM Roleを定義します。ある程度まとまったPolicyがテンプレートとして準備されているようです。作成したタイミングでは下記のテンプレートが準備されていました。
- Notification Permission: CloudWatchからメトリックグラフを取得するのを許可する権限
- Read-only command permissions: Readのコマンドを許可する権限
- Lambda-invoke command permissions: サポートされているクライアントでLambdaの呼び出しを許可する権限
- AWS Support command permissions: support APIを呼び出しを許可する権限
今回は、Lambdaの呼び出しだけで良かったので、Lambda-invoke command permissionsのテンプレートを利用しました。Defaultだと、すべてのLambdaに対するinvokeが許可されているようなので、適宜呼び出せる関数は制御したほうが良さそうです。下記がテンプレートで付与されたLambdaの呼び出し用のIAM Policyです。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "lambda:invokeAsync", "lambda:invokeFunction" ], "Resource": [ "*" ] } ] }
あとSNSの設定項目がありますが、今回は不要なので割愛します。CloudWatchのAlarmや、CostExplorerのBudgetsに関する通知を送るときは設定が必要になってきます。
Lambdaを作る
Slack から呼び出すLambdaを作成します。Lambdaの中では下記の処理を実装しました。
- Athenaのクエリを実行、結果をS3に出力させる
- 配置された結果ファイルに対するpresigned-urlを取得する
下記コードです。テーブル等は適当なものに変更してるのでそのままじゃ動かないです。
サンプルコード
import boto3
import time
from urllib.parse import urlparse
athena_client = boto3.client('athena')
s3_client = boto3.client('s3')
query_format = '''
select * from <table>
limit 10
'''
def execute():
response = athena_client.start_query_execution(
QueryString=query_format,
ResultConfiguration={
'OutputLocation': 's3://<S3 bucket name to output result>',
}
)
return response['QueryExecutionId']
def get_output_location(query_execution_id):
while True:
response = athena_client.get_query_execution(
QueryExecutionId=query_execution_id
)
print(response['QueryExecution']['Status']['State'])
if response['QueryExecution']['Status']['State'] in ['QUEUED', 'RUNNING']:
time.sleep(5)
continue
if response['QueryExecution']['Status']['State'] == 'SUCCEEDED':
return response['QueryExecution']['ResultConfiguration']['OutputLocation']
print(response)
return None
def get_presigned_url(location):
parsed_s3path = urlparse(location)
print(parsed_s3path)
response = s3_client.generate_presigned_url(
'get_object',
Params={
'Bucket': parsed_s3path.netloc,
'Key': parsed_s3path.path.replace('/','')
},
ExpiresIn=3600)
return response
def lambda_handler(context, event):
query_execution_id = execute()
output_location = get_output_location(query_execution_id)
if output_location is None:
print("Query Failed!!")
return "Query Failed!!"
presigned_url = get_presigned_url(output_location)
print(presigned_url)
return presigned_url
if __name__ == "__main__":
context = {}
event = {}
lambda_handler(context, event)
Athenaに対してクエリを投げるときはstart_query_execution
でクエリを実行したあと、get_query_execution
を実行して結果の状態を把握する必要があるみたいでした。とりあえずLoopの中で5秒おきに実行するようにしていますが、実行時間が長いクエリだとLambdaのコストが増えるため微妙だなと思っています。SNSとかSQSを間に挟んで待ち時間を関数の外に出せば節約できそうですが、今回は一旦Lambda関数内でsleepさせることにしました。
Lambdaをデプロイする
Apexを使ってLambdaをデプロイしました。(よく使っているので)
ディレクトリ構成は下記のような感じです。
aws-chatbot/
├── functions
│ └── athena-query-executor
│ └── main.py
└── project.json
{
"name": "aws-chatbot",
"description": "",
"runtime": "python3.7",
"memory": 128,
"timeout": 300,
"handler": "main.lambda_handler",
"role": "arn:aws:iam::<Your AWS Account ID>:role/<Your role name>",
"nameTemplate": "{{.Function.Name}}",
"environment": {}
}
デプロイは下記コマンドで行います。
apex deploy -r ap-northeast-1 -p <your profile>
Lambdaに割り当てるIAM RoleにはAmazonAthenaFullAccess、AmazonS3ReadOnlyAccessを割り当てました。本当は権限を絞っていきたいんですが、Athenaの権限が思ったより複雑だったので検証の段階では動かすことを優先しました。ちゃんと運用するときはしっかり理解した上での設定が必要だなと感じています。
SlackからLambdaを呼び出す
Lambdaがデプロイできたら、いよいよSlackからLambdaを呼び出してみます。
Chatbotの設定画面で設定したchannelに/invite @aws
でchatbotのユーザーを招待します。
ユーザーが招待できたら次の内容をchannelに投下してLambdaを実行します。
@aws lambda invoke --function-name <Your function name> --region ap-northeast-1
実行すると本当に実行してよいかを聞かれます。(スクリーンショットの Would you like~ あたり。) YES とするとLambdaの実行が始まります。
Lambdaの実行が完了すると戻り値として、Payload,ExecutedVersion,StatusCodeが返却されます。
どうやらPayloadはbase64エンコードされた状態で返却されるようです。。。(マスクしているので見えませんがbase64エンコードされた状態となっています。)想定ではここでpresined-urlをクリックしてAthena実行結果のcsvをゲットする算段だったのですが、ローカルでbase64デコードする一手間が挟まってしまいました。。
しかし、デコードした結果のURLからは正しくcsvを取得することができました。
まとめ
今回Chatbotを使ってみた所感です。
ChatOps環境を作りやすくなった
AWS Chatbotを使うとAWS連携がより手軽にできるようになったと感じました。以前AWS Chatbotが出る前にもSlackでスラッシュコマンド等を作ったことがあり、そのときはAPI Gateway + Lambdaを作成してバックエンドを作成したのですが、API Gatewayの設定が地味にめんどくさかった記憶があります。今回Chatbotで直接AWSコマンドを実行可能になり、API Gateway等を作る手間は削減されたのかなと感じます。個人的にオペレーション用に作るものはなるべく単純にしておきたいという思いがあるので、その点は少しスッキリしてよかったかなと思います。
権限管理は慎重に
SlackからAWSへの操作をできるようになるため、Chatbotに付与する権限は用途ごとにきっちり絞ったほうがよいなと感じました。DefaultのIAMポリシーテンプレートをそのまま使うのではなく、実行できる関数を絞っておくなどして、不用意な事故を防ぐ必要があるなと感じます。Chatbotに限った話ではないですが、ここで使うIAMにも必要最小限の権限を付与するというのを意識しておけば良さそうです。
個人の環境に左右されにくくなる
特定の操作をSlack上でできるので、よく行う運用作業をいくつかまとめられれば誰でもすぐログ調査等ができるようになる点はいいなと思いました。各個人に払い出されている権限、ツールのバージョン等を統一せずに調査等、必要な操作環境を提供できる点は良さそうです。
戻り値のPayloadをbase64デコードした状態で表示してほしい
今回試してみた結果Payloadがbase64エンコードされて返ってくることがわかりました。。base64デコードした状態で表示してくれれば戻ってきたpresigned-urlから直接ファイルがダウンロードできるのに、、、。ちなみに、AWS CLIでlambda invoke
を実行するとoutfileに指定したファイルに戻ってきた結果が書き込まれるのですが、それはbase64デコードされた状態となっていました。
呼び出せるコマンドはまだ少ない?
当初Athenaのクエリ実行コマンドを、直接Slackから呼び出そうとしたのですが下記のエラーが出ました。まだ対応しているコマンドが限られているのかもしれません。今後呼び出せるコマンドが増えるといいなと思っています。
I can't run the command athena start-query-execution because it isn't enabled.