AWS Lambda と Pythonを使ってメタデータをAmazon Elasticsearch Serviceにインデクシング

AWS Solutions Architect ブログ: 【AWS Database Blog】AWS Lambda と Pythonを使ってメタデータをAmazon Elasticsearch Serviceにインデクシング. 続きを読む

AWS Lambda(Python)によるサーバー簡易監視と、Slackでの結果通知

実現したかったこと

  • 運用中サーバーの簡易監視として、定期的にHTTPアクセスを行い、結果を確認したい。
  • サーバーレスで実行したい。
    • 対象サーバーにアクセスするIPを制限しているため、監視元は固定IPの必要がある。
  • 完全自動化し、監視結果だけが通知されて欲しい。

最終的に実現したこと

  • 全体イメージ
    全体イメージ

  • S3に監視先サーバーデータを配置する。

    • データの形式は以下の通り。

      servers.json
      {
          "servers": [
              {"name": "Google", "url": "http://www.google.co.jp"},
              {"name": "Yahoo", "url": "http://www.yahoo.co.jp"}
          ]
      }
      
  • AWS Lambda(Python)から上記のデータを読み込み、各サーバーにHTTPアクセスを行う。

  • 監視結果をSlackに通知する。

  • 上記を、CloudWatch Events - Scheduleで定期実行する。

設定詳細

VPC設定

IAM設定

  • Lambda作成時に、roleのtemplateとして、Simple Microservice permissionsを選択。
  • S3にアクセスするため、AmazonS3ReadOnlyAccessのポリシーをアタッチ。
  • LambdaVPC上で実行するため、AWSLambdaVPCAccessExecutionRoleのポリシーをアタッチ。

CloudWatch Events – Schedule設定

Slack

AWS Lambda(Python)

  • エラーハンドリング周りは改善の余地あり。
lambda_function.py
import json
import requests
import boto3

BUCKET_NAME = 'xxxxxxxxxx'
OBJECT_NAME = 'xxxxxxxxxx/servers.json'
SLACK_POST_URL = 'https://hooks.slack.com/services/xxxxxxxxxx/xxxxxxxxxx/xxxxxxxxxxxxxxxxxxxx'

def lambda_handler(event, context):
    json_data = __getServers()
    __check_server(json_data)

def __getServers():
   s3 = boto3.resource('s3')
   obj = s3.Object(BUCKET_NAME, OBJECT_NAME)
   response = obj.get()
   body = response['Body'].read()
   return body.decode('utf-8')

def __check_server(json_data):
    data = json.loads(json_data)
    servers = data['servers']

    has_error = False

    for server in servers:
        name = server['name']
        url = server['url']
        print("Check: " + name)

        try:
            r = requests.get(url)
            if r.status_code != 200:
                __send_error_message(name, url)
                has_error = True
        except requests.exceptions.RequestException as e:
            __send_request_error_message(name, url)
            has_error = True

    if has_error == False:
        __send_success_message()

def __send_error_message(name, url):
    payload = {
        "text": name + 'n' + url + 'n' + '*ERROR!*',
        "icon_emoji": ":x:"
    }
    __send_message(payload)

def __send_request_error_message(name, url):
    payload = {
        "text": name + 'n' + url + 'n' + '*Request Error!*',
        "icon_emoji": ":warning:"
    }
    __send_message(payload)

def __send_success_message():
    payload = {
        "text": "All Servers OK!",
        "icon_emoji": ":o:"
    }
    __send_message(payload)

def __send_message(payload):
    try:
        return requests.post(SLACK_POST_URL, json=payload)
    except requests.exceptions.RequestException as e:
        return None


AWS Lambda(Python)設定時の注意点

  • ライブラリはソースコードと同じディレクトリに配置する。
pip install requests -t .
  • ソースコードとライブラリをまとめて、zipにして配置する。
zip -r lambda_function.zip *

