Web ページから、AWS Lambda の関数に引数付きでアクセスする

次の AWS Lambda 関数に、API Geteway 経由でアクセスする方法です。
これは、3つの整数、aa,bb,cc を与えて合計 sum を計算するプログラムです。

sum_up.py
# -*- coding: utf-8 -*-
#
#   sum_up.py
#
#                   Oct/20/2017
#
# --------------------------------------------------------------------
import sys
import json

# --------------------------------------------------------------------
def sum_up_handler(event, context):
    sys.stderr.write("*** sum_up_handler *** start ***\n")
#
    aa = 0
    bb = 0
    cc = 0
#
    sys.stderr.write("Received event: " + json.dumps(event, indent=2) + "\n")
    if ('aa' in event):
        sys.stderr.write("aa = " + event['aa'] + "\n")
        aa = int(event['aa'])
#
    if ('bb' in event):
        sys.stderr.write("bb = " + event['bb'] + "\n")
        bb = int(event['bb'])
#
    if ('cc' in event):
        sys.stderr.write("cc = " + event['cc'] + "\n")
        cc = int(event['cc'])
#
    version = "Oct/19/2017 AM 16:42"
#
    sum = aa + bb + cc
#
    sys.stderr.write("version = " + version + "\n")
    sys.stderr.write("sum = " + str(sum) + "\n")
    sys.stderr.write("*** sum_up_handler *** end ***\n")
#
    rvalue = {}
    rvalue['aa'] = aa
    rvalue['bb'] = bb
    rvalue['cc'] = cc
    rvalue['sum'] = sum
    rvalue['version'] = version
    rvalue['language'] = 'Python'
#
    return rvalue
#
# --------------------------------------------------------------------

1) これを、AWS Lambda の関数にする方法は、

python の関数を AWS Lambda で使用する

2) API Gateway の設定は、

API Gateway から Lamda にアクセスする

3) API Gateway で cors を有効にします。

cors_oct1901.png

4) Web ページとスクリプトです。jQuery を使います。

cors.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="CONTENT-TYPE" content="text/html; charset=utf-8" />
<script src="jquery-3.2.1.min.js"></script>
<script src="cors.js"></script>
<title>AWS API Gateway</title>
</head>
<body>

<div id="answer">answer</div>


<hr />
<div id="outarea_aa">outarea_aa</div>
<div id="outarea_bb">outarea_bb</div>
<div id="outarea_cc">outarea_cc</div>
<div id="outarea_dd">outarea_dd</div>
<div id="outarea_ee">outarea_ee</div>
<div id="outarea_ff">outarea_ff</div>
<div id="outarea_gg">outarea_gg</div>
<div id="outarea_hh">outarea_hh</div>


<hr />
Version: Oct/19/2017 PM 15:19<br />

</body>
</html>

次のプログラムの rest_api_id は、変更して下さい。

cors.js
// -----------------------------------------------------------------------
//
//  cors.js
//
//                      Oct/20/2017
//
// -----------------------------------------------------------------------
jQuery (function ()
{
    jQuery("#outarea_aa").text ("*** cors.js *** start *** PM 18:28")

    getPost()

    jQuery("#outarea_hh").text ("*** cors.js *** end ***")
})

// -----------------------------------------------------------------------
function getPost()
{
    const rest_api_id="bbc2fbufm6"

    const url="https://" + rest_api_id + ".execute-api.ap-northeast-1.amazonaws.com/test"

    const params = {'aa': '121','bb': '304','cc': '256'}

    const args = JSON.stringify(params)

    jQuery.post(url,args,function(res)
        {
        const sum = res.sum
        var str_out = ""
        str_out += "aa = " + params['aa'] + "<br />"
        str_out += "bb = " + params['bb'] + "<br />"
        str_out += "cc = " + params['cc'] + "<br />"
        str_out += "sum = " + sum + "<br />"
        jQuery("#answer").html (str_out)
        })
}

// -----------------------------------------------------------------------

続きを読む

S3 → Lambda → SQS にメッセージを投げるまでの構築手順!

やりたいこと

タイトルの通り、「Lamda」から「Amazon SQS」に「メッセージを送信」すること。

All.jpg

具体的には、こんな感じ。

処理フロー

  1. S3にファイルをアップロードする。( User )
  2. ファイルアップロードをトリガーにAmazon SQSにメッセージを送信する。( Lambda )
  3. Amazon SQS内のキューにメッセージを格納する。( Amazon SQS )
  4. 5分間隔で、Amazon SQSのキューのメッセージ個数をログ出力する。( Lambda )

実装する前に、ちょっとだけAmazon SQSについて紹介します。

What’s Amazon SQS !?

SQS.jpg

そもそもメッセージキューイングって何?

Message Queueing ( MQ : メッセージキューイング )

アプリケーションソフト間でデータを交換して連携動作させる際に、送信するデータをいったん保管しておき、
相手の処理の完了を待つことなく次の処理を行う方式。

メッセージキューイングの仕組みを活用することにより、
メッセージ送信者は受信側のステータスを気にすることなく、
メッセージを送信し、送信側と受信側間で非同期での処理が可能に。

Amazon SQSは、Pull型のMQサービス

MQ.jpg

受信側 (Instance) がSQSに問い合わせて、メッセージを取得します。

Amazon SQSは、AWSフルマネージドな分散キュー

フルマネージドとは ?

サービスの利用に必要な機器などの運用や管理、
導入時に必要な機器の設置や設定なども一体として提供するサービスのこと。

  • 高い信頼性 : 複数のサーバー/データセンターにメッセージを保持
  • スケーラブル : 多数の送信者/受信者に対応
  • 高スループット : メッセージが増加しても高スループットを出し続ける
  • 低コスト : 毎月の無料枠 + 使った分だけの従量課金

動作検証

(1) Lambda関数作成

※ 作成手順の詳細は、下記記事を参考にしていただければと思います。
Lambda関数を作成する方法 ~S3へのファイルアップロードをトリガーにログ出力編~

ここでは、特に重要な部分のみ抜粋しています。

1.トリガーの設定


ここでは、トリガーの対象となるS3のバケット名とイベント(今回は、「オブジェクトの作成」)の指定を行います。
また、作成後すぐに検証が行えるように、「トリガーの有効化」にチェックを入れておきます。

image.png

2.関数コードの編集


続いて、関数コードの編集を行います。ここでは、トリガーの対象となるイベント発生時に
どのような処理をおこなうのかを記述していきます。(Node.js 6.10)

