Slackに定期通知するLambda関数をCloudFormationとAWS CLIを使ってデプロイしてみた
7月からアノテーション テクニカルサポートチームにJOINしました 川崎です。
新しいチームで、心機一転がんばりたいと思います。
早速OJTという形で、テクニカルサポートの業務を学ぶ機会をいただいております。
日々蓄積されてきたナレッジを活用し、効率的に業務が運営されている現場に接することができ、おかげさまで、刺激的な毎日を送っています。
そこには、働き方改革のヒントがつまっています。
やってみた
さて、今回のブログでは、Lambda関数をCloudFormationとAWS CLIを使ってデプロイする方法を試してみました。
Pythonスクリプトをテンプレートに埋め込む方法や、あらかじめzip圧縮してS3バケットにアップロードする方法などは知っていましたが、 今回、ファイルとして用意するPythonスクリプトを、デプロイ時にcloudformation packageコマンドを使ってアップロードする方法を調べてみました。
1.準備するもの
Webhook URL
Slackで通知に使うIncoming WebhookのURLを作成しておきます。
S3バケット
CloudFormationのデプロイに使うS3バケットを作成しておきます。
CloudFormation用 IAMロール
CloudFormation を使用して、スタックのリソースを作成、変更、削除する IAM ロールを作成しておきます。
2.Lambda関数にするPythonファイル
Webhook URLを使用して、対象のSlackチャンネルに通知のメッセージを送付します。
import json from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError def lambda_handler(event, context): # 後でパラメータストアから取得する形にする slack_hook_url = "https://hooks.slack.com/services/XXXXXXXXX/YYYYYYYYYYY/zzzzzzzzzzzzzzzzzzzzzzzz" message = "Hello from Lambda!" slack_message = {"attachments": [{"text": message}]} req = Request(slack_hook_url, json.dumps(slack_message).encode("utf-8")) try: response = urlopen(req) response.read() print("Message posted to %s", "****" + slack_hook_url[-5:]) except HTTPError as e: print("Request failed: %d %s", e.code, e.reason) except URLError as e: print("Server connection failed: %s", e.reason)
3.テンプレートファイル準備
CloudFormationのテンプレートは2つ用意します。
1つ目のテンプレートファイル
ファイル名:template4package.yml
1つ目のテンプレートファイルは「aws cloudformation package」コマンドで、PythonスクリプトをS3バケットにアップロードするのに利用します。
AWSTemplateFormatVersion : '2010-09-09' Description: Lambda function template for package command. Resources: LambdaFunction: Type: AWS::Lambda::Function Properties: Code: source/
2つ目のテンプレートファイル
ファイル名:template_lambda.yml
2つ目のテンプレートファイルに、Lambdaの設定と、スケジュール実行のためのCloudWatch Eventsのルールを定義します。
AWSTemplateFormatVersion: '2010-09-09' Description: Lambda Template Parameters: CodeS3Key: Type: String DeploymentBucket: Type: String Resources: SlackNotifyLambdaFunction: Type: 'AWS::Lambda::Function' Properties: Code: S3Bucket: !Ref DeploymentBucket S3Key: !Ref CodeS3Key FunctionName: !Sub notify Handler: handler/lambda_function.lambda_handler MemorySize: 128 Role: !GetAtt - SlackNotifyLambdaRole - Arn Runtime: python3.7 Timeout: 15 TracingConfig: Mode: Active DependsOn: - SlackNotifyLambdaRole SlackNotifyEvent: Type: 'AWS::Events::Rule' Properties: Name: !Sub slack-notify-SlackNotifyEvent ScheduleExpression: cron(0 0 * * ? *) State: ENABLED Targets: - Arn: !GetAtt SlackNotifyLambdaFunction.Arn Id: SlackNotifyLambdaFunction SlackNotifyLambdaRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: 'sts:AssumeRole' ManagedPolicyArns: - !Ref SlackNotifyLambdaRolePolicy - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' SlackNotifyLambdaRolePolicy: Type: 'AWS::IAM::ManagedPolicy' Properties: Path: /slack-notify/ PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'sts:AssumeRole' Resource: '*' LambdaInvokePermissionSlackNotifyEvent: Type: 'AWS::Lambda::Permission' Properties: FunctionName: !Ref SlackNotifyLambdaFunction Action: 'lambda:InvokeFunction' Principal: events.amazonaws.com SourceArn: !GetAtt SlackNotifyEvent.Arn
4.スクリプトを作成する
CloudFormation のテンプレートを作成、更新するためのbashスクリプトを作成します。
#!/bin/bash set -eu if [ $# -ne 1 ]; then echo "指定された引数は$#個です。" 1>&2 echo "実行するには1個の引数が必要です。" 1>&2 echo "$0 [AWS CLI Profile]" exit 1 fi Profile=$1 echo "指定されたProfileは $Profile です。" unset AWS_PROFILE DeploymentBucket=slack-notify-s3-xxxxxx cfnRole=arn:aws:iam::xxxxxxxxxxxx:role/cfn-role s3Key="slack-notify" STACK_NAME="slack-notify" TEMPLATE_FILE="template4package.yml" StackTemplateUrl="template_lambda.yml" echo "$(date "+%Y/%m/%d %H:%M:%S") CloudFormation Stack: create "${STACK_NAME}" started." TOOL_DIR=$(dirname $0) cd ${TOOL_DIR} mkdir -p source/handler cp handler/lambda_function.py ./source/handler/ echo ${TEMPLATE_FILE} regexp="S3Key\: ([^ ]+)" CF_STACK_STATUS=$( \ aws --profile ${Profile} cloudformation package --template-file ${TEMPLATE_FILE} \ --s3-bucket "${DeploymentBucket}" --s3-prefix "${s3Key}" \ ) \ && echo ${CF_STACK_STATUS} if [[ "${CF_STACK_STATUS}" =~ $regexp ]] ; then CODES3KEY="${BASH_REMATCH[1]}" echo ${CODES3KEY} else echo "no match" fi echo "Lambdaを構築するCloudFormationを実行します" result=$(aws --profile ${Profile} cloudformation list-stacks --stack-status-filter CREATE_COMPLETE ROLLBACK_COMPLETE UPDATE_COMPLETE UPDATE_ROLLBACK_COMPLETE | jq '[.StackSummaries[]][]|select(.StackName=="'${STACK_NAME}'")') set +u if [ -n "$result" ]; then echo "Stackが存在していたのでStackの更新を行います" aws --profile ${Profile} cloudformation update-stack --stack-name ${STACK_NAME} --template-body "file://${StackTemplateUrl}" \ --capabilities CAPABILITY_NAMED_IAM \ --role-arn ${cfnRole} \ --parameters \ ParameterKey=CodeS3Key,ParameterValue=${CODES3KEY} \ ParameterKey=DeploymentBucket,ParameterValue=${DeploymentBucket} echo "Stackの更新完了を待ちます。" aws --profile ${Profile} cloudformation wait stack-update-complete --stack-name ${STACK_NAME} echo "Stackの更新が完了しました。" else echo "Stackが存在していないのでStackの作成を行います" aws --profile ${Profile} cloudformation create-stack --stack-name ${STACK_NAME} --template-body "file://${StackTemplateUrl}" \ --capabilities CAPABILITY_NAMED_IAM \ --role-arn ${cfnRole} \ --parameters \ ParameterKey=CodeS3Key,ParameterValue=${CODES3KEY} \ ParameterKey=DeploymentBucket,ParameterValue=${DeploymentBucket} echo "Stackの作成完了を待ちます。" aws --profile ${Profile} cloudformation wait stack-create-complete --stack-name ${STACK_NAME} echo "Stackの作成が完了しました。" fi
5.デプロイを実行する
指定されたProfileは xxdev です。 2021/07/07 22:28:39 CloudFormation Stack: create slack-notify started. template4package.yml Uploading to slack-notify/7eafd8db81a46ea7dc3eff236aadbc18 215 / 215.0 (100.00%) AWSTemplateFormatVersion: '2010-09-09' Description: Lambda function template for package command. Resources: LambdaFunction: Type: AWS::Lambda::Function Properties: Code: S3Bucket: slack-notify-s3-xxxxxx S3Key: slack-notify/7eafd8db81a46ea7dc3eff236aadbc18 slack-notify/7eafd8db81a46ea7dc3eff236aadbc18 Lambdaを構築するCloudFormationを実行します Stackが存在していないのでStackの作成を行います { "StackId": "arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/slack-notify/27a88940-df26-11eb-82f6-0a255f541851" } Stackの作成完了を待ちます。 Stackの作成が完了しました。
6.Lambda関数をテスト実行する
Lambda関数をテスト実行し、Slackの指定のチャンネルに通知がされるか、確認します。
まとめ
Lambda関数をCloudFormationとAWS CLIを使ってデプロイする方法を試してみました。
今回、ファイルとして用意するPythonスクリプトを、デプロイ時にcloudformation packageコマンドを使ってアップロードする方法を試してみました。
CloudFormationのテンプレートファイルを2つ用意するなど、 工夫が必要な箇所があるものの、無事Lambda関数をデプロイし、スケジュール実行させることができました。