pict3の日記

AWSネタを中心に

Boto3のヘルパークラスを作った【cloudpack大阪BLOG】

これまでBoto3を使う時にコピペで流用していたコード部分をライブラリ化しました。
ついでに、PyPIに登録して世にリリースしてみました。
以下で利用可能になります。

# pip install boto3helper

機能の紹介

いろいろ機能は作っていく予定ですが、今回は、各リソースに対してイテレート対策を紹介します。

Strategyパターン風にイテレートするよう実装しました。
Throttlingによるリトライも考慮しています。

これをコーディングすると、結構なボリュームになります。
例えば、各EC2インスタンスに対し処理(インスタンスIDを出力)を行う場合は、以下のようになります。

#!/usr/bin/python
# coding:utf-8

import boto3
from boto3.session import Session
import botocore

session ={"accessKey" : "hogehoge...",
                  "secretKey"  : "fugafuga...",
                  "region"        : "ap-northeast-1など"}

client = session.client('ec2')


def describe_instances_force_1st():
    try:
        response = client.describe_instances()
    except botocore.exceptions.ClientError:
        if not 'ThrottlingException' in sys.exc_info():
            raise
        time.sleep(1.0)
        response = describe_instances_force_1st()

    return response

def describe_instances_force(next_token):
    try:
        response = client.describe_instances(
            NextToken = next_token
        )
    except botocore.exceptions.ClientError:
        if not 'ThrottlingException' in sys.exc_info():
            raise
        time.sleep(1.0)
        response = describe_instances_force(next_token)

    return response


if __name__ == "__main__":

    first_call = True
    result     = True
    while True:
        if first_call:
            resources = describe_instances_force_1st()
            first_call = False
        else:
            resources = describe_instances_force(next_token)

        for reservation in resources['Reservations']:
            instances = reservation['Instances']
            for instance in instances:
                print('InstanceId'),
                print(instance['InstanceId'])

        # 次のページなし
        if not resources.has_key('NextToken'):
            break
        # 次のページへのポインタを取得
        next_token = resources['NextToken']


boto3helperを使うことで、以下のようになります。

#!/usr/bin/python
# coding:utf-8

import boto3helper

# 各インスタンスに対して実行する処理
def test_func(instance, args):
    print(args['arg1']),
    print(instance[args['arg2']])


if __name__ == "__main__":

    credential ={"accessKey" : "hogehoge...",
                         "secretKey"  : "fugafuga...",
                         "region"        : "ap-northeast-1など"}

    o = boto3helper.ec2_helper.EC2_Helper(credential)
    o.exec_func_each_instances(test_func,
                               arg1 = 'InstanceId: ',
                               arg2 = 'InstanceId')

テストコードが適当だったり、対応サービスも少なかったりまだまだ未熟なライブラリですが、コツコツとアップデートしておきますので、気になった方は使ってみてください。

github.com

おさらい!AWS Route53【cloudpack大阪BLOG】

Route53について復習したので、もろもろまとめてみました。 f:id:pict3:20160224171106j:plain

目次

Hosted Zone

Public Hosted Zone

普通に使う分にはこっち。

Private Hosted Zone

Publicに書いたものは、グローバルに伝搬しますが、こちらはVPC内でのみでしか名前解決できません。
用途は以下のような感じかと。

  • インターネットに出ない名前解決
  • RDSのエンドポイントとかに短いエイリアスを付けることができる
検証

VPC内のEC2から名前解決

[ec2-user@ec2-in-vpc ~]$ dig hoge.pict3.net +short
10.0.0.xxx

VPC外のEC2から名前解決

[ec2-user@ec2-out-of-vpc ~]$ dig hoge.pict3.net +short
52.yy.yy.yy

Publicと重複した場合はPrivateが優先されるようです。


ルーティングポリシー

シンプルルーティング

通常のDNS。静的マッピング

重み付けルーティング