今回は、下記のAmazon SQSへの操作を実装していきます。

(1) Amazon SQSに、新しいキューを作成 (AWS Console上で作成)

Amazon SQSのページを開き、「新しいキューの作成」ボタンを押下します。

image.png

任意の「キュー名」を付けます。

image.png

キューの作成」を押下すると、新しくキューが作成され、一覧に表示されます。

image.png

続いて、作成したキューにメッセージを送信します。

(2) (1)で作成したキューに、メッセージを送信

image.png

sendMessage.js

console.log('Loading function');

const AWS = require('aws-sdk');
const SQS = new AWS.SQS({apiVersion: '2012-11-05'});
/* 宛先QueueのURL */
const QUEUE_URL = 'https://sqs.ap-northeast-1.amazonaws.com/<アカウントID>/<キューの名前>';

exports.handler = function(event, context) {

    // Get Upload FilePath
    var Bucket = event.Records[0].s3.bucket.name;
    var ObjKey = event.Records[0].s3.object.key;
    var FilePath = Bucket + '/' + ObjKey;

    // SendMessage
    var params = {
        MessageBody: FilePath, /* required */
        QueueUrl: QUEUE_URL,   /* required */
        DelaySeconds: 0
    };

    SQS.sendMessage(params, function(err, data) {
        if (err) console.log(err, err.stack); // an error occurred
        else     console.log(data);           // successful response
    });

};

※ちなみに、対象キューのURLは、Amazon Console画面上から確認することができます。(SDKでも可。) [下図、赤色部分]

image.png

これで、S3にファイルをアップロードすると、Messageが送信されると思います。

と、その前に、Lambda上で、sendMessageをするためには、
実行ロールにAmazon SQSへの書き込み権限、ポリシーのアタッチをする必要があります。

3.アクセスポリシーのアタッチ


実行RoleにSQSアクセスポリシーをアタッチ

image.png

AmazonSQSFullAccess」のポリシーを実行ロールにアタッチします。

image.png

これですべての準備が整いました。
S3にファイルをアップロードしてみましょう。

(2) S3へファイルアップロード

上記で作成したLambda関数が動作をしているか実際に検証してみます。

適当なファイルを対象のバケット内にアップロード

image.png

Lamda関数が実行されていれば、Amazon SQSにメッセージが届いているはずです。

image.png

利用可能なメッセージが1になりました。もう1つS3にファイルをアップロードしてみると、、。

image.png

しっかりと、利用可能なメッセージが2に増えていますね。

終わりに

ひとまず、[S3へファイルアップロード][Lambda関数実行][Amazon SQSにメッセージ追加] までの
一連の流れが動くという検証ができました。

次回記事で、キュー内のメッセージ個数を確認する[Lambda関数]を作成したいと思います。

最後までお読みいただきありがとうございました。

続きを読む

【注意】Amazon Aurora(MySQL)でZero Downtime Patch実行時に不具合が発生するケースがある【AWS】

はじめに

先日、Amazon Aurora(MySQL)の必須アップデートの案内が来たのでアップデートの検証作業を実施しておりました。
Zero Downtime Patch(以下、ZDP)が実行され、ダウンタイム無しでアップデートが行われるはずが、不具合が発生してしまいました。

ググっても関連する情報が見つからなかったので、取り急ぎ分かっている情報について記録しておきます。

なお、対象のアップデートは下記のものになります。(Cluster Version 1.14)

Database Engine Updates 2017-08-07
http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Aurora.DatabaseEngineUpdates.20170807.html

ポイント(TL;DR)

重要なポイントは下記のとおりです。

Aurora(MySQL)に対し、DB Connection Poolを利用し、且つPrepared Statementを利用していると、Zero Downtime Patch実行時に不具合が発生する可能性が高い。
Aurora(MySQL)のアップデートを行う前に、アプリの構成・実装を調べた方が良い。

Zero Downtime Patch(ZDP)とは何か

Auroraを無停止(正確には5秒程度の遅延)でアップデートできる画期的な仕組みです。
アップデート処理中にも、クライアントからのDB接続は維持されます。

今回、Cluster Version 1.14へのアップデートとなりますが、本アップデートに関するドキュメントにも説明があります。
http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Aurora.DatabaseEngineUpdates.20170807.html

AuroraのCluster Version 1.10で初めて導入されたようです。
ZDPの内容について、下記ドキュメントにも記載があります。

Database Engine Updates 2016-12-14
http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Aurora.DatabaseEngineUpdates.20161214.html

こちらの記事も情報がまとまっていて参考になります。

Amazon Aurora のアップグレード
https://qiita.com/tonishy/items/542f7dd10cc43fd299ab

事象の内容

Aurora(MySQL)でClusterのアップデートが必要の旨の案内が来たため、AWS Management Consoleからアップデートを行いました。
AuroraのClusterアップデートは無事に完了しましたが、当該Auroraインスタンスを利用しているJavaアプリが軒並みエラーを吐き出し始めました。
その後Javaアプリを再起動し、復旧しました。

AWS Management Console上、WriterとなっているインスタンスのEventに下記のものが表示されました。
従って、Zero Downtime Patchが行われていたことは間違いありません。

The database was successfully patched with a zero downtime patch.

Javaアプリのログ上には、「Unknown prepared statement handler」といったエラーが多数出力されておりました。

事象の原因

当該JavaアプリはJDBC Connection Poolを利用し、DBとの接続を維持する構成になっています。
また、Prepared Statementを利用する実装になっております。

ZDP実行時、MySQLのクライアント(i.e. Javaアプリ)からの接続は維持されるようですが、Prepared Statementは維持されないことが原因のようでした。
時系列的に整理すると、下記のような状況になっていたようです。

  1. Javaアプリ起動時、Auroraに対して幾つかJDBC接続を確立する
  2. リクエストに応じて、JavaアプリからAuroraにクエリを発行する。この際、Prepareを発行してPrepared Statementを作る。
    Javaアプリは、同一のリクエストに対してはPrepared Statementを再利用する。
  3. AuroraのアップデートをZDPで行う。この際、JavaアプリからのJDBC接続は維持されるが、Prepared Statementは全てクリアされる。
  4. Javaアプリに対し、項番2と同様のリクエストを行う。Javaアプリは、コンパイル済みのPrepared Statementが存在しているという認識のまま、これを再利用しようとし、エラーとなる。

