20200817のGoに関する記事は1件です。

AWS Chatbot + Lambda(Go)でSlackからRDSを操作する

はじめに

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にフォーカスしています。

chatbot.png

やってみる

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

スクリーンショット 2020-08-09 14.34.07.png

マスキング部分にはチャンネル名が入ります。
Slackの準備はこれでOKです!

Chatbotの作成

IAMロール

Chatbotのチャンネル設定時に、いくつかポリシーテンプレートが用意されており、その中のLambda呼び出しコマンドのアクセス許可というポリシーを選択してIAMロールを簡単に作成することができます。

Lambda呼び出しコマンドのアクセス許可のポリシーテンプレートを使用すると下記のようなポリシーが作成されます。


{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:invokeAsync",
                "lambda:invokeFunction"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

ただ、リソースは選択できないので、全てのLambda関数の実行権限があるポリシーがアタッチされます。また、非推奨?とされているinvokeAsyncの権限もついています。

ポリシーテンプレートで作成したとしても作成後に編集は可能ですが、実行できるLambda関数も絞りたかったので、今回は自分で作成しました。

スクリーンショット 2020-08-04 19.35.32.png

ユースケースは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の作成を行います!

スクリーンショット 2020-08-04 19.35.12.png

チャットクライアントでSlackを選択し、クライアント設定をクリックします。そして、先ほど作成したチャンネルのあるワークスペースにサインインして、AWS Chatbotがワークスペースにアクセスできるように権限付与します。

スクリーンショット 2020-08-04 23.23.29.png

新しいチャネルを設定から設定を行います。

スクリーンショット 2020-08-04 19.36.05.png

補足事項に沿って名前の設定、Slackのチャンネルの登録を行います。ちなみに、ここで設定する名前はAWS上で識別するのために使うもので、SlackのApp Nameではないです。

スクリーンショット 2020-08-04 19.36.17.png

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を起動させることができます。

スクリーンショット 2020-08-09 15.00.05.png

ステータス確認

起動が完了しました。ステータスを確認してみましょう。

スクリーンショット 2020-08-09 15.14.42.png

ターゲットのRDSステータスのみが返ってきて、Payloadに入るようになっています。availableになっているのでRDSの起動が完了して利用可能なことがわかります!

ステータスの種類と意味はこちら

RDSの停止

停止

停止用のLambda関数名を入れたコマンドを実行して、[Yes]を選択します。

スクリーンショット 2020-08-14 15.05.11.png

ステータス確認

停止が完了しました。ステータスを確認してみましょう。

スクリーンショット 2020-08-09 15.42.21.png

PayloadがstoppedになっているのでRDSが停止していることがわかります。

無事Slackから操作ができていることを確認できました!

その他のコマンド

Chatbotのコマンドの全体的なことが知りたいときは次のコマンドを実行します。

@aws help

RDSのコマンドオプションが知りたいとき

あるAWSサービスについてのコマンドオプションが知りたいときは次のコマンドを実行します。

@aws rds --help

ただ、一覧に表示されてもChatbotに実行権限をつけていないと実行はできません。また、実行権限をつけていてもChatbotでできることで記載した通り、基本的には読み取り専用コマンドをサポートしているので、作成、削除、または構成するコマンドなどは実行できません。

スクリーンショット 2020-08-09 15.42.29.png

もっと簡単に実行したい

今回使った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

スクリーンショット 2020-08-09 14.41.03.png

取得した情報はまだまだこの下も続いています。
結果は長いですが、DBInstanceStatusを確認することでステータスの確認ができます。

今回はステータスのみを抽出するためにLambda関数を使いましたが、AWS CLIのように --queryが使えるようになるといいなあと思いました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む