AWS Lambda(Python)を手っ取り早く高速化する方法【cloudpack 大阪 BLOG】

aws lambdaでは、CPUの使用時間に対し100ミリ秒単位で課金されるため、処理を高速化できるとその分料金も下がります。今回は簡単にLambda(Python)を高速化する方法を紹介します。

方法

処理系をJITコンパイル機能を持つPyPyに変更します。
これだけです。特にソースを見なおすとかではないので手軽に試せます。

PyPyとは
PyPy(パイパイ)は、プログラミング言語Pythonの実装の1つであり、Pythonで記述されたPythonの処理系であることが特徴の1つである(セルフホスティング)。PyPyは、実行速度と効率、およびオリジナルのPython実装であるCPythonとの互換性に重点を置いている。

PyPy - Wikipedia

環境構成

OS:Amazon Linux
PyPy:5.1.1 x86_64:

PyPyのインストール

公式サイトではredhat系のバイナリが提供されていませんので、以下ページで提供されているportable-pypyをダウンロードします。今回は、「PyPy 5.1.1 x86_64」をダウンロードしました。
GitHub - squeaky-pl/portable-pypy: Portable 32 and 64 bit x86 PyPy binaries for many Linux distributions.

ファイル構成

ダウンロードしたpypy-5.1.1-linux_x86_64-portable.tar.bz2を解凍してpypyにリネームしておきます。

ファイル構成は以下のとおりです

├── lambda_function.py    ・・・lambdaから呼び出されてrun.shを実行する
├── run.sh       ・・・main_functionをpypyで実行する 
├── main_function.py  ・・・実際の処理が書かれたpython
└── pypy        ・・・portable-pypy
lambda_function.py

run.shを実行するだけのlambda関数です

# coding: utf-8
import commands

def _(cmd):
    return commands.getoutput(cmd)

def lambda_handler(event, context):
    main()

def main():
    print _('sh ./run.sh')
run.sh

pypyで対象のpythonを実行します。

#!/bin/sh

echo exec pypy
./pypy/bin/pypy main_function.py
main_function.py

実際に実行したい処理をここに記述します。
今回はパフォーマンス測定用に大量のリストを追加する処理としました。

# coding: utf-8

def main():
  rangelist = range(1,100000)

  for var in range(0, 100):
     func(rangelist)

  print('main func finish')

def func(rangelist):
    list = []
    for var in rangelist:
        list.append(var)

if __name__=='__main__':
    main()

ファイルを全て準備できれば、ディレクトリごとzipで固めてlambdaへアップします。pypy-portableの容量が大きいのでs3経由でないとアップできませんでした。

パフォーマンス結果

以下測定結果です。上記のmain_function.pyをデフォルトで実行した時とPyPyで実行した時とで、それぞれ5回測定して平均を取りました。PyPyの場合初回が遅くなるかと思いましたがあまり変わりませんでした。

PyPy デフォルト
1 5085.81 13523.69
2 5655.01 13497.07
3 4984.38 15315.06
4 4931.47 14630.05
5 4981.22 13615.74
平均 5127.57 14116.32

単位:ms

まとめ

今回のケースの場合、処理時間が約1/3になったのでかなりの高速化の効果がありました。
lambdaで重い処理を実行している場合など、PyPyでパフォーマンス改善を試しててみるのもいいかもしれません。
ただし、全てのケースで早くなるわけではなく処理が軽いlambdaで試した場合は、PyPyで実行した時のほうが逆に遅かったです。また、デフォルトのCpythonとは互換性がない部分もありますので、実際に切り替える場合はしっかりとテストが必要になると思います。今後、パフォーマンス改善が必要になった場合に、1つの手法として試してみたいと思います。

pythonのimaplibでメールのAND/OR検索【cloudpack 大阪 BLOG】

pythonでimaplibを使ってGmailを取得しようとして対象メールの検索をしてみたのですが、AND検索やOR検索でうまく検索できずに少しハマりましたので備忘録として残しておきます。

事前設定

まずは対象のGmailアカウントで以下を参考にアプリパスワードを取得します。通常のパスワードではログインできません。また、imapを有効にしておく必要があります。
support.google.com

実行コード

今回実行するコードは以下

import imaplib,email,email.Header

GMAIL_USER=YOUR_GMAIL_ACCOUNT
GMAIL_APP_PASSWORD=YOUR_GMAIL_APP_PASSWORD  #取得したアプリパスワード

def decode(src):

    result = ''
    decodefrag = email.Header.decode_header(src)
    
    for frag, enc in decodefrag:↲
        if enc:
            result += unicode(frag, enc)
        else:
            result += unicode(frag)
    
    return result