同一のアプリに対し、Prepared Statementを利用するもの/しないものを用意してAuroraのアップデートを実施しました。
結果、前者はエラーが発生し、後者はエラーが発生しませんでした。

なお上記の挙動は、独自に調査・検証したものとなります。
現在、AWSの公式見解・ドキュメント等の情報は見つけられておりません。
# 一応、Database Engine Updates 2016-12-14には以下の記載がありますが、これが該当するかどうかは不明です。
# Note that temporary data like that in the performance schema is reset during the upgrade process.

2017/9/26にAWSサポートへの問い合わせを行っておりますが、2017/10/19現在、回答待ちの状況です。

確認した環境・構成

上記事象は、下記構成のJavaアプリで確認しました。

  • Java 8 (Oracle JDK)
  • spring-boot-1.4.0.RELEASE
  • mybatis-spring-boot-starter 1.1.1
  • mariadb-java-client 1.5.5

併せてMyBatisGeneratorも利用しています。
特別な理由がない限り、JavaアプリからはMyBatisGeneratorで生成されたMapperとClassを利用しています。

デフォルトでは、MyBatisGeneratorではPrepared Statementを利用するMapperを生成するようです。

回避策

回避策は概ね以下の3通りになると思います。

1.Prepared Statementを利用しないよう、アプリを変更する。

ソースコード/ORマッパーの改修・設定変更を行い、Prepared Statementを利用しないようにする方法です。
おそらく、この対応は難しいケースがほとんどだと思います。

2.MySQL接続ドライバの設定で、Prepared Statementを利用しないよう変更する。 

今回のケースでは、MariaDB Connector/Jを利用しており、オプションを指定することでPrepared Statementを利用しないように変更することができました。

useServerPrepStmtsオプションをfalseにすることで実現可能です。
ちなみに、1.6.0以降のバージョンはデフォルトでfalseになっているようですので、これ以降のバージョンを利用してる場合は、本件の影響を受けません。

About MariaDB Connector/J -> Optional URL parameters -> Essential options
https://mariadb.com/kb/en/library/about-mariadb-connector-j/

3.ZDPではない、通常のアップデートを実施する

Auroraインスタンスの再起動を伴う、通常のアップデートを実行することで、本件事象を回避することが可能と考えられます(※未検証)
20~30秒程度のダウンタイムが発生します。

下記に、ZDPにならないケースについて記載がありますので、このどれかを満たせば通常のアップデートになると推測できます(※未検証)

Database Engine Updates 2017-08-07 -> Zero-Downtime Patching
http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Aurora.DatabaseEngineUpdates.20170807.html

  • Long-running queries are in progress
  • Open long-running transactions exist
  • Binary logging is enabled
  • Binary log replication is running
  • Pending parameter changes exist
  • Temporary tables are in use
  • Table locks are in use
  • Open SSL connections exist

まとめ

DBへのConnection Poolを利用し、且つPrepared Statementを利用するようアプリを実装していると、不具合が発生する可能性が高いです。

経験的な感覚ですが、JavaアプリはConnection Poolを利用しているケースが多いかと思います。
Aurora(MySQL)を利用している場合は、チェックをした方が良いかと思います。

検証作業について

古いClusterバージョンのAuroraインスタンスを作ることはできません。AWSサポートにも聞いてみましたが無理なようでした。
従って、運良く過去に構築した古いAuroraインスタンスが存在していれば検証は可能ですが、それ以外のケースでは検証手段がありません。

続きを読む

JavaScriptで、Cognitoコード認証フローを実装したまとめ

前書き

ユーザ認証のCognitoを使って、ユーザ認証を実装してみました。
今回はそのまとめです。
実装したフローは、いろんなサービスでよくある以下のフローです。

  1. サインアップ
  2. 認証コードとURLが届く
  3. URL先の認証コード入力画面でコードを入力する

上記のフローを実装して見たので、まとめます。

実装

Cognitoの設定

まずはじめに、Cognito側でメッセージがコードであることを確認します。
スクリーンショット 2017-10-18 23.37.09.png

あらかじめ、ユーザープールIDとクライアントIDは控えておきましょう。

コード認証実装

AWS-SDK、およびCognitoを利用するに当たって、以下のライブラリが必要なのでnpmかyarnでインストールします。

# npm
npm install aws-sdk
npm install amazon-cognito-identity-js
# yarn
yarn add aws-sdk
yarn add amazon-cognito-identity-js

2個目は、JavaScript上でCognitoを使いやすくしてくれるライブラリです。

Github

では早速、最初の設定部分と実コード部分を実装していきます。
新規に、util的な扱いとしてcognito.jsを作成します。
長くなるので分けて解説し、最後にまとめて記載します。

初期設定

// src/utils/cognito.js

import AWS from 'aws-sdk';
import {
  CognitoUserPool,
  CognitoUser
} from 'amazon-cognito-identity-js';

import * as aws from './../config/aws';

AWS.config.region = aws.AWS_REGION;

AWS.config.update({
  accessKeyId: aws.AWS_ACCESS_KEY,
  secretAccessKey: aws.AWS_SECRET_ACCESS_KEY
});

const poolData = {
  UserPoolId: aws.AWS_COGNITO_USER_POOL_ID,
  ClientId: aws.AWS_COGNITO_APP_CLIENT_ID
};

const userPool = new CognitoUserPool(poolData);

AWSのクレデンシャルをAWS.config.updateで設定し
poolDataという形で、プールIDとクライアントIDを設定し、CognitoUserPool関数に渡して
ユーザプールを作成します。
初期設定はこれだけです。

1. サインアップ

// Signup
export const signup = (formValue) => {
  return new Promise((resolve, reject) => {
    const attributeList = [
      {
        Name: 'email',
        Value: formValue.email
      }
    ];

    userPool.signUp(formValue.email, formValue.password, attributeList, null, (err) => {
      if (err) {
        reject(err);
      }
      resolve();
    });
  });
};

初期設定で作成したuserPoolのsignUpを利用します。
第1引数にemail、第2引数にパスワードを渡して第3引数にはユーザ属性(基本的にemailだけですかね)の配列を渡します。
第4引数は公式をみても、nullを渡していたのでnullを渡します。
第5引数がコールバックになります。
ちなみに、カスタム属性を利用する場合は、Name部が以下になります。

// firstNameがカスタム属性の場合
{
  Name: 'custom:firstName',
  Value: formValue.firstName
}

これで、指定したemailにCognitoから認証コードを乗せたメールが届きます。

2. 認証コードとURLが届く

