はじめに
RDSを常時起動させておく必要がないシステムがあります。そのシステムを使う時だけRDSを起動させたいのですが、使用する人はIAMユーザを持っていないため、コンソールから起動、停止できない、ということがありました!
そこで、SlackからRDSの操作をできるようにしました!
Chatbotを紐づけたSlackのチャンネルからコマンドを実行し、Lambda関数を呼び出すことで、RDSの操作をします。
やりたいこと
Slackからやりたいことは以下の3つです。
- RDSの起動
- RDSの停止
- RDSのステータスの確認
方法検討
調べてみると、Slackから実行する方法が2つ見つかりました。
- API Gatewayを使う方法(Slash Commandsの作成)
- AWS Chatbotを使う方法
今回は、2020年4月にGAになった、使ってみたかった、Slackとの組み合わせが素晴らしいらしいという観点からAWS Chatbotを使ってみることにしました!
AWS Chatbotってなに?
公式サイトによると、次のように説明されています。
AWS Chatbot は、Slack チャンネルや Amazon Chime チャットルームで AWS のリソースを簡単にモニタリングおよび操作できるようにしてくれるインタラクティブエージェントです。AWS Chatbot を使用すると、アラートを受信することや、診断情報の取得、AWS Lambda 関数の呼び出し、AWS サポートケースの作成を行うコマンドを実行することができるようになります。
Chatbotでできること
今回は、Slackチャンネルから操作を行うようにしたいです。
AWS Chatbotは、AWSサービスの読み取り専用コマンドをサポートしています。AWSリソースを作成、削除、または構成するコマンドは実行できません。
ただし、一部のサービスは読み取りもサポートしていません。IAM、AWS Security Token Service、AWS Key Management ServiceなどはChatbotを通じて読み取りコマンドの呼び出しも行えません。もちろん作成、削除、構成のコマンドも使えません。
RDSの起動/停止はChatbotから操作できず、ステータスの確認はChatbotからコマンドで操作できます。ただし、取得できる大量の情報からステータスのみを抽出したかったのでChatbotでLambda関数の呼び出しを実行し、Lambda関数からそれぞれの操作を行うようにします。
参考
料金
Chatbot自体には料金はかかりません!
AWS Chatobot には追加料金はかかりません。お支払いは基盤となるサービス (Amazon Simple Notification Service、AWS GuardDuty、AWS Security Hub など) の使用に対してのみであり、AWS Chatbot を使用していない場合と同様です。また、最低料金や前払いの義務はありません。
参考:https://aws.amazon.com/jp/chatbot/pricing/
今回の構成だとLambdaの料金のみがかかります。
構成図
構成は下図の通りです。本記事では、Slack、AWS Chatbot、Lambdaにフォーカスしています。
やってみる
Lambda関数の作成
今回は操作ごとに3つのLambda関数を作成しました。
- RDSの起動
- RDSの停止
- RDSのステータスの確認
IAMロール
ターゲットRDSの起動、停止、情報を取得するポリシーを作成してアタッチします。今回作る3つのLambda関数にこのロールを設定しました。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"rds:DescribeDBInstances",
"rds:StopDBInstance",
"rds:StartDBInstance"
],
"Resource": "<ターゲットRDSのarn>"
}
]
}
ソースコード
※それぞれクリックしたらソースコードが見れます。
RDS起動
package main
import (
"fmt"
"log"
"os"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/rds"
"github.com/aws/aws-sdk-go/service/rds/rdsiface"
)
func StartDBInstance(svc rdsiface.RDSAPI) {
// os.Getenv()でLambdaの環境変数を取得
InstanceID := os.Getenv("InstanceID") // DB識別子
InstanceIDP := aws.String(InstanceID)
input := &rds.StartDBInstanceInput{
DBInstanceIdentifier: InstanceIDP,
}
result, err := svc.StartDBInstance(input)
if err != nil {
panic(err.Error())
}
// 結果を出力
fmt.Println(result)
}
/**************************
処理実行
**************************/
func run() (interface{}, error) {
log.Println("--- RDS自動起動バッチ 開始")
log.Println("----- セッション作成")
svc := rds.New(session.Must(session.NewSession()))
log.Println("----- インスタンス起動 実行")
StartDBInstance(svc)
log.Println("--- RDS自動起動バッチ 完了")
res := "RDSを起動しています。"
return res, nil
}
/**************************
メイン
**************************/
func main() {
lambda.Start(run)
}
RDS停止
package main
import (
"fmt"
"log"
"os"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/rds"
"github.com/aws/aws-sdk-go/service/rds/rdsiface"
)
func StopDBInstance(svc rdsiface.RDSAPI) {
// os.Getenv()でLambdaの環境変数を取得
InstanceID := os.Getenv("InstanceID") // DB識別子
InstanceIDP := aws.String(InstanceID)
input := &rds.StopDBInstanceInput{
DBInstanceIdentifier: InstanceIDP,
}
result, err := svc.StopDBInstance(input)
if err != nil {
panic(err.Error())
}
// 結果を出力
fmt.Println(result)
}
/**************************
処理実行
**************************/
func run() (interface{}, error) {
log.Println("--- RDS自動停止バッチ 開始")
log.Println("----- セッション作成")
svc := rds.New(session.Must(session.NewSession()))
log.Println("----- インスタンス停止 実行")
StopDBInstance(svc)
log.Println("--- RDS自動停止バッチ 完了")
res := "RDSを停止しています。"
return res, nil
}
/**************************
メイン
**************************/
func main() {
lambda.Start(run)
}
RDSステータス取得
package main
import (
"fmt"
"log"
"os"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/rds"
"github.com/aws/aws-sdk-go/service/rds/rdsiface"
)
func DescribeDBStatus(svc rdsiface.RDSAPI) interface{} {
// os.Getenv()でLambdaの環境変数を取得
InstanceID := os.Getenv("InstanceID") // DB識別子
InstanceIDP := aws.String(InstanceID)
input := &rds.DescribeDBInstancesInput{
DBInstanceIdentifier: InstanceIDP,
}
result, err := svc.DescribeDBInstances(input)
if err != nil {
panic(err.Error())
}
status := *result.DBInstances[0].DBInstanceStatus
return status
}
/**************************
処理実行
**************************/
func run() (interface{}, error) {
log.Println("--- RDSステータス取得バッチ 開始")
log.Println("----- セッション作成")
svc := rds.New(session.Must(session.NewSession()))
log.Println("----- RDSステータス取得 実行")
status := DescribeDBStatus(svc)
log.Println("--- RDSステータス取得バッチ 完了")
return status, nil
}
/**************************
メイン
**************************/
func main() {
lambda.Start(run)
}
Slackのチャンネル準備
まずは自分のDMの中で試してみようと思いましたが、上手くいきませんでした。
WebhookでSlackに通知するときは自分のDMに送信することができますが、Chatbotは自分のDMで動かすことができません!なのでチャンネルを作成するようにしましょう。
操作できる人を限定するために、今回はプライベートチャンネルを作成しました。プライベートチャンネルの場合は、下記のコマンドを対象のチャンネルで実行して、Chatbotユーザを招待してください。(試せていませんが、パブリックチャンネルの場合は不要みたいです。)
/invite @aws
マスキング部分にはチャンネル名が入ります。
Slackの準備はこれでOKです!
Chatbotの作成
IAMロール
Chatbotのチャンネル設定時に、いくつかポリシーテンプレートが用意されており、その中のLambda呼び出しコマンドのアクセス許可というポリシーを選択してIAMロールを簡単に作成することができます。
Lambda呼び出しコマンドのアクセス許可のポリシーテンプレートを使用すると下記のようなポリシーが作成されます。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:invokeAsync",
"lambda:invokeFunction"
],
"Resource": [
"*"
]
}
]
}
ただ、リソースは選択できないので、全てのLambda関数の実行権限があるポリシーがアタッチされます。また、非推奨?とされているinvokeAsync
の権限もついています。
ポリシーテンプレートで作成したとしても作成後に編集は可能ですが、実行できるLambda関数も絞りたかったので、今回は自分で作成しました。
ユースケースはAWS Chatbot - AWS Chatbot
を選択します。
そして、このポリシーを作成して、Chatbot用のロールにアタッチします。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": [
"<RDS起動Lambdaのarn>",
"<RDS停止Lambdaのarn>",
"<RDSステータス取得Lambdaのarn>"
]
}
]
}
Chatbotの作成
Chatbotの作成を行います!
チャットクライアントでSlack
を選択し、クライアント設定
をクリックします。そして、先ほど作成したチャンネルのあるワークスペースにサインインして、AWS Chatbotがワークスペースにアクセスできるように権限付与します。
新しいチャネルを設定
から設定を行います。
補足事項に沿って名前の設定、Slackのチャンネルの登録を行います。ちなみに、ここで設定する名前はAWS上で識別するのために使うもので、SlackのApp Nameではないです。
IAMロールには先ほど作成したロールを選択します。
今回は通知は行わないので、通知欄は特に設定しません。
これでChatbotの作成も完了です!
実行結果
実行はSlackのメッセージで下記のようにコマンドを送信します。
@aws service command --options
今回は、ChatbotでLambda関数の呼び出しを行うので次のコマンドを実行します。
@aws lambda invoke --function-name <Lambda関数名> --region ap-northeast-1
Lambda関数名にはそれぞれの関数名を入力してください。
RDSの起動
起動
起動用のLambda関数名を入れて上記のコマンドを実行すると**Would you like me to do so?**と確認されるので、[Yes]を選択します。
これでRDSを起動させることができます。
ステータス確認
起動が完了しました。ステータスを確認してみましょう。
ターゲットのRDSステータスのみが返ってきて、Payloadに入るようになっています。availableになっているのでRDSの起動が完了して利用可能なことがわかります!
ステータスの種類と意味はこちらへ
RDSの停止
停止
停止用のLambda関数名を入れたコマンドを実行して、[Yes]を選択します。
ステータス確認
停止が完了しました。ステータスを確認してみましょう。
PayloadがstoppedになっているのでRDSが停止していることがわかります。
無事Slackから操作ができていることを確認できました!
その他のコマンド
Chatbotのコマンドの全体的なことが知りたいときは次のコマンドを実行します。
@aws help
RDSのコマンドオプションが知りたいとき
あるAWSサービスについてのコマンドオプションが知りたいときは次のコマンドを実行します。
@aws rds --help
ただ、一覧に表示されてもChatbotに実行権限をつけていないと実行はできません。また、実行権限をつけていてもChatbotでできることで記載した通り、基本的には読み取り専用コマンドをサポートしているので、作成、削除、または構成するコマンドなどは実行できません。
もっと簡単に実行したい
今回使ったSlackは無料版だったので使えなかったのですが、有料版だとワークフロービルダーというSlackの機能を使うことができます。このワークフローを使うとコマンドを打たなくて良く、やりたい操作を選択するだけで実行できるので良さそうです。
参考
おわりに
すごく簡単にSlackとAWSを連携することができました!Chatbot自体にもロールを付与するので、指定した操作以外はできなくなっています。IAMユーザーがない人でも、操作できるようになるのは魅力的ですね!他のサービスでも応用ができるので、今後もいろんなところで使えそうです!
おまけ
ステータスの確認をLambdaを介さずに行ってみます。
Chatbotのポリシー追加
先ほど作成したChatbot用のポリシーに、RDSインスタンスの情報を取得する権限を追加します。
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "rds:DescribeDBInstances",
"Resource": "*"
}
実行結果
@aws rds describe-db-instances --db-instance-identifier <DB識別子> --region ap-northeast-1
取得した情報はまだまだこの下も続いています。
結果は長いですが、DBInstanceStatus
を確認することでステータスの確認ができます。
今回はステータスのみを抽出するためにLambda関数を使いましたが、AWS CLIのように --query
が使えるようになるといいなあと思いました。