はじめに
AWS Lambda Container Image Support や EventBridgeのcron設定、Secrets Managerからシークレット情報の取得を試してみたかったので、以下のような構成図で一気に試してみます。
構成図
- SAMでSlackにメッセージを通知するLambdaをデプロイする
- Lambdaはイメージ化してECRで管理する
- LambdaはEventBridgeで定期実行する
- SlackのWebhook URLをSecrets Managerで管理する
手順
- ECRにリポジトリを作成しておく
- Secrets ManagerにSlackのWebhook URLを設定する
- sam initでテンプレート作成
- template.yamlの修正
- Slackにメッセージ通知するLambda関数を作成
- デプロイ
- テスト
1. ECRにリポジトリを作成しておく
2. Secrets ManagerにSlackのWebhook URLを設定する
SlackのWebhook URLをSecrets Managerに登録します。
設定が完了すると、シークレット情報取得のサンプルコードが確認できるので、後でこれをベースにLambda関数を実装します。
3. sam initでテンプレート作成
事前準備ができたので、次はSAMのテンプレートを作成していきます。
適当にディレクトリを作成し、$ sam init
を実行します。
$ sam init
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
What package type would you like to use?
1 - Zip (artifact is a zip uploaded to S3)
2 - Image (artifact is an image uploaded to an ECR image repository)
Package type: 2
Which base image would you like to use?
1 - amazon/nodejs14.x-base
2 - amazon/nodejs12.x-base
3 - amazon/nodejs10.x-base
4 - amazon/python3.8-base
5 - amazon/python3.7-base
6 - amazon/python3.6-base
7 - amazon/python2.7-base
8 - amazon/ruby2.7-base
9 - amazon/ruby2.5-base
10 - amazon/go1.x-base
11 - amazon/java11-base
12 - amazon/java8.al2-base
13 - amazon/java8-base
14 - amazon/dotnet5.0-base
15 - amazon/dotnetcore3.1-base
16 - amazon/dotnetcore2.1-base
Base image: 10
Project name [sam-app]: notification
Cloning app templates from https://github.com/aws/aws-sam-cli-app-templates
-----------------------
Generating application:
-----------------------
Name: notification
Base Image: amazon/go1.x-base
Dependency Manager: mod
Output Directory: .
Next steps can be found in the README file at ./notification/README.md
先ほど作成したECRにイメージをプッシュしたいので、 What package type would you like to use?
は 2 - Image (artifact is an image uploaded to an ECR image repository)
を選択します。
ランタイムについては、goを選択しました。
$ sam init
実行後のディレクトリ構成は以下です。(notificationディレクトリを作成し、その配下で $ sam init
を実行しています。)
notification/
├── README.md
├── samconfig.toml
├── hello-world
│ ├── Dockerfile
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ └── main_test.go
└── template.yaml
package typeにimageを選択したため、Dockerfileが作成されています。
中身は以下のようになっています。
FROM golang:1.14 as build-image
WORKDIR /go/src
COPY go.mod main.go ./
RUN go build -o ../bin
FROM public.ecr.aws/lambda/go:1
COPY --from=build-image /go/bin/ /var/task/
# Command can be overwritten by providing a different command in the template directly.
CMD ["hello-world"]
4. template.yamlの修正
次に、$ sam init
で作成されたtemplate.yamlに修正を加え、以下のようになりました。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: notify slack
Globals:
Function:
Timeout: 5
Resources:
SlackRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
Policies:
- PolicyName: "GetSecretsPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
- "secretsmanager:GetSecretValue"
Resource: "*"
SlackFunction:
Type: AWS::Serverless::Function
Properties:
Role: !GetAtt SlackRole.Arn
PackageType: Image
Events:
Notification:
Type: Schedule
Properties:
Schedule: cron(0 0 * * ? *) # JST 9:00
Metadata:
DockerTag: go1.x-v1
DockerContext: ./slack
Dockerfile: Dockerfile
Outputs:
SlackFunction:
Description: "Notify Slack Lambda Function ARN"
Value: !GetAtt SlackFunction.Arn
SlackFunctionIamRole:
Description: "Implicit IAM Role created for Slack function"
Value: !GetAtt SlackRole.Arn
大きな修正点としては以下です。
- Role
- 実行スケジュール
Role
デフォルトで生成されるCloudWatch用のポリシーに、 Secrets Managerからシークレット情報取得用のsecretsmanager:GetSecretValue
を加えたロールを作成します。
(ロールの設定をここで記述しない場合、デフォルトでLambda実行に必要なロールが付与されます。)
Resources:
SlackRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
Policies:
- PolicyName: "GetSecretsPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
- "secretsmanager:GetSecretValue"
Resource: "*"
SlackFunction:
Type: AWS::Serverless::Function
Properties:
Role: !GetAtt SlackRole.Arn
実行スケジュール
EventBridgeのスケジュールをトリガーにしたいので、以下のように設定します。
Events:
Notification:
Type: Schedule
Properties:
Schedule: cron(0 0 * * ? *) # JST 9:00
5. Slackにメッセージ通知するLambda関数を作成
Secrets ManagerからSlackのWebhook URLを取得して、Slackにメッセージを送信します。
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"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/secretsmanager"
)
// Secrets Managerで設定したキーとリージョン
var (
secretName string = "SlackWebhookUrl"
region string = "ap-northeast-1"
)
type SlackEvent struct {
Text string `json:"text"`
}
func Modify(se *SlackEvent) {
se.Text = "test message"
}
// シークレット情報取得用function
func getSecret() (string, error) {
svc := secretsmanager.New(session.New(),
aws.NewConfig().WithRegion(region))
input := &secretsmanager.GetSecretValueInput{
SecretId: aws.String(secretName),
VersionStage: aws.String("AWSCURRENT"),
}
// GetSecretValue関数にキー名を渡して、値を取得
result, err := svc.GetSecretValue(input)
if err != nil {
return "", err
}
// SecretString配下にJSON形式でシークレット情報がレスポンスされます
secretString := aws.StringValue(result.SecretString)
res := make(map[string]interface{})
if err := json.Unmarshal([]byte(secretString), &res); err != nil {
return "", err
}
return res["WEBHOOK_URL"].(string), nil
}
func handler() error {
webhoolUrl, err := getSecret()
if err != nil {
return err
}
se := &SlackEvent{}
Modify(se)
params, err := json.Marshal(se)
if err != nil {
return err
}
// slackにメッセージ送信
resp, err := http.PostForm(
webhoolUrl,
url.Values{"payload": {string(params)}},
)
if err != nil {
return err
}
body, _ := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
fmt.Printf("response code:%s, response body:%s, hook:%s\n", resp.Status, body, webhoolUrl)
return nil
}
func main() {
lambda.Start(handler)
}
6. デプロイ
ここまで設定ができたら、デプロイしていきます。
$ sam build
→ $ sam deploy
の順に実行します。
修正内容に不備がなければ、以下のように$ sam build
が成功します。
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided
ビルドが完了したら、$ sam deploy
を実行してデプロイします。
はじめての実行の場合、以下のように --guided
オプションを付けることで、対話形式でデプロイの設定ができるので便利です。
$ sam deploy --guided
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Found
Reading default arguments : Success
Setting default arguments for 'sam deploy'
=========================================
Stack Name [notify-slack]:
AWS Region [ap-northeast-1]:
Image Repository for SlackFunction [xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/notify-slack]:
slackfunction:go1.x-v1 to be pushed to xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/notify-slack:slackfunction-eb1817a25a39-go1.x-v1
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [Y/n]: Y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: Y
Save arguments to configuration file [Y/n]: Y
SAM configuration file [samconfig.toml]:
SAM configuration environment [default]:
これで、CloudFormationにスタックが作成されます。
7. テスト
Scheduleを適当な時間に調整し、Webhook URLに設定したSlackチャンネルにメッセージが届けば確認完了です。
Events:
Notification:
Type: Schedule
Properties:
Schedule: cron(0 0 * * ? *) # JST 9:00
備考
コンソールのLambda関数の画面からテスト実行することもできます。
secretsmanager:GetSecretValue
のポリシーをロールにアタッチしていない場合、エラーとなりAPIが呼び出せないので注意が必要です。
AccessDeniedException: User: arn:aws:sts::901071604628:assumed-role/notify-slack-SlackFunctionRole-1IND13I9826OF/notify-slack-SlackFunction-AL8RRMO0EQ8H is not authorized to perform: secretsmanager:GetSecretValue on resource: SlackWebhookUrl