デフォルトでは、認証コードのみが届きます。
コードさえあれば、認証は可能ですが
やはりメールに認証コード入力画面へのURLが欲しいと思います。
ここは、別の記事で細かく解説したいですが、Lambdaの作成が必要になります。
作成するLambdaの中身は以下です。

// LambdaHandler
module.exports.sendVerifyMessage = (event, context, callback) => {

  // 指定のUserPoolIdとリクエストのUserPoolIdが同じ
  if (event.userPoolId === process.env.USER_POOL_ID) {
    // トリガーがカスタムメッセージ
      if (event.triggerSource === 'CustomMessage_SignUp') {
        // 実処理
        // 件名
        event.response.emailSubject = "件名";
        // 本文
        event.response.emailMessage = "本文";
      }
  }

  // Return result to Cognito
  context.done(null, event);
};

Cognitoが呼び出した際に、eventの中にサインアップ情報が含まれています。
event.response.emailSubjectに件名をemailMessageに本文を入れて返すことでメールが送信されます。
HTMLメールの利用ができるので、簡単にテンプレートを作成して入れるといいです。
eventから取得できる関連するパラメータは以下の通りです。

// メールアドレス
event.userName
// 認証コード
event.request.codeParameter
// そのほかカスタム属性
event.request.userAttributes['custom:hogehoge']

入力画面で利用するため、URLの最後にemailをパラメータとして渡す必要があります。
作成したLambdaはトリガー設定のカスタムメッセージへ設定します。

スクリーンショット 2017-10-19 0.32.41.png

URL先の認証コード入力画面でコードを入力する

認証コード入力画面では、基本的に2回の処理が必要です。

  1. パラメータで飛んで来たemailが正しいメールかどうか
  2. 認証コードで認証する

それぞれを実装すると以下の形になります。

1. パラメータで飛んで来たemailが正しいメールかどうか

export const checkUser = ((email) => {
  return new Promise((resolve, reject) => {
    if (!email) {
      reject('Reject');
    }
    const userData = {
      Username: email,
      Pool: userPool
    };

    const cognitoUser = new CognitoUser(userData);

    if (!cognitoUser) {
      reject('Reject');
    }

    resolve(cognitoUser);
  });
});

パラメータで取得したemailとuserPoolを利用してcognitoUserを利用します。
存在しないユーザの場合はrejectされます。

2. 認証コードで認証する


export const activateUser = ((email, code) => {
  return new Promise((resolve, reject) => {
    const userData = {
      Username: email,
      Pool: userPool
    };

    const cognitoUser = new CognitoUser(userData);
    cognitoUser.confirmRegistration(code, true, (err) => {
      if (err) {
        console.log(err);
        reject(err);
      } else {
        // 成功
        resolve();
      }
    });
  });
});

cognitoUserのconfirmRegistrationを利用します。
ここでまたuserDataを利用するので、最初のcheckUserで作成したcognitoUserを再利用してもいいかもしれません。

まとめ

ここまでで作成したソースを記載してまとめとします。


import AWS from 'aws-sdk';
import {
  CognitoUserPool,
  CognitoUser
} from 'amazon-cognito-identity-js';

AWS.config.region = aws.AWS_REGION;

AWS.config.update({
  accessKeyId: aws.AWS_ACCESS_KEY,
  secretAccessKey: aws.AWS_SECRET_ACCESS_KEY
});

const poolData = {
  UserPoolId: aws.AWS_COGNITO_USER_POOL_ID,
  ClientId: aws.AWS_COGNITO_APP_CLIENT_ID
};

const userPool = new CognitoUserPool(poolData);


// Signup
export const signup = (formValue) => {
  return new Promise((resolve, reject) => {
    const attributeList = [
      {
        Name: 'email',
        Value: formValue.email
      }
    ];

    userPool.signUp(formValue.email, formValue.password, attributeList, null, (err) => {
      if (err) {
        reject(err);
      }
      resolve();
    });
  });
};

// User Check
export const checkUser = ((email) => {
  return new Promise((resolve, reject) => {
    if (!email) {
      reject('Reject');
    }
    const userData = {
      Username: email,
      Pool: userPool
    };

    const cognitoUser = new CognitoUser(userData);

    if (!cognitoUser) {
      reject('Reject');
    }

    resolve(cognitoUser);
  });
});

// Code Activate
export const activateUser = ((cognitoUser, code) => {
  return new Promise((resolve, reject) => {
    cognitoUser.confirmRegistration(code, true, (err) => {
      if (err) {
        console.log(err);
        reject(err);
      } else {
        // 成功
        resolve();
      }
    });
  });
});

後書き

早口な説明になりましたが、
AWSのSDKがなかなか充実しているので、思ったフローが簡単に実装できました。
サービスのユーザの認証は、
ユーザがサービスの印象を感じる部分でもあるので、しっかり実装していきたいですね。

続きを読む

python の関数を AWS Lambda で使用する

次の python の関数を、aws の Lambda にアップロードします。

evening_function.py
# -*- coding: utf-8 -*-
#
#   evening_function.py
#
#                   Oct/18/2017
#
# --------------------------------------------------------------------
import sys
import json

# --------------------------------------------------------------------
def evening_handler(event, context):
    sys.stderr.write("*** evening_handler *** start ***\n")
    print("Received event: " + json.dumps(event, indent=2))
    print("name = " + event['name'])
    print("AAA value1 = " + event['key1'])
    print("BBB value2 = " + event['key2'])
    print("CCC value3 = " + event['key3'])
    print("DDD value4 = " + event['key4'])
    print("EEE value5 = " + event['key5'])
    sys.stderr.write("*** evening_handler *** end ***\n")
#
    return event['name']  # Echo back the first key value
    #raise Exception('Something went wrong')
# --------------------------------------------------------------------

1) zip で、固めます。

zip -r evening.zip evening_function.py

2) aws にアップロードします。
 morning_exec_role というロールが作られているとします。 ロールの作り方は、
aws cli でラムダを使う

function_create.sh
aws lambda create-function \
    --function-name evening_function \
    --runtime python3.6 \
    --role arn:aws:iam::178234159025:role/morning_exec_role \
    --handler evening_function.evening_handler \
    --zip-file fileb://evening.zip \
    --region ap-northeast-1

3) CLIからイベントを発火

cli_exec.sh
aws lambda invoke --invocation-type Event \
    --function-name evening_function \
    --region ap-northeast-1 \
    --payload  '{"name": "島崎藤村","key1":" こんにちは", "key2":"おはよう", "key3":"さようなら","key4": "Hello","key5": "Bonjour"}' \
    outputfile.txt