複数の同一名称のレコードを登録し、割振る割合を設定できます。
ブルー・グリーン・デプロイメントに最適!

設定例

f:id:pict3:20160223203800j:plain

TTLの件は詳しくはコチラを参照ください。

検証

キャッシュも入っていると思うので、あまり正確なものではないですが、なんとなく動いていそうです。

$ for i in {1..10}; do dig test.pict3.net +short; sleep 60; done;
10.0.0.2
10.0.0.2
10.0.0.2
10.0.0.2
10.0.0.1
10.0.0.1
10.0.0.2
10.0.0.2
10.0.0.2
10.0.0.2



レイテンシルーティング

AWSに蓄積されたレイテンシ情報をもとに、最適なリージョンにふり分けます。

設定例

f:id:pict3:20160223220609j:plain

検証

オレゴンリージョンと東京リージョンにローンチしたEC2から名前解決してみて確認してみます。

[ec2-user@ec2-at-usa ~]$ dig test.pict3.net +short
10.0.0.1

[ec2-user@ec2-at-japan ~]$ dig test.pict3.net +short
10.0.0.2

いい感じ!

Failoverルーティング

f:id:pict3:20160224205816p:plain
必ずヘルスチェックとセットで使います。
ヘルスチェックに失敗した際に、S3上のSorryページや別リージョンのELBにアクセスを向けることができます。
ヘルスチェックの設定は後述。

設定例

f:id:pict3:20160224191511j:plain
加えて、S3にSorryページのHTMLをアップロードしておきました。

検証

OKのとき、Route53に設定したドメインに対して解決されるIPは、ELBのものとなります。

$ curl -LI hoge-elb-xxxxxxxx.ap-northeast-1.elb.amazonaws.com -o /dev/null -w '%{http_code}\n' -s
200
$ dig hoge-elb-xxxxxxxx.ap-northeast-1.elb.amazonaws.com +short
54.xxx.xxx.xxx
52.yyy.yyy.yyy
$ dig test.pict3.net +short
54.xxx.xxx.xxx
52.yyy.yyy.yyy