if __name__ == "__main__":

    gmail = imaplib.IMAP4_SSL(host=HOST)
    gmail.login(GMAIL_USER, GMAIL_APP_PASSWORD)
    gmail.select(LABEL)

    search_option = 'ここに検索条件をセット'
    typ, data = gmail.search(None, search_option)

    for num in data[0].split():
        typ, data = gmail.fetch(num, '(RFC822)')
        
        msg = email.message_from_string(data[0][1])
        subject = decode(msg.get('Subject'))

検索条件

件名で検索
search_option = '(SUBJECT "testmail01")'
AND 検索

件名と日付のFROM-TOでAND検索

search_option = '(SUBJECT "testmail01" SENTSINCE "09-Apr-2016" SENTBEFORE "10-Apr-2016")'
OR 検索

件名とFromでOR検索

search_option = '(OR (SUBJECT "testmail01") (FROM "test@gmail.com"))'
AND・OR 検索

件名でOR検索し日付のFROM-TOでAND検索

search_option = '((OR (SUBJECT "testmail01") (FROM "test@gmail.com")) SENTSINCE "01-May-2016" SENTBEFORE "03-May-2016")'

検索条件についてはRFCを読みましたが、実際の表記形式がよく分からなかったので試行錯誤しました。また、検索方法には括弧を付与する方法と付与しない方法がありましたが、付与しない方法ではうまく検証できませんでした。

注意するポイント

  • 外側も括弧がないとエラーになるので括弧で囲ってください。
  • スペースの有無も重要。半角1文字分のスペースが必要でした。2文字以上空けるとエラーになるます。
  • 検索日付はUTCで(Gmailの場合)

NewRelicでEC2をモニタリング【cloudpack 大阪 BLOG】

NewRelicでEC2にWordPressをセットアップしてモニタリングしてみました。リソース状況が視覚的によく分かるので、パフォーマンス分析に使いやすいです。また、単にCPU使用率だけでなくプロセスごとのCPU使用率・メモリの推移やアプリケーションのレスポンスタイムの内訳などもグラフで表示されておりシステムのボトルネックを調査するのにお勧めです。

環境

OS:Amazon Linux
WebServer : Apache2.4
DB:MySQL5.5
WordPress:4.5

導入方法

初期設定

導入方法についてそれぞれ以下のページを参照してください。
qiita.com

AWSユーザーは以下ページからNew Relicのユーザー登録をすることでStandard版が無料で使えます。
New Relic AWS | AWS Performance Monitoring | New Relic | New Relic Partner

モニタリング画面

以下にモニタリング画面を紹介します。

サーバリソース

CPU使用率やメモリなどの推移と内訳
f:id:cloudfish:20160501000357p:plain

PHPアプリケーション

PHPMySQLそれぞれのレスポンスタイム
f:id:cloudfish:20160501000525p:plain
アプリからのqueryの実行状況
f:id:cloudfish:20160501144335p:plain

MySQLプラグイン

コネクション数の推移やRead、Writeの割合。取得メトリクスを増やせばもっと色々表示できます。
f:id:cloudfish:20160501000440p:plain

カスタムダッシュボード

上記で必要なメトリクスを集めてダッシュボードのカスタマイズも可能です。
f:id:cloudfish:20160501000543p:plain

まとめ

各種のリソースの細かいところまで単に数字だけでなく綺麗にビジュアル化されているのですごく把握しやすくなっています。
カテゴリごとに分けられたリソース状況もカスタムダッシュボードを使うことで必要なメトリクスだけを設定して確認できます。また、プラグインも豊富に揃えられており様々なミドルウェアのメトリクスの取得が可能です。
特にAWSユーザーは無料で使える(データの保存期間に制限有り)ので是非使ってみて欲しいと思います。

EC2の鍵を入れ替える手順【cloudpack 大阪 BLOG】

別のアカウントからAMIを共有して起動した際に、OSによっては公開鍵をうまく変更できていないことがあるので、公開鍵を入れ替える方法についての備忘録

手順概要

  • EC2からrootボリュームをデタッチ
  • 別のEC2にrootボリュームをアタッチ
  • rootボリュームをマウント
  • 公開鍵の修正
  • rootボリュームをアンマウント
  • rootボリュームをデタッチ
  • 元のEC2にrootボリュームをアタッチ

手順詳細

① EC2からrootボリュームをデタッチ

まずは、対象のEC2からrootボリュームをデタッチしますが、再度アタッチする際にデバイス名が必要となるのでデバイス名を控えておきます。