4) CloudWatchLogsにログが出力されているか確認

続きを読む

Lambda関数を作成する方法 ~S3へのファイルアップロードをトリガーにログ出力編~

はじめに

Lamba関数を作成しようといろいろ調べてみたが、
GUIのアップデートも早く、現状と同様の画面で作成している手順が見当たらなかったので、
自分の備忘録も含めて、Qiita記事に書き残しておくことにします。

今回実現したいこと

トリガー : S3バケットにファイルがアップロードされる
処理内容 : オブジェクトキーを含めたファイルパスをログに出力

設定方法

1. AWS Lambda設定画面を開き、関数の作成ボタンを押下。

image.png

2. 「一から作成」ボタンを押下

image.png

3. 「カスタムロールの作成」を選択

image.png

4. カスタムロールを作成

3.の操作後、新しいウィンドウで、カスタムロール作成画面が表示されるので、何も設定を変えず作成を行う。

image.png

5. Lamda関数の基本情報を入力

image.png

実行ロールについては、既存のロールを選択し、先ほど作成したロールを選択する。

6. Lamda関数が生成されました

image.png

トリガーが設定されていないので、設定を行います。

7. トリガーの設定

[トリガー]タブの「トリガーを追加」ボタンを押下

image.png

下記、詳細設定になります。

image.png

バケット欄に対象となるバケット名を指定し、「トリガーの有効化」設定を行います。

image.png

9. 実行ソースコードの修正

とりあえず動いていることを確認したいので、
アップロードされたら、バケット名とオブジェクトキーを出力するといった簡易なソースコードを用意します。

image.png

index.js

console.log('Loading function');

exports.handler = function(event, context) {
    var Bucket = event.Records[0].s3.bucket.name;
    var ObjKey = event.Records[0].s3.object.key;
    var FilePath = Bucket + '/' + ObjKey;
    console.log('BucketName : ' + Bucket);
    console.log('ObjKey : ' + ObjKey);
    console.log('FilePath : ' + FilePath);
};

「 Ctrl + S 」で保存し、指定のS3バケットにファイルをアップロードしてみてください。

10. ログ出力の確認

ログの確認は、 [モニタリング] タブの中にある、「CloudWatchのログを表示」というボタンを押下し、
遷移したページにて確認することができます。

image.png

一覧が表示される。

image.png

ログ出力中身確認。

image.png

無事、動作確認することができました。

とにかく動作検証なので、動くところまで、
必要な情報に関しては、今後随時更新していこうと思います。
最後までお読みいただき、ありがとうございました!

続きを読む

Serverless FrameworkでEC2をスケジュール起動/停止するテンプレート(Lambda[java])

Serverless Framework

はじめに

  • コンテナ付いてる昨今は、久しくServerless Framework触って無かったのですが、EC2を8時に起動して、20時半に停止する要件が浮上したので、サクッとslsで作りました。
  • ソースはGithubで公開してます。
  • 至極簡単な内容なので、速攻実現出来ると思ってます。

環境のセットアップ

Serverless FrameworkでDeploy

ソースの取得

  • 以下のGithubからソースを取得します。
$ git clone https://github.com/ukitiyan/operation-ec2-instance.git

STS(Eclipse)にインポート

  • STSを起動して、Project Explorer -> 右クリック -> Maven -> Existing Maven Projectsで先程Githubから取得した「operation-ec2-instance」フォルダを選択します。

serverless.yml の修正 + Build

  • serverless.ymlのL37 周辺の設定を適宜修正します。

    • rate: AWS Lambda – Scheduled EventのCron書式
      を参考に UTC で記載
    • instanceId: 対象インスタンスのinstanceIdを記載
    • scheduleは、縦に増やせるので複数インスタンスに対応できます。(それを踏まえて環境変数でinstanceIdを指定してません)
serverless.yml
- schedule:
    rate: cron(30 11 * * ? *)
    input:
      goal: stop
      instanceId: i-XXXXXXXXXXXXXXXXX
- schedule:
    rate: cron(0 23 * * ? *)
    input:
      goal: start
      instanceId: i-XXXXXXXXXXXXXXXXX
  • プロジェクトを右クリック -> Run As -> Maven Install でビルドします。
  • target配下にoperation-ec2-instance.1.0.0.jarが出来上がります。

Deploy

  • 例のごとく、以下のコマンド一発です。
$ cd operation-ec2-instance
$ serverless deploy -v
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
・
・
Serverless: Stack update finished...
Service Information
service: operation-ec2-instance
stage: prod
region: ap-northeast-1
api keys:
  None
endpoints:
  None
functions:
  aws-java-maven-prod-hello: arn:XXXXXXXX
  • 以下のJsonでコンソールから test するか、設定時間になるまで待って、問題ないか気にしておきましょう。
{
  "goal": "stop",
  "instanceId": "i-XXXXXXXXXXXXXXXXX"
}

まとめ

  • まぁ、やっつけですが。速攻実現出来ました。
  • STSで完結するのが、アプリ屋にとっては本当うれしいです。

続きを読む

Lambdaの実行環境にフォントを追加する

はじめに

AWS LambdaでPhantomJS日本語フォント対応では fontconfig をビルドしてデプロイパッケージに含めているが、 Lambdaの実行環境を確認したところ、fontconfig は導入されている。
したがって、フォントキャッシュさえ生成すれば、実行環境のfontconfigが利用できる。

なおLambdaの実行環境のfontconfigはXDGには対応していない。
検証時、fc-cache のバージョンは 2.8.0 であった。

Lambda の実行時 LAMBDA_TASK_ROOT=/var/task/share/fonts となっている。

fontconfig は

~/.fonts.conf
~/.fonts
~/.fontconfig

は見るので、HOME=$LAMBDA_TASK_ROOT に設定して、これらが

$HOME/
        .fontconfig
        .fonts
        .fonts.conf

というツリー構造で見えるようにデプロイパッケージに含めてやれば良い。

フォントキャッシュの生成

NotoSansCJK のパッケージから NotoSansCJK-Regular.ttc を抽出して、.fonts に配置する。

Lambda の実行時に $LAMBDA_TASK_ROOT/.fontconfig は書き込みできないため、そのままでは fc-cache の実行でキャッシュファイルの作成に失敗する。

いったん /tmp/cache/fontconfig にキャッシュを生成して、デプロイパッケージでは .fontconfig に配置する。