雑感

  • AWS LambdaにVPCを設定できるようになったことで、データ送信元を固定IPアドレスにしてLambdaを実行できるようになったため、使いどころが広がった。
  • 手軽な通知先として、Slackとの連携が簡単で便利すぎ。
  • servers.jsonの中身をbodyに詰めた、HTTP POST通信をトリガーに、API Gateway経由で実行させる方法も試したが、最終的に定期実行がラクな現在の形とした。

続きを読む

Lambda-backed Custom Resourceのcfn-responseモジュールを利用する上での注意点

はじめに こんにちは、中山です。 軽くハマったのでエントリにまとめてご紹介したいと思います。ドキュメントちゃんと読めという話ではありますが。。。 CloudFormation(以下CFn)テンプレートを作成している際にL […] 続きを読む

[AWS]KMSで暗号化・復号化するサンプル(node.js版)

この記事の内容

AWS KMSを試してみたかった
Lambdaから文字列をKMSに投げて暗号化し、暗号化文字列をもう1度投げて復号化できるかをやってみた

前提条件

  • ManagementコンソールのIAM内、鍵管理からKMSに鍵が登録されていること
  • KSM登録時にKMSを使用できるロールにLambdaを実行するロールを指定していること

つまり、KMSに鍵が登録されていて、その鍵を操作する権限を持ったロールをLambdaにアタッチ出来ていること。
マネジメントコンソールに従ってやっていればできます。

実行環境

  • AWS Lambda
  • Node.js 4.3

実装

コンソール直書きで大丈夫です

index.js

'use strict'
const AWS = require('aws-sdk');
const kms = new AWS.KMS({apiVersion: '2014-11-01'});
const id = <登録した鍵のKyeID>;

exports.handler = (event, context, callback) => {
    let base64txt = new Buffer('hogehogeTxt').toString();
    let params = {
        KeyId: id, 
        Plaintext: base64txt
    };
    kms.encrypt(params, (err, data) => {
        if(err) console.log(err);
        console.log(data);

        let encript = data.CiphertextBlob;

        let param = {
            CiphertextBlob: encript
        };
        kms.decrypt(param, (err, data) => {
           if(err) console.log(err);
           let txt = data.Plaintext;
           console.log(txt.toString('utf-8', 0, txt.length))
           callback(null, 'success');
        });
    });
};

実行すると暗号化後復号化されたhogehogeTxtが返ってきます。

結果

以下のデータサンプルはドキュメントから引用
http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/KMS.html#encrypt-property

暗号化データサンプル

{
    CiphertextBlob: <Binary String>, 
    KeyId: "arn:aws:kms:us-west-2:111122223333:key1234abcd-12ab-34cd-56ef-1234567890ab"
}

復号化データサンプル

{
    KeyId: "arn:aws:kms:us-west-2:111122223333:key1234abcd-12ab-34cd-56ef-1234567890ab",     
    Plaintext: <Binary String>
}   

まとめ

KMSはじめて触ってみたけどいいね

続きを読む

S3のファイルの中身をSQLで検索できるAWS Athenaを(遅まきながら)触ってみた

AWS Athenaは表題の通り、S3にあるファイルをSQLで検索できるAWSのサービスで、
内部にはPrestoが使用されているそうです。

Athenaの利用

Athenaは東京リージョン(ap-northeeast-1)に対応していないので、東京リージョンからAthenaを選択すると、

1.png
[undefined]()

と出ますが、対応しているリージョンを選択すればいいので使用することに問題はありません。
Atenaを利用するリージョンと異なるリージョンにあるS3バケットも、検索の対象にすることができます。

注意:
AthenaのリージョンとS3のリージョンが異なる場合、リージョン間通信料金がかかります。

詳しくは、S3の料金についてを参照ください。

テーブルの作成

今回は、米国東部リージョンを利用しました。
Athenaが利用可能だと以下のような画面が表示されます。

2.png

Query実行欄があるので直接Queryを書いてテーブルを作ることもできますが、
Hiveの記法で書く必要があり、Hiveを知らないので、
CatalogManagerに従ってテーブルを作成します。

CatalogManagerを押下して遷移する以下の画面で、Add tableを押下します。

3.png

Name & Location