デバイス名を確認
f:id:cloudfish:20160410170203p:plain

対象ボリュームをデタッチする
f:id:cloudfish:20160410170208p:plain

② 他のEC2に対象のボリュームをアタッチする

f:id:cloudfish:20160410170213p:plain

③ rootボリュームをマウント

別のEC2(起動していなかったら起動する)にログインし下記コマンドを実行する

ボリュームの確認

# lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvdf    202:80   0  50G  0 disk
└─xvdf1 202:81   0  50G  0 part       ←対象ボリュームが認識されている
xvda1   202:1    0  50G  0 disk /

マウント用のディレクトリを作成する

# mkdir -p /mnt/ebs/0

マウントする

# mount /dev/xvdf1 /mnt/ebs/0

③ 対象ユーザーの公開鍵(authorized_key)を修正

ログインするための公開鍵をセットする

# vim /mnt/ebs/0/home/target_user/.ssh/authorized_keys

必要に応じてsshd_configを修正

# vim /mnt/ebs/0/etc/ssh/sshd_config

③ 対象ボリュームをアンマウント

# umount /mnt/ebs/0/

④ 対象ボリュームをデタッチ

手順①と同様にデタッチする

⑤ 元のEC2にアタッチ

元のインスタンスを選択し、①で控えておいたデバイス名をセットする
f:id:cloudfish:20160410175007p:plain

⑥ 起動して接続確認

元のインスタンスを起動して、対象の秘密鍵でログイン可能かを確認する

AWS Lambdaの簡易コマンド(lambda-controller)を作ってみた【cloudpack 大阪 BLOG】

やりたかったこと

外部ライブラリを使ってlambdaを開発しているとローカル環境で開発してアップロードしてAWSコンソールから実行という手間がすごく面倒だったので、開発時の簡単な実行確認については、ローカル環境のコマンドラインで全て完結できないかと思っていました。
AWSへのアップロードについては、lambda-uploaderを使っており、後はlambdaの実行がコマンドラインから簡単にできれば何とかなると思っていました。CLIは手軽に実行するには少し面倒なのと結果もjson形式となり見づらかったので、簡易実行できるコマンドを作ってみました。
ただし、まだまだ出来ることは限られているのと不具合もあると思うので今後改善予定です。

何ができるのか?

コマンドラインで以下の実行が可能です。

  • lambda関数の実行&ログ表示
  • lambda関数のリスト表示
  • lambda関数の詳細情報表示

インストール

pip install lambda-controller

使い方

前提としてaccesskey,secretkeyのcredentialへの設定が必要になります。

lambda関数の実行

lambda-controller --invoke(or -i) FunctionName
f:id:cloudfish:20160324091140p:plain

lambda関数のリスト表示

lambda-controller --detail(or -d) FunctionName
f:id:cloudfish:20160324090654p:plain

lambda関数の詳細情報表示

lambda-controller --list(or -l)
f:id:cloudfish:20160324091119p:plain

profileを指定して実行する場合

lambda-controller --profile=yourprofile --invoke(or --list or --detail)
f:id:cloudfish:20160324091759p:plain

Route53にドメインをコマンドラインで登録する【cloudpack 大阪 BLOG】

Route53にドメインを大量に登録する必要があったので、cliでのやり方を調べてみるとインプットにjsonファイルが必要なことがわかりました。ちょっと面倒だったので別の方法を探してみるとcli53というツールを見つけました。使い勝手はかなりいい感じだったので、インストール方法と使い方を紹介したいと思います。

インストール方法

ダウンロード

以下のリンクから自分の環境に応じて、パッケージをダウンロードしてください。
今回はMac版(cli53-mac-amd64)をダウンロードしました。
Release 0.7.0 · barnybug/cli53 · GitHub

インストール方

リネームして実行権を付与します。

# sudo mv cli53-mac-amd64 /usr/local/bin/cli53
# sudo chmod +x /usr/local/bin/cli53

使い方

事前に~/.aws/credentialsにシークレットキーとアクセスキーを設定しておいてください。
もしくは実行時に以下のように変数にセットしてください

aws_access_key_id = AKID1234567890
aws_secret_access_key = MY-SECRET-KEY
hosted zoneの作成
cli53 create example.com 
Aレコード

Zone Apex

cli53 rrcreate example.com '@ 300 A 192.168.1.2'

サブドメインがwww

cli53 rrcreate example.com 'www 60 A 192.168.0.1'
CNAMEレコード
cli53 rrcreate example.com 'mail CNAME ghs.googlehosted.com.'
MXレコード
cli53 rrcreate example.com '@ MX "10 192.168.0.1" "20 192.168.0.2"'