このため、次の内容で .fonts.conf を作成する。

fonts.conf
<fontconfig>
    <cachedir>/tmp/cache/fontconfig</cachedir>
</fontconfig>

フォントキャッシュを生成する Lambda 関数は次の通り。

fontcache.py
from __future__ import print_function
import subprocess

import sys
import os
import base64
import boto3
from os.path import join

logger = logging.getLogger()  
logger.setLevel(logging.INFO)  

BUCKET_NAME = 'バケット名'

def lambda_handler(event, context):
    os.environ['HOME'] = os.environ['LAMBDA_TASK_ROOT']
    try:
        os.makedirs('/tmp/cache/fontconfig')
    except OSError as e:
        (errno, strerror) = e
        print('OSError {}'.format(strerror))

    args = [ 'fc-cache', '-v', join(os.environ['HOME'], '.fonts') ]
    p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    returncode = p.returncode
    stdout_data, stderr_data = p.communicate()

    print('stdout_data:n' + stdout_data)
    print('stderr_data:n' + stderr_data)
    s3bucket = boto3.resource('s3').Bucket(BUCKET_NAME)
    tmp_fontconfig = '/tmp/cache/fontconfig'
    for cache in os.listdir(tmp_fontconfig):
        response = s3bucket.upload_file(join(tmp_fontconfig, cache), cache)
        with open(join(tmp_fontconfig, cache), mode='rb') as f:
            print("{}:n{}n".format(cache, base64.b64encode(f.read())))

デプロイパッケージの内容

% unzip -l fontcache.zip 
Archive:  fontcache.zip
  Length     Date   Time    Name
 --------    ----   ----    ----
     1116  10-16-17 15:34   fontcache.py
      105  10-12-17 20:54   .fonts.conf
        0  10-12-17 22:01   .fonts/
 18748872  10-12-17 17:04   .fonts/NotoSansCJK-Regular.ttc
 --------                   -------
 18750093                   4 files

この Lambda 関数を実行すると、S3 にキャッシュファイルがアップロードされる。

テスト

先ほど生成したキャッシュファイルを .fontconfig に配置する。

phantomjs のロードモジュールと rasterize.jsbin ディレクトリに配置する。

phantomjs でのスクリーンキャプチャーは、Screen Capture | PhantomJSを参考に、 rasterize.js 使用してテストプログラムを作成した。

phantomjs.py
from __future__ import print_function
import subprocess
import tempfile
import os
import boto3

def phantomjs(url, bucket, name):
    from os.path import join
    run_dir = os.environ['LAMBDA_TASK_ROOT']
    bin_dir = join(run_dir, 'bin')
    args = [ join(bin_dir, 'phantomjs'), join(bin_dir, 'rasterize.js'), url ]

    with tempfile.NamedTemporaryFile(suffix='.png') as f:
        args.append(f.name)
        print('{}n'.format(' '.join(args)))
        p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        returncode = p.returncode
        stdout_data, stderr_data = p.communicate()

        s3 = boto3.resource('s3')
        bucket = s3.Bucket(bucket)
        response = bucket.upload_file(f.name, name)

    print('returncode {}n'.format(returncode))
    print('stdout:: n{}n'.format(stdout_data.decode('utf-8')))
    print('stderr:: n{}n'.format(stderr_data.decode('utf-8')))
    print('stderr:: n{}n'.format(stderr_data.decode('utf-8')))
    print('response {}n'.format(response))

def lambda_handler(event, context):
    os.environ['HOME'] = os.environ['LAMBDA_TASK_ROOT']
    os.chdir(os.environ['LAMBDA_TASK_ROOT'])
    phantomjs('https://www.amazon.co.jp', 'バケット名', 'screenshot.png')

デプロイパッケージの内容

% unzip -l phantomjs.zip 
Archive:  phantomjs.zip
  Length     Date   Time    Name
 --------    ----   ----    ----
     1304  10-13-17 17:09   phantomjs.py
      105  10-12-17 20:54   .fonts.conf
        0  10-12-17 22:01   .fonts/
 18748872  10-12-17 17:04   .fonts/NotoSansCJK-Regular.ttc
        0  10-12-17 23:05   .fontconfig/
    12792  10-12-17 22:27   .fontconfig/996789b4ba9d471a5fd80c008c2b2acf-le64.cache-3
 67932064  01-25-16 10:01   bin/phantomjs
     2241  10-12-17 23:19   bin/rasterize.js
 --------                   -------
 86697378                   8 files

この Lambda 関数を実行して、うまく画面がキャプチャーされていればよい。

phantomjs の実行のために、Lambda が使用するメモリを192MB以上にしておかないと、メモリ不足で処理が途中で打ち切られる。

続きを読む

DynamoDBの予約語一覧を無理やり動的に取得してみた

DynamoDBの予約語を対処するために一覧が欲しくなったのですが、いちいちプログラム内で定義したくなかったので、AWSの予約語一覧ページから一覧を取得するサービスを作りました。

前提

  1. AWSが公開しているDynamoDB予約語一覧のWebページ( http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html )をスクレイピングして予約語一覧を抽出します。
  2. 実装はAWS Lambda (Python3)、Webサービスとして動作させるためにAPI Gatewayを利用します。

結果

https://github.com/kojiisd/dynamodb-reserved-words

DynamoDB予約語一覧ページの確認

今回は以下のページをParseしたいと思います。

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/ReservedWords.html

HTMLを見てみると、Parseしたい箇所はcodeタグで囲まれているようで、しかもこのページ内でcodeタグが出現するのは1度だけのようです。

スクリーンショット 2017-10-15 18.03.18.png

これであればすぐにパースできそうです。

HTMLの取得とParse

BeautifulSoupを利用すれば、簡単に実装ができます。BeautifulSoupのインストールは他のページでいくらでも紹介されているので、ここでは省略します。

import sys
import os


sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'lib'))
from bs4 import BeautifulSoup
import requests

URL = os.environ['TARGET_URL']

def run(event, context):

    response = requests.get(URL)
    soup = BeautifulSoup(response.content, "html.parser")

    result_tmp = soup.find("code")
    if result_tmp == None:
        return "No parsing target"

    result = result_tmp.string

    return result.split('n');

とりあえず申し訳程度にパースできない場合(AWSのページがcodeタグを使わなくなった時)を想定してハンドリングしています。

API Gatewayを定義してサービス化

コードが書けてしまえばAPI Gatewayを定義してサービス化するだけです。

