EC2インスタンスにRaspberry Piで電源スイッチを付けてみた【cloudpack 大阪 BLOG】
久しぶりにRaspberryPiを触ってみて思いついたので早速作ってみました。以前はLチカやったところで終わっていましたが、Pythonを少し覚えたので何かAPIと連携させてみようと考えていました。最近、EC2インスタンスを触ることが多くなりましたが、サーバの電源を入れるということを全てWeb上でやっているので物理スイッチでわざわざ起動、停止をやってみようと思い作ってみました。
今回やってみたのは以下のような内容です。
・RaspberryPiでスイッチを押すとEC2が起動もしくは停止
・起動中 or 停止中はLEDを点滅させる
・起動、停止が完了したらslackで自分に通知
準備
RaspberryPi B+ (OSはraspbian 今回使用したバージョンは「Linux raspberrypi 3.12.28+ 」)
ブレッドボード
LED
タクトスイッチ
抵抗 330Ω(LED用)、1kΩ(タクトスイッチ用)
ジャンパーワイヤ
モジュールのインストール
pipのインストール
apt-get install python-pip
boto3(AWS SDK FOR Python)のインストール
pip install boto3
配線の確認
実際に部品が正しく配置されているか確認するため、下記のプログラムを実行して動作確認を行います。
LEDの動作確認
#! /usr/bin/env python import RPi.GPIO as GPIO import time LEDPIN = 21 GPIO.setwarnings(False) # Suppress warnings GPIO.setmode( GPIO.BCM ) GPIO.setup( LEDPIN, GPIO.OUT ) while 1: GPIO.output( LEDPIN, True ) time.sleep( 1.0 ) GPIO.output( LEDPIN, False ) time.sleep( 1.0 )
実行すると一定間隔でLEDが点滅します。点滅しなければ部品の配置や配線が間違えてないか見直してください
タクトスイッチの動作確認
#! /usr/bin/env python import RPi.GPIO as GPIO import time # define INPUT_PIN = 4 # init GPIO.setwarnings(False) # Suppress warnings GPIO.cleanup() GPIO.setmode(GPIO.BCM) GPIO.setup(INPUT_PIN,GPIO.IN) if __name__ == "__main__": Btn_Flag = False while True: print (GPIO.input(INPUT_PIN)) time.sleep(0.2)
実行してボタンを押すとボタンが押されている間はコンソールに「1」が表示されます
EC2インスタンスの起動・停止プログラム
上記のテストプログラムが正常に実行されれば実際にボタンを押してEC2のストップ、スタートができるプログラムを作成していきましょう。
AWSのアクセスキー、シークレットキー及びSlackのAPIキーは取得したものに変更してください。また、Slackの送信先は自身のユーザー名(@ + ユーザー名)に変更してください。
#! /usr/bin/env python import RPi.GPIO as GPIO import time import boto3 from boto3.session import Session import sys import urllib import urllib2 # define INPUT_PIN = 4 LEDPIN = 21 # init GPIO.setwarnings(False) # Suppress warnings GPIO.cleanup() GPIO.setmode(GPIO.BCM) GPIO.setup(INPUT_PIN,GPIO.IN) GPIO.setup( LEDPIN, GPIO.OUT ) ################################################################# # Function ################################################################# def getResourceEC2 (): session = Session(aws_access_key_id='ACCESS_KEY', aws_secret_access_key='SECRET_KEY', region_name='ap-northeast-1') ec2 = session.resource('ec2') return ec2 def get_ec2_state(instanceId): ec2 = getResourceEC2() instance = ec2.Instance(instanceId) if(instance.state['Name']=='running'): return True else: return False def start_ec2(instanceId): ec2 = getResourceEC2() instance = ec2.Instance(instanceId) if(instance.state['Name']=='stopped'): instance.start() # Flashing LED led_flag = False while instance.state['Name'] != 'running': GPIO.output( LEDPIN, not led_flag) led_flag = not led_flag instance.reload() time.sleep( 1.0 ) GPIO.output( LEDPIN, True) print('EC2 Start!') post_slack('EC2 Start!') else: print('EC2 already Started!') post_slack('EC2 already Started!') def stop_ec2(instanceId): ec2 = getResourceEC2() instance = ec2.Instance(instanceId) if(instance.state['Name']=='running'): instance.stop() # Flashing LED led_flag = False while instance.state['Name'] != 'stopped': GPIO.output( LEDPIN, not led_flag) led_flag = not led_flag instance.reload() time.sleep( 1.0 ) GPIO.output( LEDPIN, False) print('EC2 Stop!') post_slack('EC2 Stop!') else: print('EC2 already Stopped!') post_slack('EC2 already Stopped!') def post_slack(message): url = "https://slack.com/api/chat.postMessage" params = {'token' :'SLACK_API_KEY', 'channel':'@USER', 'text' : message } req = urllib2.Request(url) req.add_header('Content-Type', 'application/x-www-form-urlencoded') req.add_data(urllib.urlencode(params)) res = urllib2.urlopen(req) ################################################################# # Main Function ################################################################# if __name__ == "__main__": print('Switch Start') argvs = sys.argv Id = argvs[1] Btn_Flag = get_ec2_state(Id) GPIO.output( LEDPIN, Btn_Flag) while True: if(GPIO.input(INPUT_PIN)): Btn_Flag = not Btn_Flag if(Btn_Flag): start_ec2(Id) else: stop_ec2(Id) time.sleep(0.2)
※簡略化するため、ステータスチェックなど細かい処理は省いています。
泉南 2015.11.08
天候 :雨
今季初のタチウオでした。
貝塚 2015.11.03
天候 :曇
boto3(AWS SDK for Python)でCloudWatchからメトリクスを取得する【cloudpack 大阪 BLOG】
boto3を使ってCloudWatchからメトリクスを取得する必要が出てきたので勉強がてら簡単なサンプルを作ってみました。
環境
サーバ:CentOS6.6
Python:2.6.6
boto3:1.2.1
EC2インスタンスのメトリクスを取得のサンプル
まずはEC2のCPU使用率を取得してみたいと思います。
下記のコードを記載して適当なファイル名で保存してください。
import boto3 from boto3.session import Session import datetime import dateutil.tz import pprint pp = pprint.PrettyPrinter(indent=4) accesskey = "YOUR_ACCESSKEY" secretkey = "YOUR_SECRETKEY" region = "YOUR_REGION" session = Session( aws_access_key_id=accesskey, aws_secret_access_key=secretkey, region_name=region) client = session.client('cloudwatch') # Get EC2 CPUUtilization response = client.get_metric_statistics( Namespace='AWS/EC2', MetricName='CPUUtilization', Dimensions=[ { 'Name': 'InstanceId', 'Value': 'i-12345678' }, ], StartTime=datetime.datetime.utcnow() - datetime.timedelta(seconds=600), EndTime=datetime.datetime.utcnow(), Period=300, Statistics=['Average'] ) pp.pprint(response)
CloudWatchコンソール
リファレンスをななめ読みしただけですが、サイドメニューのMetricsがNamespaceとなりMetricNameが画面右側のMetricNameを指定します。Namespaceは「AWS/」をMetricsに付加すればよさそうです。(ここは後ほど書いてますが誤りでした)Dimensionsについては、取得したいサービスによって名称が異なるため、NameとValueを指定するようになっています。CPU使用率の場合はNameにInstanceIdをValueに実際のIDを指定します。
実行結果
python get_ec2_cpu.py { u'Datapoints': [ { u'Average': 0.20000000000000001, u'Timestamp': datetime.datetime(2015, 10, 31, 8, 16, tzinfo=tzutc()), u'Unit': 'Percent'}], u'Label': 'CPUUtilization', 'ResponseMetadata': { 'HTTPStatusCode': 200, 'RequestId': '1dfba5e6-7fa9-11e5-bc7c-1f8b347fd072'}}
EMRのメトリクスを取得
実際に取得したいMetricsは、EC2ではなくEMRのメトリクスを取得したかったのでEC2と同様にコードを書いてみましたが何故か取得できず。エラーは発生せず返却されるDatapointsの値に何もセットされていませんでした。スペルミスか時間指定が間違っているのかと思い見直すも問題はなさそうでした。そもそもNamespaceに問題があるのか?と疑いを抱き、list_metrics関数で取得してみると何も取得できませんでした。どうやら名前空間の指定に問題があるようで、調べてみると名前空間はAWS の名前空間 - Amazon CloudWatchで定義されていました。CloudWatchのMetricsと紐付くと思い込んでいましたがそうではないようです。今回は「AWS/ElasticMapReduce」と指定して正しく値を取得することができました。
response = client.get_metric_statistics( #Namespace='AWS/EMR', ←間違い Namespace='AWS/ElasticMapReduce', ←正 MetricName='IsIdle', Dimensions=[ { 'Name': 'JobFlowId', 'Value': 'j-1PNEZHM6MFON8' }, ], StartTime=datetime.datetime.utcnow() - datetime.timedelta(seconds=300), EndTime=datetime.datetime.utcnow(), Period=300, Statistics=['Average'] )
実行結果
python boto_sample.py { u'Datapoints': [ { u'Average': 1.0, u'Timestamp': datetime.datetime(2015, 10, 22, 9, 20, tzinfo=tzutc()), u'Unit': 'None'}], u'Label': 'IsIdle', 'ResponseMetadata': { 'HTTPStatusCode': 200, 'RequestId': '7c3a6076-8044-11e5-82dd-9bacba3e4791'}}
Laravel5でphp-imapからgmailにアクセス【cloudpack 大阪 BLOG】
PHPからgmailを取得したいと思い、方法を調査していたところphp-imapを使って取得できることがわかりました。が、結構ハマってしまいました。
今回はLaravel5を使ってgmailから固定条件で検索したメールの件名を一覧で表示させるサンプルアプリを作成してみました。
環境
以下の構成でサンプルアプリを作成しました。
Apache、PHPのインストール等、Laravel5が動作する環境を構築をしておいてください。
サーバー : Amazon Linux
Webサーバー : Apache 2.4
PHP : php 5.6
php framework : Laravel5
Controllerの作成
[project_root]/app/Http/Controller/GmailController.phpを作成する
namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Http\Requests; use App\Http\Controllers\Controller; class GmailController extends Controller { public function getIndex() { $server = "imap.gmail.com"; $port = 993; // ポート番号 $account = "test@gmail.com"; // Gmailアカウント $password = "password"; // パスワード $mailbox = "{".$server.":".$port."/novalidate-cert/imap/ssl}INBOX"; // メールサーバ接続 $mbox=null; $mbox = imap_open($mailbox, $account, $password) or die('Cannot connect to Gmail: ' . imap_last_error()); // メール検索(検索条件:件名にTEST MAILが含んでおり、受信日時が2015/08/08以降) $result = imap_search($mbox,'SUBJECT "TEST MAIL" SINCE "8 August 2015" ', SE_UID); $mail=null; for($i = 0;$i<count($result);$i++){ // ヘッダ情報の概要を取得 $overview = imap_fetch_overview($mbox, $result[$i], FT_UID); // 件名、送信元アドレス、送信日を取得 $subject = mb_convert_encoding(mb_decode_mimeheader($overview[0]->subject), 'utf-8'); $from = mb_convert_encoding(mb_decode_mimeheader($overview[0]->from), 'utf-8'); $date = date("Y-m-d H:i:s", strtotime($overview[0]->date)); $mail['subject'] =$subject; $mail['date'] = $date; $mail['from'] =$from; array_push($mail_data,$mail); } // メールボックスのクローズ imap_close($mbox); return view('gmail.index')->with('mail_data',$mail_data); } }
Viewの作成
resources/views/app.blade.phpを以下のように修正
共通レイアウトファイルでjqueryとbootstrapを読み込んでおきます。(好みで修正してください)
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Gmail Test</title> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script> </head> <body> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> <div class="container"> <div class="row"> <div class="col-md-12"> @yield('content') </div> </div> </div> </body> </html>
[project_root]/resources/views/gmail/index.blade.php
@extends('app') @section('content') <h1>Gmail</h1> <div class="container"> <table class="table table-striped table-bordered"> <tr> <th>#</th> <th>date</th> <th>subject</th> <th>from</th> </tr> @foreach ($mail_data as $index => $recorde) <tr> <td>{{$index+1 }}</td> <td> {{ $recorde['date'] }}</td> <td> {{ $recorde['subject'] }}</td> <td> {{ $recorde['from'] }}</td> </tr> @endforeach </table> </div> @endsection
Routingの設定
Route::controller('gmail', 'GmailController');
実行確認
「url/gmail」にアクセスしてみましょう。Gmailにアクセスしているため表示まで少し時間がかかります。
正しく表示されない場合は、Gmailでブロックされている可能性が高いので、以下のハマったところを参考にしてみてください。
ハマったところ
imap_openでCouldn't Open Stream エラー
以下のエラー画面が表示されました。
まずは接続先、ポート番号、アカウント、パスワードに誤りがないか確認しましたが問題なさそうです。ググッてみましたが、設定を見直すというところで良く分かりませんでした。
そこで、以前にメールボックスにtelnetでアクセスしたことを思い出し、コマンドで直接アクセスできるか試してみました。
openssl s_client -connect imap.gmail.com:993 -crlf ? login test@gmail.com password
参考:telnet(openssl)でIMAPプロトコルを喋ってGmailに命令を出す。 - それマグで!
このコマンドで直接アクセスしても接続出来ませんでした。ということでもしかするとGmail側でブロックされているのかも?という可能性を疑ったところ、「ログイン試行をブロックしました」というメールが届いていました。(早くきづいておけば・・・)
ひとまず接続確認をしたかったので、セキュリティ的にオススメではありませんが「安全性の低いアプリがアカウントにアクセスするのを許可する」(下記参照)設定を行ったところ、コマンドでのアクセス確認ができアプリからも正常にアクセスできました。
参考:安全性の低いアプリがアカウントにアクセスするのを許可する - Google アカウント ヘルプ
このままではセキュリティ的によろしくないので他に方法がないか調べたところ、アプリ用のパスワードを設定する方法がありました。(下記参照)2段階認証を有効にしてアプリ用パスワードを発行し、プログラム内のパスワードをアプリ用パスワードに変更して再度実行して表示されることが確認できました。
検索したメールが正しく表示されない
次に固定文言で検索したメールを表示させたところ、タイトルにその文言が含まれていないメールが表示される問題がありました。
これは、imapの仕組みを良く分かっていなかったことと、imap_searchのリファレンスをしっかり読んでいなかったことが原因でした。コードをコピペして修正するだけではダメですね。
imapではメールにメッセージ番号とUIDの2種類の番号が割り当てられているようで、↑のコードではimap_searchの引数にSE_UIDを指定したためUIDが返されていました。当初、件名を取得するのにimap_headerinfo関数を使用していましたが、これはメッセージ番号をキーに取得する関数なので検索条件に該当しないメールが取得されていました。なので、ヘッダー情報をUIDでも取得できるimap_fetch_overview関数を使うことで解決しました。これでメールを取得できるようになったので、色々と効率化できそうです。
.ssh/configの便利な設定【cloudpack 大阪 BLOG】
管理するサーバ台数が増えてくるとSSHでの接続が手間になりconfigに設定を書くことも多くなると思います。
また、扱うサーバ台数が増えてくるとそもそもconfigに記載したかどうかすらも分からなくなってきます。そこで今回は、「.ssh/config」を利用するときにちょっと便利になる設定を紹介します。
Host名の補完機能
これはHost名を補完してくれる機能です。地味に便利な機能です。
インストール方法
以下のコマンドを実行し補完機能をインストールしてください。
この機能はsshだけでなくserviceコマンドなど他のコマンド実行時においても補完してくれます。
# Mac brew install bash-completion # CentOs yum --enablerepo=epel install -y bash-completion
Host名の一覧表示
Host名の一覧を表示するコマンドを自作しました。補完機能でほぼまかなえますが、Host名の先頭のを覚えていない時に。
以下のコマンドを「.bash_profile」なりに設定してください。
設定方法
alias sshlist="cat ~/.ssh/config |grep ^Host\ |sed -e 's/^Host\ //g'"
使い方
使い方は「sshlist」とするだけでHostの一覧が表示されます。
下記のようなconfig設定があったとして
Host abc_web_01 HostName 111.111.111.001 IdentityFile ~/.ssh/abc_id_rsa1 User ec2-user Host def_web_01 HostName 111.111.111.002 IdentityFile ~/.ssh/def_id_rsa1 User ec2-user
これに対して実行すると
$ sshlist abc_web_01 def_web_01
Host名に「def」を含む一覧を表示
$ sshlist |grep def def_web_01
タイムアウト防止
一定時間操作しなかった場合にタイムアウトすることがありますが、以下の設定をすると
指定時間時間ごとに通信をしてタイムアウトを防いでくれます。
ServerAliveInterval 15
サーバのタイムアウト設定は基本的にはセキュリティを考慮したもののため、
この設定をして回避するのは微妙な気がします。
おまけ
scpコマンドからでも.ssh/configが利用できます。
ファイル受信
scp abc_web_01:/home/user/remotefile.txt /tmp/
ファイル送信
scp localfile.txt abc_web_01:/home/user/
貝塚 2015.09.20
釣行時間:04:00 - 07:00
釣果 :サゴシ 1匹、エソ 1匹
朝の4時過ぎに釣座を確保し釣り開始。タチウオシーズンなのでこの時間でもかなり人が多いですね。かなり肌寒い状況です。まずはワインドでタチウオ狙い。今年はどうでしょうか?若干、風と波があり少しあたりが取り辛い状況でした。ワインドの色を変え、ジグに変えと色々試行して5時半過ぎまで粘りましたが、時合も特になく周りでは隣のえさ釣り師が1匹釣っていたくらいでした。 もちろん自分もヒットなし。本命の青物に期待することにします。
タチウオに見切りを付けて青物タックルに持ち替えて、ジグでキャスト開始。風もおさまってきました。1時間位キャストを続けていましたが、ベイトも見当たらずナブラもないので、今日もボウズかと諦めかけていました。が!6時半前に底付近でフォールでヒット!ヒットの仕方と引きからエソっぽいと思いつつ期待して寄せてみましたが、やっぱりエソでした。残念!せっかくですが、海にお帰りいただきました。引きだけは楽しませてもらいました。ともあれ、活性が上がってきたかと期待してキャストし続けると、中層付近でひったくるような感じでヒット!これは青物確実と思い寄せてみると今季初サゴシ!ヒットパターンは少しテンポの早いワンピッチジャークでした。サゴシを〆てすぐにキャストを開始しましたが、群れはもう去っていったのかこれ単発でした。
今回はリーダーにワイヤーを使ってなかったのですが、針が1つサゴシに切られていました。サゴシの歯は鋭いですね。針を外すときはくれぐれも気をつけてください。もう一つも噛まれてボロボロになっていたので、抜き上げずにネットでランディングして良かったです。