AWS LambdaでGmail APIの呼び出し 第2回【cloudpack 大阪 BLOG】

 前回でGmailAPIにアクセスする準備は整いましたので、引き続きLambdaでアクセスしてみたいと思います。

2.Lambdaの設定

① Lambda用プロジェクトの作成

プロジェクトのディレクトリを作成します。また、GmailAPIにアクセス用のライブラリなども利用するためvirtualenvで環境を分けておきます。

mkdir gmailapi_for_lambda
cd gmailapi_for_lambda/
virtualenv .venv
秘密鍵をp12からpemに変換

ダウンロードしたp12形式のファイルをpem形式に変換

openssl pkcs12 -in xxxxxxx.p12 -nodes -nocerts > privatekey.pem
③ Lambdaの作成

以下の4つのファイルを準備する。

gmailapi_for_lambda
├── gmail_access.py       ・・・ lambda関数本体
├── lambda.json             ・・・ labda-uploader用設定ファイル
├── privatekey.pem        ・・・ APIアクセス証明書
└── requirements.txt      ・・・ ライブラリリスト

gmail_access.py
GmailにアクセスするLambda関数本体となります。

#######################################################################################
# This is a sample to get the message list from Gmail
#######################################################################################
import httplib2
import imaplib,email,email.Header
import base64

from apiclient.discovery import build
from oauth2client.file import Storage
from oauth2client.client import SignedJwtAssertionCredentials
import pprint

def lambda_handler(event, context):
    try:
       main()
    except Exception:
       traceback.print_exc()
       sys.stderr.flush()
       sys.exit(1)

def main():
    pp = pprint.PrettyPrinter(indent=4)

    SERVICE_ACCOUNT_ID="your service account id"     ← サービスアカウントIDをセット
    KEY_SECRET="your secret key"                                 ← 秘密鍵のパスワード
    GMAIL_ACCOUNT="your gmail account"                  ← gmailのアカウント

    f = open("privatekey.pem","rb")
    key = f.read()
    f.close()

    credentials = SignedJwtAssertionCredentials(
        SERVICE_ACCOUNT_ID,
        key,
        scope="https://www.googleapis.com/auth/gmail.readonly",
        sub=GMAIL_ACCOUNT)
    http = httplib2.Http()

    http = credentials.authorize(http)
    service = build("gmail", "v1", http=http)

    message_list = service.users().messages().list(userId='me',
                                                    #labelIds=label_ids,
                                                    #pageToken=page_token,
                                                    maxResults=10,
                                                    q='is:unread'
                                                    ).execute()
    for message in message_list['messages']:
       message_detail = service.users().messages().get(userId='me', id=message['id'],format='metadata').execute()

       for element in message_detail['payload']['headers']:
          if element['name'] == 'Subject':
             print element['value']

if __name__ == '__main__':
        main()

◆lambda.json
lambda-uploder用の設定ファイルとなります。名称は任意です。roleは環境に合わせて変更してください。

{
  "name": "GmailTest",
  "description": "GmailTest",
  "region": "ap-northeast-1",
  "handler": "gmail_access.lambda_handler",
  "role": "arn:aws:iam::000000000000:role/lambda_basic_execution",
  "timeout": 300,
  "memory": 128
}

◆requirements.txt
利用するpythonのライブラリを記載します。Lambdaへのアップロード時にはこのファイルを参照してライブラリがアップロードファイルへ固められます。

pyCrypto
google-api-python-client

virtualenv環境をactive化する

source .py27/bin/activate

pythonライブラリのインストール

pip install -r requirements.txt

lambda-uploaderのインストール

pip install lambda-uploader

lambda-uploaderを実行
事前にaws configrueでアップロード先のアカウントのアクセスキー及びシークレットキーをセットしておく

lambda-uploader 
④ 動作確認

Gmailに下記のサンプルデータを準備しました。
f:id:cloudfish:20160208230400p:plain

アップロードしたLambda関数を選択
f:id:cloudfish:20160208230413p:plain

Testボタンで実行します。
f:id:cloudfish:20160208230422p:plain

ログに取得したメールのタイトルが出力されました。
f:id:cloudfish:20160208230426p:plain


これでLambdaからGmailにアクセスできるようになりました。同様の方法で他のAPIへのアクセスもできるはずなので、色々と用途が広がりそうです。
gmailapi-explorerが、Web上で実行できかつコードを生成してくれるのでかなり便利だと思いました。
今回のサンプルはGitHub - cloudfish7/gmailapi_for_lambdaにアップしています。