設定は非常に単純なので割愛で。

スクリーンショット 2017-10-15 18.12.11_deco.png

アクセスしてみる

実際にアクセスしてみます。

$ curl -X GET https://xxxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod
["", "ABORT", "ABSOLUTE", "ACTION", "ADD", "AFTER", "AGENT", "AGGREGATE", "ALL", "ALLOCATE", "ALTER", "ANALYZE", "AND", "ANY", "ARCHIVE", "ARE", "ARRAY", "AS", "ASC", "ASCII", "ASENSITIVE", "ASSERTION", "ASYMMETRIC", "AT", "ATOMIC", "ATTACH", "ATTRIBUTE", "AUTH", "AUTHORIZATION", "AUTHORIZE", "AUTO", "AVG", "BACK", "BACKUP", "BASE", "BATCH", "BEFORE", "BEGIN", "BETWEEN", "BIGINT", "BINARY", "BIT", "BLOB", "BLOCK", "BOOLEAN", "BOTH", "BREADTH", "BUCKET", "BULK", "BY", "BYTE", "CALL", "CALLED", "CALLING", "CAPACITY", "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", "CHAR", "CHARACTER", "CHECK", "CLASS", "CLOB", "CLOSE", "CLUSTER", "CLUSTERED", "CLUSTERING", "CLUSTERS", "COALESCE", "COLLATE", "COLLATION", "COLLECTION", "COLUMN", "COLUMNS", "COMBINE", "COMMENT", "COMMIT", "COMPACT", "COMPILE", "COMPRESS", "CONDITION", "CONFLICT", "CONNECT", "CONNECTION", "CONSISTENCY", "CONSISTENT", "CONSTRAINT", "CONSTRAINTS", "CONSTRUCTOR", "CONSUMED", "CONTINUE", "CONVERT", "COPY", "CORRESPONDING", "COUNT", "COUNTER", "CREATE", "CROSS", "CUBE", "CURRENT", "CURSOR", "CYCLE", "DATA", "DATABASE", "DATE", "DATETIME", "DAY", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DEFINE", "DEFINED", "DEFINITION", "DELETE", "DELIMITED", "DEPTH", "DEREF", "DESC", "DESCRIBE", "DESCRIPTOR", "DETACH", "DETERMINISTIC", "DIAGNOSTICS", "DIRECTORIES", "DISABLE", "DISCONNECT", "DISTINCT", "DISTRIBUTE", "DO", "DOMAIN", "DOUBLE", "DROP", "DUMP", "DURATION", "DYNAMIC", "EACH", "ELEMENT", "ELSE", "ELSEIF", "EMPTY", "ENABLE", "END", "EQUAL", "EQUALS", "ERROR", "ESCAPE", "ESCAPED", "EVAL", "EVALUATE", "EXCEEDED", "EXCEPT", "EXCEPTION", "EXCEPTIONS", "EXCLUSIVE", "EXEC", "EXECUTE", "EXISTS", "EXIT", "EXPLAIN", "EXPLODE", "EXPORT", "EXPRESSION", "EXTENDED", "EXTERNAL", "EXTRACT", "FAIL", "FALSE", "FAMILY", "FETCH", "FIELDS", "FILE", "FILTER", "FILTERING", "FINAL", "FINISH", "FIRST", "FIXED", "FLATTERN", "FLOAT", "FOR", "FORCE", "FOREIGN", "FORMAT", "FORWARD", "FOUND", "FREE", "FROM", "FULL", "FUNCTION", "FUNCTIONS", "GENERAL", "GENERATE", "GET", "GLOB", "GLOBAL", "GO", "GOTO", "GRANT", "GREATER", "GROUP", "GROUPING", "HANDLER", "HASH", "HAVE", "HAVING", "HEAP", "HIDDEN", "HOLD", "HOUR", "IDENTIFIED", "IDENTITY", "IF", "IGNORE", "IMMEDIATE", "IMPORT", "IN", "INCLUDING", "INCLUSIVE", "INCREMENT", "INCREMENTAL", "INDEX", "INDEXED", "INDEXES", "INDICATOR", "INFINITE", "INITIALLY", "INLINE", "INNER", "INNTER", "INOUT", "INPUT", "INSENSITIVE", "INSERT", "INSTEAD", "INT", "INTEGER", "INTERSECT", "INTERVAL", "INTO", "INVALIDATE", "IS", "ISOLATION", "ITEM", "ITEMS", "ITERATE", "JOIN", "KEY", "KEYS", "LAG", "LANGUAGE", "LARGE", "LAST", "LATERAL", "LEAD", "LEADING", "LEAVE", "LEFT", "LENGTH", "LESS", "LEVEL", "LIKE", "LIMIT", "LIMITED", "LINES", "LIST", "LOAD", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LOCATION", "LOCATOR", "LOCK", "LOCKS", "LOG", "LOGED", "LONG", "LOOP", "LOWER", "MAP", "MATCH", "MATERIALIZED", "MAX", "MAXLEN", "MEMBER", "MERGE", "METHOD", "METRICS", "MIN", "MINUS", "MINUTE", "MISSING", "MOD", "MODE", "MODIFIES", "MODIFY", "MODULE", "MONTH", "MULTI", "MULTISET", "NAME", "NAMES", "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NEXT", "NO", "NONE", "NOT", "NULL", "NULLIF", "NUMBER", "NUMERIC", "OBJECT", "OF", "OFFLINE", "OFFSET", "OLD", "ON", "ONLINE", "ONLY", "OPAQUE", "OPEN", "OPERATOR", "OPTION", "OR", "ORDER", "ORDINALITY", "OTHER", "OTHERS", "OUT", "OUTER", "OUTPUT", "OVER", "OVERLAPS", "OVERRIDE", "OWNER", "PAD", "PARALLEL", "PARAMETER", "PARAMETERS", "PARTIAL", "PARTITION", "PARTITIONED", "PARTITIONS", "PATH", "PERCENT", "PERCENTILE", "PERMISSION", "PERMISSIONS", "PIPE", "PIPELINED", "PLAN", "POOL", "POSITION", "PRECISION", "PREPARE", "PRESERVE", "PRIMARY", "PRIOR", "PRIVATE", "PRIVILEGES", "PROCEDURE", "PROCESSED", "PROJECT", "PROJECTION", "PROPERTY", "PROVISIONING", "PUBLIC", "PUT", "QUERY", "QUIT", "QUORUM", "RAISE", "RANDOM", "RANGE", "RANK", "RAW", "READ", "READS", "REAL", "REBUILD", "RECORD", "RECURSIVE", "REDUCE", "REF", "REFERENCE", "REFERENCES", "REFERENCING", "REGEXP", "REGION", "REINDEX", "RELATIVE", "RELEASE", "REMAINDER", "RENAME", "REPEAT", "REPLACE", "REQUEST", "RESET", "RESIGNAL", "RESOURCE", "RESPONSE", "RESTORE", "RESTRICT", "RESULT", "RETURN", "RETURNING", "RETURNS", "REVERSE", "REVOKE", "RIGHT", "ROLE", "ROLES", "ROLLBACK", "ROLLUP", "ROUTINE", "ROW", "ROWS", "RULE", "RULES", "SAMPLE", "SATISFIES", "SAVE", "SAVEPOINT", "SCAN", "SCHEMA", "SCOPE", "SCROLL", "SEARCH", "SECOND", "SECTION", "SEGMENT", "SEGMENTS", "SELECT", "SELF", "SEMI", "SENSITIVE", "SEPARATE", "SEQUENCE", "SERIALIZABLE", "SESSION", "SET", "SETS", "SHARD", "SHARE", "SHARED", "SHORT", "SHOW", "SIGNAL", "SIMILAR", "SIZE", "SKEWED", "SMALLINT", "SNAPSHOT", "SOME", "SOURCE", "SPACE", "SPACES", "SPARSE", "SPECIFIC", "SPECIFICTYPE", "SPLIT", "SQL", "SQLCODE", "SQLERROR", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "START", "STATE", "STATIC", "STATUS", "STORAGE", "STORE", "STORED", "STREAM", "STRING", "STRUCT", "STYLE", "SUB", "SUBMULTISET", "SUBPARTITION", "SUBSTRING", "SUBTYPE", "SUM", "SUPER", "SYMMETRIC", "SYNONYM", "SYSTEM", "TABLE", "TABLESAMPLE", "TEMP", "TEMPORARY", "TERMINATED", "TEXT", "THAN", "THEN", "THROUGHPUT", "TIME", "TIMESTAMP", "TIMEZONE", "TINYINT", "TO", "TOKEN", "TOTAL", "TOUCH", "TRAILING", "TRANSACTION", "TRANSFORM", "TRANSLATE", "TRANSLATION", "TREAT", "TRIGGER", "TRIM", "TRUE", "TRUNCATE", "TTL", "TUPLE", "TYPE", "UNDER", "UNDO", "UNION", "UNIQUE", "UNIT", "UNKNOWN", "UNLOGGED", "UNNEST", "UNPROCESSED", "UNSIGNED", "UNTIL", "UPDATE", "UPPER", "URL", "USAGE", "USE", "USER", "USERS", "USING", "UUID", "VACUUM", "VALUE", "VALUED", "VALUES", "VARCHAR", "VARIABLE", "VARIANCE", "VARINT", "VARYING", "VIEW", "VIEWS", "VIRTUAL", "VOID", "WAIT", "WHEN", "WHENEVER", "WHERE", "WHILE", "WINDOW", "WITH", "WITHIN", "WITHOUT", "WORK", "WRAPPED", "WRITE", "YEAR", "ZONE "]

