「AWS無料相談会」をオンラインで開催中

CloudWatch Syntheticsを時刻指定で実行してみた

CloudWatch Syntheticsを時刻指定で実行してみた

CloudWatch Synthetics を、 AWS Lambda から時刻指定して実行するようにしました。
今回は、その背景と実行方法や注意事項についてご紹介いたします。

背景

弊社では、コーポレートサイトのお問い合わせページが正常に機能しているかという確認を、 Amazon CloudWatch Synthetics を使って行っています。

Amazon CloudWatch Synthetics はREST API、URL、ウェブサイトコンテンツを監視するためのサービスです。

2021年2月20日現在、 CloudWatch Synthetics のスケジュール設定画面には、下記のようになっており、「1回実行」「継続的に実行」の2つしか選べません。

「継続的に実行」の設定値としては最大で 60 までしか入力できないため、例えば「1日に1度だけ実行したい」場合や「XX月XX日の10時に実行したい」などの細かい設定は行なえません。

そのため、これまで弊社では実行タイミングを1時間に1度に(設定値を 60 に設定)した上で、実行された時間が9時台だったら処理を行うが、それ以外だったら何も行わない(処理を抜けてしまう)という実装にして、1日に1回の動作確認を行っていました。

【9時台にだけ実行する場合の実装例】

// only 09:00 JST
const now = new Date()
if (now.getHours() !== 0) return;
---その後の処理略---

この実装で、数カ月間運用してきたものの、なぜか徐々に実行時間がずれてきました。

【運用開始直後(2020/10/26)】

【最近(2021/02/13)】

画像をご覧いただけるとおわかりかと思いますが、運用開始直後は 09:01 に終わっている処理が、最近では 09:36 になっています。

こんな状況だったので、「あれ?今日は動作確認の通知が来てないな」と思った数分後に来ることがあったり、実際のお客様からのお問い合わせが来た!と思ったら動作確認の通知だった、ということがありました。
この状況を解消すべく、 CloudWatch Synthetics を実行するだけの AWS Lambda を作り、それを時刻指定で実行するように変更しました。
AWS Lambda はこういった「決まった時間」や、「S3にファイルが置かれたというようなイベント」をトリガーに、ちょっとした関数を安価に実行したいという場合に最適です。

実行方法

Lambda 関数作成

Lambda 関数を作成します。
Lambda の関数としては主に synthetics の StartCanary を実行するだけの処理になっています。

package main

import (
    "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/synthetics"
)

const region = "ap-northeast-1"

func main() {
    lambda.Start(startSynthetics)
}

func startSynthetics() error {
    name := os.Getenv("CANARY_NAME")
    sess, err := session.NewSession(&aws.Config{Region: aws.String(region)})
    if err != nil {
        log.Fatal(err)
    }

    svc := synthetics.New(sess)
    input := &synthetics.StartCanaryInput{
        Name: aws.String(name),
    }
    _, err = svc.StartCanary(input)
    if err != nil {
        log.Fatal(err)
    }
    return nil
}

汎用的に使えるように、Canary名を CANARY_NAME の環境変数から取得するようにしました。

serverless framework の設定

先程作成した関数を、 serverless framework を使ってデプロイします。
serverless framework の設定ファイル serverless.yml を下記のように設定します。

service: synthetics-start
frameworkVersion: '2.23.0'

provider:
  name: aws
  runtime: go1.x
  stage: prd
  region: ap-northeast-1
  timeout: 300
  memorySize: 128
  iamManagedPolicies:
    - "arn:aws:iam::aws:policy/CloudWatchSyntheticsFullAccess"
  iamRoleStatements:
    - Effect: Allow
      Action:
        - logs:CreateLogGroup
        - logs:CreateLogStream
        - logs:PutLogEvents
      Resource: "arn:aws:logs:*:*:*"
  environment:
    CANARY_NAME: ${env:CANARY_NAME}

package:
  exclude:
    - ./**
  include:
    - ./bin/**

functions:
  syntheticsstart:
    handler: bin/app
    events:
      - schedule: cron(0 0 * * ? *)

ポイントとしては、 iamManagedPoliciesCloudWatchSyntheticsFullAccess を付与することです。
これがないと、Lambda から CloudWatch Synthetics が実行できません。

iamManagedPolicies:
  - "arn:aws:iam::aws:policy/CloudWatchSyntheticsFullAccess"

CloudWatchSyntheticsFullAccess を付与することに抵抗がある場合は、下記のページを参考により厳密な権限を設定してみて下さい。

毎日、日本時間の9時に実行したい場合は下記のようにcron設定します。

events:
  - schedule: cron(0 0 * * ? *)

あとは、デプロイすれば終わり、なのですが、最後に1つだけ注意事項があります。
CloudWatch SyntheticsCanary の設定が「継続的に実行」になっている状態だと、Lambda から Canary を呼び出した時に、下記のようなエラーが出てしまいます。

ConflictException: Canary is not in a startable state.

これを回避するためには、スケジュールを「1回実行」に変更しておかなければならないので、忘れずに変更しておきましょう。

まとめ

以上、 CloudWatch Synthetics を時刻指定で実行してみた背景と実行方法や注意事項でした。
今回使用したファイルや、実行環境については、 GitHub にあげてあるので、もしご興味があればご覧ください。