NGのとき、Route53に設定したドメインに対して解決されるIPは、ELBのものとなりません。
(きっとS3のwebホスティングインスタンス

$ curl -LI hoge-elb-xxxxxxxx.ap-northeast-1.elb.amazonaws.com -o /dev/null -w '%{http_code}\n' -s
403
$ dig test.pict3.net +short
54.zzz.zzz.zzz

ブラウザからもフェイルオーバー時に、S3に置いたsorryページが表示されることが確認できました。

Geolocationルーティング

アクセス元の位置情報を元にルーティングします。
レイテンシベースのものと似通った結果になりそうですが、より厳密に設定することができます。

設定例

f:id:pict3:20160224212302j:plain

検証

ここでもオレゴンリージョンと東京リージョンにローンチしたEC2から名前解決してみて確認してみます。

[ec2-user@ec2-at-usa ~]$ dig test.pict3.net +short
10.0.0.1

[ec2-user@ec2-at-japan ~]$ dig test.pict3.net +short
10.0.0.3

アクセス元の国のレコードセットがなかったら。。。
名前解決しませんでした。USで州が異なる場合も同様に名前解決しません。

ヘルスチェック

「エンドポイント」または「その他のヘルスチェック」の状態をチェックします。

「エンドポイント」にはIPアドレスドメイン名を指定できます。 ELBのヘルスチェックのように対象プロトコルなどが選択できます。

設定例

ELBのエンドポイントで設定してみました。 f:id:pict3:20160224181747j:plain

検証

エンドポイントのヘルスチェックではレスポンスコードが200以上かつ400未満かどうかをチェックします。
従って、

$ curl -LI hogehoge.ap-northeast-1.elb.amazonaws.com -o /dev/null -w '%{http_code}\n' -s
200

のときは、Healtyとなります。

ここで、webサーバーのindex.htmlを消してレスポンスコードが403になるようにしてみます。

$ curl -LI hogehoge.ap-northeast-1.elb.amazonaws.com -o /dev/null -w '%{http_code}\n' -s
403

このときは、Unhealtyとなります。

まとめ

思っていたよりも簡単に機能的なことができるので、Route 53を好きになりました!
今後、Traffic flowとかも触ってみます。

AWS LambdaのログがCloudWatchLogsに出力されない(?)【cloudpack大阪BLOG】

f:id:pict3:20160217201721j:plain

自分自身もハマりましたが、周りでも同じような声を聞くので手順を展開しておきます。

Lambdaを利用していて、イベントソースによりちゃんと発火したかどうか、Cloud Watch Logsを確認することが多いと思います。

ただし、このログ、出力されないことがあるんじゃないか?との声をたまに聞きます。

そんなときにチェックする方法を紹介いたします。

まずは最初に。。。

本当に実行されたのか? CloudWatchから「Invocation count」を確認します。

まぁここは大丈夫かと思いますが、念のため。 f:id:pict3:20160217214301j:plain

Cloud Watch Logsを見るときの落とし穴

次にCloud Watch Logsを見ますが、この時に見落としが発生することが多いです。

ストリーム内には複数ページのログが出力されていることがあります。 同じような出力内容が続き、一見、末端まで見たかなと思っても、 ページ送りをポチポチすると、目的のログが出てくることがあります。 f:id:pict3:20160217214433j:plain

しかし見づらい(´Д`)

そんなわけで、ローカル環境(mac)にログをダウンロードして、目的のログを探すことにします。

ローカル環境でログを確認する手順

S3へエクスポート

まずは、Cloud Watch LogsをS3にエクスポートします。

エクスポートはロググループ単位で行うことができます。 期間やプレフィックスで絞り込むことも可能です。

出力先のS3のバケットは事前に準備ください。

f:id:pict3:20160217215315j:plain

S3からダウンロード

エクスポートしたログは現物さながら、ストリーム単位でフォルダ階層化されています。

マネジメントコンソールからペチペチとダウンロードするのは至極面倒なので、CLIを使ってダウンロードしちゃいます。

$ aws s3 --profile hoge cp s3://fuga ~/Documents/temp/ --recursive

ダウンロードしたファイルから検索

落としてきたログファイル群から、特定のキーワードで検索します。

find ~/Documents/temp/exportedlogs -type f -name "*.gz" | xargs gunzip -c | grep piyo

上記の手順は、Lambdaログに限らず有効です。

Cloud Watch Logsを探りたい時にお試しください。

AWS CloudWatchのカスタムメトリクスをDataDogで確認【cloudpack大阪BLOG】

f:id:pict3:20160205230817j:plain

DataDogとは、監視のSaaSで、様々な形式でグラフ表示できたりします。 タグなどでグルーピングもできるので、簡単なデータ解析にも役立ちます。

AWSとの親和性も高く、簡単な設定でCloudWatchのデータを監視することができちゃいます。 そして、CloudWatchでは最大2週間しかデータを保持しませんが、 DataDogに登録することでより以前のデータをみることもできます。

詳しくは本家サイトで。

今回は、CloudWatchのカスタムメトリクスをDataDogで表示してみようと思います。 以下、手順です。

カスタムメトリクスを設定する

メモリ監視を追加してみます。 導入環境はAmazon Linuxで。

$ uname -a
Linux ip-10-0-0-194 4.1.10-17.31.amzn1.x86_64 #1 SMP Sat Oct 24 01:31:37 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

AWSスクリプトを公開してくれているのでそちらを拝借。 http://aws.amazon.com/code/8720044071969977

perlのライブラリが諸々必要なのでインストール。

$ wget http://aws-cloudwatch.s3.amazonaws.com/downloads/CloudWatchMonitoringScripts-1.2.1.zip
$ unzip CloudWatchMonitoringScripts-1.2.1.zip
$ cd aws-scripts-mon/
$ sudo yum -y install perl-core perl-URI perl-DateTime-TimeZone perl-Bundle-LWP

問題なくインストールができたかを確認!

$ ./mon-put-instance-data.pl --mem-util --verify --verbose
MemoryUtilization: 11.7104374428861 (Percent)
No credential methods are specified. Trying default IAM role.
Using IAM role <ec2-basic>
Endpoint: https://monitoring.ap-northeast-1.amazonaws.com
Payload: {"MetricData":[{"Timestamp":1454679988,"Dimensions":[{"Value":"i-0b1dfb233a07e0d4f","Name":"InstanceId"}],"Value":11.7104374428861,"Unit":"Percent","MetricName":"MemoryUtilization"}],"Namespace":"System/Linux","__type":"com.amazonaws.cloudwatch.v2010_08_01#PutMetricDataInput"}

Verification completed successfully. No actual metrics sent to CloudWatch.

cronに登録して、CloudWatchにデータが積まれることを確認します。

$ crontab -l
*/5 * * * * ~/aws-scripts-mon/mon-put-instance-data.pl --mem-util --mem-used --mem-avail --from-cron

OK!! f:id:pict3:20160205225243j:plain

DataDogを設定する

アカウントを登録します。

ご覧のとおり、たくさんのリソースに対応しています。 f:id:pict3:20160205225655j:plain

今回はcredentialで登録します。 全体的にカスタムメトリクスを受けてもいいですが、 アカウントを限定してカスタムメトリクスを取ることもできます。 f:id:pict3:20160205230000j:plain

以上! 実に簡単。

しばらくすると、Metrics ExplorerからDataDogでグラフ表示可能に。 f:id:pict3:20160205230411j:plain

DataDog、いろいろ試していこうと思います!

AWS IoTでインターフォン【cloudpack大阪BLOG】

cloudpack大阪オフィスのインターフォンが鳴らなくなりました。
なんともみすぼらしいので、なんとかしたい(; ´Д`)
f:id:pict3:20160119214139j:plain

こんなときにもAWSで解決するのがcloudpack!
うってつけのサービス、AWS IoTを使います。

システム構成はこんな感じです。
呼び鈴側のデバイスは、AWS IoT ボタンを使いました。
受け口側のデバイスは、固定電話を使います。LambdaからTwilioでコールします。
f:id:pict3:20160119205014p:plain

実現

AWS IoT

今回はAWS IoT ボタンを使いましたが、Rule以外のリソースはデフォルトのままで進めます。
ちなみに、AWS IoTには4種のリソースがあります。
それぞれの役割はかなりざっくりと言うと、以下のような感じです。(詳しくは別の機会に)
・Certificate ... 証明書。PolicyとThingに紐付けます。
・Policy ... アクセス許可の設定を行います。
・Rule ... デバイスから受けたデータをどう処理するかを定義します。
・Thing ... デバイスにあたります。

今回はピンポンダッシュ対策として、ボタンの長押し(LONG Click : 1.5sec以上)を拾うようにします。
Ruleはこんな感じになりました。

・Name
aws_iot_button_rule_long
・State
Enabled
・Description
aws_iot_button_rule_long
・Query string
SELECT * FROM 'iotbutton/+' WHERE clickType = "LONG"

actionには後述のLambdaファンクションを当てがいます。

Lambda

twilioを使ったお電話Lambda、以前のBlogではnode.jsを使ったので今回はPython版を例示します。

$ mkdir caller
$ cd caller/
$ pip install twilio -t .
$ vi lambda_function.py
$ zip -r upload.zip *

TwilioのサイトPython版コードも例示してくれるので、lambda_function.pyは割愛します。

あとは、Lambdaにupload.zipをアップロードするだけ。

結果

あっという間に応急処置Done!! f:id:pict3:20160119214108j:plain

AWS IoTもLambdaもTwilioも使い易い!
インターフォンの故障対応もcloudpackまで(嘘)

※ 本記事はフィクションです。AWS IoTボタンは日本国内では技適通っていないので使えません。  実現の際は技適の通ったデバイスをご使用ください。

AWSのIPアドレスレンジ変更をBacklogに通知する(SNS + Lambda + S3)【cloudpack大阪BLOG】

やりたいこと

AWSのリソースが使うIPアドレスレンジは、以下に公開されています。

https://ip-ranges.amazonaws.com/ip-ranges.json

ちょくちょく更新されますが、FireWallやWAFの設定に反映させる必要があるシステムをお持ちのプロジェクト担当者へ、正確かつ迅速に通知することを目標に自動化システムを構築しました。

実現

AWSのリソースが使うIPアドレスレンジの変更は、SNSで受信することができるようです。 また、担当者への連絡はBacklogを使うこととします。

以下のような構成で実現します。 f:id:pict3:20151216134539p:plain

S3

S3に通知を受ける直前のIPアドレスレンジを保管しておきます。 バケット名は適当に。

Lambdaファンクション

IAMロールには、S3の読み書き権限を付けておきます。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import httplib
import json
import difflib

import boto3

s3 = boto3.resource('s3')

import pprint
pp = pprint.PrettyPrinter(indent=4)



API_KEY               = "<Backlogより発行>"
BACKLOG_HOST          = "<Backlogスペース + ドメイン>"

S3_BACKET_NAME        = "<バケット名>"
S3_LAST_IP_RANGE_OBJ  = "last-ip-range.json"


def get_project_id(project_code):

    uri = "/api/v2/projects/%s?apiKey=%s" % (project_code, API_KEY)
    
    connect = httplib.HTTPSConnection(host = BACKLOG_HOST, port = 443)
    connect.request("GET", uri)
    response = connect.getresponse()

    body = response.read()

    connect.close()

    data = json.loads(body)

    project_id = data['id']

    return project_id


def post_issue(project_id, summary, issue_type_id, priority_id, description):

    data = {
        "projectId"   : project_id,
        "summary"     : summary,
        "issueTypeId" : issue_type_id,
        "priorityId"  : priority_id,
        "description" : description
    }

    params = json.dumps(data)
    headers = {
        "Accept":"application/json",
        "Content-Type":"application/json",
    }
    uri = "/api/v2/issues?apiKey=%s" % (API_KEY)

    connect = httplib.HTTPSConnection(host = BACKLOG_HOST, port=443)
    connect.request("POST", uri , params, headers)
    response = connect.getresponse()

    connect.close()

def download_ip_range(file):
    uri = "/ip-ranges.json"
    
    connect = httplib.HTTPSConnection(host = "ip-ranges.amazonaws.com", port = 443)
    connect.request("GET", uri)
    response = connect.getresponse()

    body = response.read()
    
    f = open(file, "w")
    f.write(body)
    f.close()

    connect.close()

    return

def construct_description(diff):
    description =  u"AWSの使用するIPアドレスレンジに更新がありました。"
    description += u"変更内容は以下です。"
    description += '\n'
    description += "{code}\n"
    for buf in diff:
        description += buf
    description += "{/code}\n"
    
    print(description)

    return description

def lambda_handler(event, context):

    last_ip_range_file    = '/tmp/' + S3_LAST_IP_RANGE_OBJ + '_last'
    current_ip_range_file = '/tmp/' + S3_LAST_IP_RANGE_OBJ + '_current'

    download_ip_range(current_ip_range_file)
    s3.meta.client.download_file(S3_BACKET_NAME, S3_LAST_IP_RANGE_OBJ, last_ip_range_file)

    last_ip_range    = open(last_ip_range_file, "r")
    current_ip_range = open(current_ip_range_file, "r")

    diff = difflib.context_diff(last_ip_range.readlines(), 
                                current_ip_range.readlines(), 
                                fromfile='before', 
                                tofile='after')

    description = construct_description(diff)

    summary = "[連絡] AWSリソースのIPアドレスレンジに更新がありました"
    issue_type_id = 3
    priority_id   = 3

    # 通知したいプロジェクトを追加
    project_ids = []
    project_ids.append(get_project_id("HOGE"))
    project_ids.append(get_project_id("FUGA"))

    for project_id in project_ids:
        post_issue(project_id, summary, issue_type_id, priority_id, description)

    response = s3.meta.client.upload_file(current_ip_range_file, S3_BACKET_NAME, S3_LAST_IP_RANGE_OBJ)

SNS

こちらのサイトを参考にTopic ARNを設定します。 ProtocolはLambda、Endpointは先ほど作ったファンクションを選択します。 f:id:pict3:20151216152842p:plain

Lambdaのイベントソースに反映されます。 f:id:pict3:20151216153044p:plain

結果

ちゃんとBacklogに投稿されました! Lambdaの恩恵、デカすぎです!! f:id:pict3:20151216160000p:plain

AMIバックアップを自動取得するLambdaファンクション(python版)【cloudpack大阪BLOG】

f:id:pict3:20151208223319p:plain

今回はシンプルなLambda。

今秋のre:InventでアップデートされたScheduled Eventを利用して、AMIバックアップをとるファンクションを作成しました。

 

仕様

仕様は以下のような感じ。

  • AMIバックアップをとりたいEC2に、タグ'Backup--Generation : 世代数'を設定しておく
  • 'Backup-Generation'には、保持したい世代数を設定する
  • AMIおよびSnapShotには、元となるEC2のタグ群(*)を設定する (*)Backup-Generationを除く
  • LambdaのScheduled Eventをトリガーに実行する
  • AMI取得後、本ファンクションで作成したAMIのうち、保持したい世代数よりも古いイメージおよびスナップショットを削除する
  • ファンクションで作成したAMIを区別するため、AMIおよびsnapshotにタグ'Backup-Type : auto'を付与する

 

EC2設定

まずは、EC2にタグ付け。
ここでは、2世代のバックアップをとることとします。

f:id:pict3:20151208223530p:plain

 

Lambdaファンクション

次は肝心かなめのLambdaファンクション。

コード 

コードは、長くなるのでQiitaを参照ください。

AMIバックアップを取るLambdaファンクション(python) - Qiita

 IAM Role

roleには以下を設定しています。

  • AmazonEC2FullAccess
  • AWSLambdaBasicExecutionRole

Event Source

毎朝2時に設定しました。

f:id:pict3:20151208224740p:plain

公式ドキュメントがわかりやすいです。

 

結果

こんな感じで、意図通り動いていそうです!

f:id:pict3:20151208230517p:plain

 

まとめ

EC2と1ヶ月のコスト比較をしてみます。コストは超概算ですので悪しからず。

 

EC2(*)の運用コストは以下です。

(*) 東京リージョン、オンデマンド、t2.micro、Linux

 0.02 (/hr) * 24 (hr) * 31 (days) = $14.88 / 月

 

Lambda(*)の運用コストを計算してみます。

(*) 東京リージョン、128MB

リクエスト回数は誤差の範囲になるので無視します。

実行時間は上述のログから引っ張ってきました。

 0.000000208 (/msec) * 43,889.93 (msec) * 31 (days)  = $0.283 / 月

 

コストはなんと、52分の1に!!

Lambdaは今のところ無料枠に期間制限がないので、こちらも考慮すると更なるコスト削減になるかもです。

可用性のメリットもお忘れなく! 

 

Scheduleイベントを使うことで、バッチ処理的に使っていたサーバーを捨てることができそうです!

 

今回はさらっと作ったLambdaファンクションですが、wait処理をSNSとかで無駄時間を減らすよう改善していきたいところです。