LoginSignup
2
2

More than 3 years have passed since last update.

SAMでSlack通知Lambdaをデプロイ

Posted at

はじめに

AWS Lambda Container Image Support や EventBridgeのcron設定、Secrets Managerからシークレット情報の取得を試してみたかったので、以下のような構成図で一気に試してみます。

構成図

  • SAMでSlackにメッセージを通知するLambdaをデプロイする
  • Lambdaはイメージ化してECRで管理する
  • LambdaはEventBridgeで定期実行する
  • SlackのWebhook URLをSecrets Managerで管理する

sam.png

手順

  1. ECRにリポジトリを作成しておく
  2. Secrets ManagerにSlackのWebhook URLを設定する
  3. sam initでテンプレート作成
  4. template.yamlの修正
  5. Slackにメッセージ通知するLambda関数を作成
  6. デプロイ
  7. テスト

1. ECRにリポジトリを作成しておく

イメージのプッシュ先のリポジトリを事前に登録しておきます。
screencapture-ap-northeast-1-console-aws-amazon-ecr-repositories-private-901071604628-notify-slack-edit-2021-03-29-02_14_19.png

2. Secrets ManagerにSlackのWebhook URLを設定する

SlackのWebhook URLをSecrets Managerに登録します。
スクリーンショット_2021-03-29_2_18_58(3).png

設定が完了すると、シークレット情報取得のサンプルコードが確認できるので、後でこれをベースにLambda関数を実装します。
スクリーンショット 2021-03-29 2.24.31(3).png

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が作成されています。
中身は以下のようになっています。

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関数の画面からテスト実行することもできます。
スクリーンショット 2021-03-29 3.35.41(3).png

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
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2