この画面では、作成するテーブルを配置するデータベースやテーブル名、
およびテーブルの元になるS3バケットのパスを指定します。

今回、テーブルを作成した時のバケット構成は以下の通りです。
(CloudWatch Logs + AWS Lambda で日付ごとインスタンス毎のログが.gzファイルに入っています。)

s3://xxxxxxxxxx-var-log-mssages/
 ┣━2016-12-29
 ┃ ┣━aws-logs-write-test
 ┃ ┗━5ce6a99c-bc8a-4af7-b818-061ed575183f
 ┃   ┣━i-03e629e997aa95f18
 ┃   ┃ ┗━000000.gz
 ┃   ┗━i-074549fdee10c46bc
 ┃     ┗━000000.gz
 ┗━2016-12-30
   ┣━aws-logs-write-test
   ┗━6c868fe1-d6ef-4d03-a6e0-4b2c89d1b44b
     ┣━i-03e629e997aa95f18
     ┃ ┗━000000.gz
     ┗━i-074549fdee10c46bc
       ┗━000000.gz

S3バケットで指定したパス以下にあるファイルがQuery対象のファイルになるので、
s3://xxxxxxxxxx-var-log-mssages/ 以下にあるファイルがQuery対象です。

4.png

Data Format

ここでは、テキストをテーブルの列として扱うために、
どのようなフォーマットで列を区切るか指定します。
CSVやTSV、正規表現等が使えます。

今回Athenaで検索対象とするテキストは以下のように記載されているため、

2016-12-30T13:46:43.279Z Dec 30 22:46:42 ip-172-16-2-115 dhclient[2069]: bound to 172.16.2.115 -- renewal in 1663 seconds.
2016-12-30T13:46:43.279Z Dec 30 22:46:42 ip-172-16-2-115 ec2net: [get_meta] Trying to get http://169.254.169.254/latest/meta-data/network/interfaces/macs/06:65:2d:4e:6d:cf/local-ipv4s
2016-12-30T13:46:43.280Z Dec 30 22:46:42 ip-172-16-2-115 ec2net: [rewrite_aliases] Rewriting aliases of eth0

先頭の時刻部分(CloudWatch Logsの時刻)とDec 30以降の部分(ログファイルの本体)との2列になるよう、
以下の正規表現で列の区切りを設定しました。

([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}Z) (.*)

5.png

Columns

Data Formatで設定した列の区切りに対して、列名と型を設定します。

今回の列の区切りでは、1列目と2列目を文字列とするため、
以下のように設定しました。

6.png

Partitions

テーブルのパーティショニングを行うかどうかの設定ですが、
今回はお試しなのでパーティショニングは設定しません。
(Athenaはスキャン量に応じて課金されるので、
 実際に運用する場合はスキャン量を減らすためにパーティショニングをしたほうがよいそうです。)

7.png

テーブル作成

PartitionsCreate tableを押下すると、
Query画面に戻され、先ほどまで設定した内容に基づいてSQLを作成・発行します。

8.png

検索

テーブルができましたので、
log_bodyにerrorの文字列が存在するものを検索します。

SELECT * FROM messages_log where log_body like '%error%' order by log_time desc;

9.png

従来のSQLの書き方で、ログファイルにerrorの文字が存在するものを抜き出すことができました。

最後に

使った感想ですが、やはりログファイルをSQLで検索できるのは、
エラー頻度やエラー解析を行うには便利なように感じました。
ただ、クエリでスキャンしたデータ量に対して課金されるので、
エラー解析のためにガンガンSQLを実行するのは難しいかと思います。

続きを読む

[Java] EC2を朝起動して夜間と土日祝日は自動的に停止状態にするLambdaファンクション

はじめに 開発環境のサーバを業務時間内だけ起動状態してくれるLambdaファンクションです。Node.jsで書かれたものは以前このブログで紹介しているのですが、私はNode.jsが苦手なのでJavaで書いてみました。つい […] 続きを読む

【AWS Lambda】Amazon Linux の Docker イメージを使ってデプロイパッケージを作成する