うまくできました。

まとめ

思いつきで作成してみましたが、AWSのページが閉鎖されたりHTML構造が変更されない限り、仕様変更などは気にせずに使えそうです。

あとBeautifulSoup初めて使いましたが、かなり便利。

続きを読む

[EC2のスケジュール 停止] 会社から帰る時にスケジュール実行したlambdaからEC2(staging)をとめる

stgingで使っているEC2を停止するのを忘れてしまうということがよくありました。(主な犯人は僕です)手動で毎回止めるのもだるいし、人間は忘れっぽい生き物ですのでEC2のスケジュール停止をlambdaで実現しました。

設定の流れ

  1. EC2を停止するlambda関数を作成

    • EC2をlambdaから停止・起動するためのIAMのロールを作成
    • node.js で関数を実装
    • テスト
  2. CloudWatch Eventsでスケジュール設定

EC2を停止するlambda関数を作成

「一から作成」で新規関数を作成します。

https://gyazo.com/3a46795ca6ed2019674451fa7c40c265

lambdaからEC2の起動・停止をするにあたり、IAMのロールを作成します。

https://gyazo.com/890e460ddafeeb2aa003495293c8fe17

ポリシーを編集します。

https://gyazo.com/554b539050c1208cf8192ac161091941

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "ec2:StartInstances",
                "ec2:StopInstances"
            ],
            "Resource": [
                "arn:aws:logs:*:*:*",
                "arn:aws:ec2:*"
            ]
        }
    ]
}

EC2を停止するための関数コードを入力します。

const INSTANCE_ID = '*****';

var AWS = require('aws-sdk'); 
AWS.config.region = '******'; // 東京リージョン: ap-northeast-1

function ec2Stop(cb){
  var ec2 = new AWS.EC2();
  var params = {
    InstanceIds: [
      INSTANCE_ID
    ]
  };

  ec2.stopInstances(params, function(err, data) {
    if (!!err) {
      console.log(err, err.stack);
    } else {
      console.log(data);
      cb();
    }
  });
}

exports.handler = function(event, context) {
  console.log('start');
  ec2Stop(function() {
    context.done(null, 'Stoped Instance');
  });
};

この段階で「テスト」を実行して以下のようになれば成功です。

https://gyazo.com/88457ba6a983ca0785092d8ab60056c9

CloudWatch Eventsでスケジュール設定

トリガータブで新規のトリガーを追加します。

https://gyazo.com/dc62eb7532e618d4a67dd29558b73cbd

こんな感じで18時に自動で止まるようにします。

https://gyazo.com/ab5e6d877d9775a50b6af2cba6e68bb1

cronの設定はUTCで時間を設定する必要があるので cron(0 9 * * ? *) です。
平日だけなどのパターンは以下に記載されているので参考にしてください。

http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/tutorial-scheduled-events-schedule-expressions.html

まとめ

これでヒューマンエラー・めんどくさい作業なしでEC2が止まるようになりました。

参考にしたLambdaのScheduleイベントでEC2を自動起動&自動停止してみたのパクリになってしまいましたが、AWSのUIが変わっていたりしていたので、新しいUIのキャプチャでも貼っておくかということで手順をまとめてみました。

参考

LambdaのScheduleイベントでEC2を自動起動&自動停止してみた

続きを読む