AWS Lambda の実行環境に無いライブラリを利用する場合には、ローカル環境でライブラリのソース群をダウンロードしておいてそれをデプロイパッケージに含めて Lambda にアップロードする必要があります。

Python の例だとデプロイパッケージを作る際に例えば下記のような手順を踏みます。

$ virtualenv -p python2.7 venv
$ source venv/bin/activate
$ pip install -r requirements.txt
$ zip deploy_package.zip lambda_function.py # 実行スクリプトを zip にする
$ cd venv/lib/python2.7/site-packages
$ zip -r ../../../../deploy_package.zip * # pip でインストールしたライブラリを zip に追加

利用したいライブラリが純粋な Python コードであればこのパッケージは問題無く Lambda 上でも動作するのですが、OS の機能に依存しているようなライブラリの場合、ローカル環境でビルド・インストールされたものを Lambda 上にアップロードしても動きません。
上記の例で言うと pip install は Lambda の実行環境と同じ環境で行う必要があります。

失敗例

pycrypto というライブラリを使う Lambda Function を作って実行します。

requirements.txt
pycrypto==2.6.1
lambda_function.py
from Crypto.PublicKey import RSA


def lambda_handler(event, context):
    return {"messagge": "success"}

上記のファイルを用意してローカルの OS X 環境でデプロイパッケージを作成します。

$ virtualenv -p python2.7 venv
$ source venv/bin/activate
$ pip install -r requirements.txt
$ zip deploy_package.zip lambda_function.py
$ cd venv/lib/python2.7/site-packages
$ zip -r ../../../../deploy_package.zip *

作成した deploy_package.zip を AWS Lambda にアップロードして実行すると次のようなエラーが出ます。
Unable to import module 'lambda_function'

Unable to import module 'lambda_function'

エラーログ全文を見ると Unable to import module 'lambda_function': /var/task/Crypto/Util/_counter.so: invalid ELF header とあります。pycrypto が参照するファイルのヘッダ情報が不正であるようです。原因は pycrypto をインストールした環境が Lambda の実行環境と異なるところにあります。

対策

Amazon Linux の Docker イメージを用いてライブラリのインストールを行うことでこの問題を回避することができます。

先述のファイルと同じ階層にこのような Dockerfile を用意して

Dockerfile
FROM amazonlinux:latest

RUN yum -y update && yum -y install gcc python27-devel
RUN cd /tmp && 
    curl https://bootstrap.pypa.io/get-pip.py | python2.7 - && 
    pip install virtualenv
WORKDIR /app
CMD virtualenv -p python2.7 venv-for-deployment && 
    source venv-for-deployment/bin/activate && 
    pip install -r requirements.txt

このようにコマンドを実行すると venv-for-deployment という名前で、Amazon Linux でビルドされた Python ライブラリコードが作成されます。

$ docker build . -t packager
$ docker run --rm -it -v $(PWD):/app packager

その後は下記のようにデプロイパッケージの zip を作成して AWS Lambda にアップロードします。

$ zip deploy_package.zip lambda_function.py
$ cd venv-for-deployment/lib/python2.7/site-packages
$ zip -r ../../../../deploy_package.zip * .* # dotfile が含まれる場合は ".*" も

実行するとライブラリが import 出来て無事 "success" が表示されます。

success

自動化

ちょっと叩くコマンド量が多いのでこのような Makefile を作っておくと make だけで zip が生成されて便利です。

Makefile
package:
    docker build . -t packager
    docker run --rm -it -v $(PWD):/app packager
    zip deploy_package.zip lambda_function.py
    cd venv-for-deployment/lib/python2.7/site-packages && zip -r ../../../../deploy_package.zip * .*
    echo "Completed. Please upload deploy_package.zip to AWS Lambda"

サンプル

今回用いた Lambda Function のサンプルはこちらのリポジトリに置いています。
https://github.com/morishin/python-lambda-function-test

所感

AWS Lambda 便利なのですがちょっと凝ったことをしようとすると泣きそうになりますね。

続きを読む