【AWS】CloudWatch

はじめに

タダです。
AWS認定試験勉強のためにCloudWatchのドキュメントを読んだ自分用メモになります。

サービス概要

  • AWSリソースと、AWSで実行されているアプリをリアルタイムでモニタリングするマネージドサービス
  • ClouWatchアラームはユーザーが定義したルールに基づいて通知を送信したり、モニタリングしているリソースに自動的に変更を加えられる
    • EC2のCPU使用率およびディスク読み書きをモニタリングし、増加する負荷を処理する追加のインスタンスを追加のインスタンスを開始できるかをはんだんする
  • EC2のモニタリングタイプ
    • 基本モニタリング:無料、データは5分間隔
    • 詳細モニタリング:追加料金が必要、データは1分間隔
  • アラームのステータス
    • OK : 定義された閾値を下回ってる状態
    • アラーム : 定義された閾値を上回っている状態
    • 不足 : データが不足のため、状態を判定できない
      • データポイント自体が存在しない状態
      • OKとアラームはデータポイントが存在する状態で評価
        • したがって必ずしも不足状態が障害を表すステータスじゃない

専門用語

  • 名前空間

    • CloudWatchメトリクスのコンテナ。 AWS/ Service という命名規則。

      • ex: AWS/EC2、AWS/RDS、AWS/ELB
  • メトリクス
    • CloudWatchに発行された時系列のデータポイントのセット

      • データの粒度は最小が1分〜3分や5分間間隔のものまである
    • メトリクスはリージョンごとに管理される
    • 最大15ヶ月まで保管される
      • 期間が 60 秒 (1 分) のデータポイントは、15 日間使用できる
      • 期間が 300 秒 (5 分) のデータポイントは、63 日間使用できる
      • 期間が 3600 秒 (1 時間) のデータポイントは、455 日 (15 か月) 間使用できる
      • https://aws.amazon.com/jp/cloudwatch/faqs/
  • ディメンション
    • 監視の範囲

他のサービスとの連携

  • SNS

    • CloudWatchアラームを使う時に連携する
  • AutoScaling
    • ユーザー定義のポリシー、ヘルスステータスチェックおよびスケジュールに基づいてEC2を自動的に起動、終了できる
  • CloudTrail
    • CloudTrailログにCloudWatch API実行を記録できる
  • IAM
    • CloudWatchの操作権限を管理する

アクション機能

モニタリングの状態によってアクションを定義する

  • SNS通知
  • EC2アクション
    • EC2 Auto Recovery(対応するインスタンスはC3,4/M3/R3/T2で、EC2-Classicとハードウェア専有インスタンスは未対応)
    • EC2 再起動/停止/起動
  • AutoScaling

CloudWatch Events

AWSリソースの変更を示すシステムイベントのほぼリアルタイムのストリームを、EC2インスタンス、Lambda、SQSキュー、ECSタスク、ステップ関数ステートマシン、SNSトピック、Kinesisストリーム、組み込むターゲットに振り分けるのがCloudWatch Events

専門用語

  • イベント

    • AWSリソースにおける変化で、CloudWatch Eventsのトリガーとなる。
  • ターゲット
    • イベントを処理する
  • ルール
    • 一致した受信イベントを検出し、処理のためにターゲットに振り分ける

他のサービスとの連携

  • CloudTrail

    • CloudWatch Eventsのログをとれる
  • CloudFormation
    • CloudWatch Eventsのルールを作れる
  • Config
    • Config Rulesがトリガーされると、CloudWatch Eventsによってキャプチャできるイベントが生成される
  • Kinesis Stream
  • Lambda

イベントタイプ

  • EBSイベント

    • スナップショット通知
    • ボリューム通知
  • EC2イベント
    • インスタンスの状態変更通知
  • EC2 System Managerイベント
    • RunCommandの実行の通知
    • EC2 Automationステップステータス変更の通知
  • EC2メンテナンスウィンドウイベント
  • ECSイベント
    • ECSコンテナインスタンスの状態変更
    • ECSタスクの状態変更
  • EMRイベント
    • クラスター状態の変更
    • EMRステップ状態の変更
  • AutoScalingイベント
    • EC2インスタンス起動のライフサイクルアクション
    • EC2インスタンスの起動/失敗
  • APIコールイベント
  • CodeDeployイベント
    • デプロイ状態変更通知
  • AWSマネジメントコンソールサインインイベント
  • AWS Healthイベント
  • KMSイベント
  • スケジュールイベント
  • TrustedAdvisorイベント

CloudWatch Logs

  • CloudWatch Logsは、EC2、CloudTrailおよびその他のソースのログファイルの監視、保存、アクセスができる
  • 特徴として、EC2インスタンス/CloudTrailのログをリアルタイムでモニタリングできる他に、ログデータをアーカイブできる

専門用語

  • ログイベント

    • モニタリングされてるアプリまたはリソースによって記録されたアクティビティの記録
    • イベント発生時のタイムスタンプおよび生のイベントメッセージ(UTF-8)
  • ログストリーム
    • 同じリソースを共有する一連のログイベント(ログフォルダのサブフォルダのイメージ)
  • ロググループ
    • ログの出力箇所(ログフォルダのイメージ)
  • メトリクスフィルタ
    • ログイベントから特定の文字列のフィルタリングが可能
  • 保持設定
    • ログの保持期間を設定する

他のサービスとの連携

  • CloudTrail
  • Kinesis Streams
    • ITインフラのログデータ、アプリのログ、SNSメディア・マーケットデータフィード、ウェブのクリックストリームデータの取り込みと処理をリアルタイムでできる
    • CloudWatch Logsに収集されたログをリアルタイムにKinesis Streamに転送可能
  • Lambda
    • ログの出力先がCloudWatch Logs
  • S3
    • ログデータをS3にエクスポートする
    • ログデータはエクスポート出来るようになるまで最大12時間かかる場合ある

参考

続きを読む

Serverless Frameworkで、AWS Lambda function用に作られるCloudWatch Logsの期限を指定する

某所でServerless Frameworkが採用されており、コンソールでCloudWatch Logsをみると、Never Expireのlogsが沢山できていた。
これは真綿で首を絞めるような課金になるので、serverless.yml上でlogRetentionInDaysを設定するやり方を調べた。

$ sls --version
1.11.0

標準リソースの上書き機能を使う

そのまんまのサンプルが案内されていました。

serverless/resources.md | serverless/serverless

You can override the specific CloudFormation resource to apply your own options. For example, if you want to set AWS::Logs::LogGroup retention time to 30 days, override it with above table’s Name Template.

なるほど、標準でつくられるLogGroup(/aws/lambda/{service_name}-{stage}-{function_name})の定義を上書きできるんだ。

上書き用名前指定の書式は、{normalizedFunctionName} + LogGroupとなっている。

例えば次の例,function名をmainとしている場合、MainLogGroupRetentionInDaysを付与してあげればよい。

serverless.yml
---
service: myservice

# ...

functions:
  main:
    handler: lambda_function.lambda_handler

resources:
  Resources:
    MainLogGroup:
      Properties:
        RetentionInDays: "30"

--noDeploy(-n)(1.12.x以降はsls package)でCFnテンプレートだけ作ってみると、ちゃんとそのようになっていることが確認できる。

  "Resources": {
    "MainLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "Properties": {
        "LogGroupName": "/aws/lambda/myservice-development-main",
        "RetentionInDays": "30"
      }
    },

function名に-_が入っている場合、それぞれ予約文字列のDashUnderscoreを使えばよいとのこと。
名前自体がDashだったりするケースは調べていません。

環境(stage)によって期限を変更したい場合

例えばcustom属性を使って、stage名で引っ張ってくれば使い分けができますね。

serverless.yml
custom:
  logRetentionInDays:
    development: "14"  # stage[development]は14日
    production: "90"   # stage[production]は90日
    default: "3"       # stageが見つからなかったらこれにfallbackするために設定

resources:
  Resources:
    MainLogGroup:
      Properties:
        RetentionInDays: ${self:custom.logRetentionInDays.${opt:stage, self:provider.stage}, self:custom.logRetentionInDays.default}

デフォルトにfallbackするかどうかはお好みかな。

続きを読む

new relic で 一定期間が経過したレコードを削除する

new relic で “server not responding” になったあと、一定期間が経過したレコードを削除する

AWS EC2 で、インスタンスが Auto Scaling でterminateされた後も new relic 側でレコードが残り続けるので、Lambdaを使って定期的に削除するようにしました。

  • Lambda のランタイムは Node.js 6.10 を使用
  • 3秒以上かかるとtimeoutしてしまうので、timeoutの設定を10秒に増やしている

code

  • やっていることは、npm install moment request をして、curl的な処理をしているだけ
  • Lambdaにアップロードするzipには、メインのjsファイルと node_modules ディレクトリを格納
  • トリガは CloudWatch events に、週1で実行するルール追加(毎日とかでも良いかも)

index.js


var request = require('request');
var moment = require('moment');

var expireDays = process.env.expireDays || 7;
var apiKey = '【new relic で発行されたAPI KEY】';
var options = {
    url: 'https://api.newrelic.com/v2/servers.json',
    headers: {
        'X-Api-Key': apiKey
    }
};
var options_del = {
    url: 'https://api.newrelic.com/v2/servers/%s.json', // 実行時に上書き
    method: 'DELETE',
    headers: {
        'X-Api-Key': apiKey
    }
};

var now = moment();

function callback(error, response, body) {
    if (!error && response.statusCode == 200) {
        var info = JSON.parse(body);
        info.servers.forEach(function(item, index, arr) {
             if (item.reporting == false) {
                var m = moment(item.last_reported_at);
                if (now.diff(m, 'days') >= expireDays) {
                    // send DETETE request
                    console.log('*** deleting not reporting server from newrelic ***')
                    console.log(item);
                    options_del['url'] = 'https://api.newrelic.com/v2/servers/' + item.id + '.json';
                    // console.log(options_del);
                    request(options_del, callback_del);
                }
             }
        });
    } else {
        console.log('*** execute FAILED ***');
        console.log(body);
    }
}

function callback_del(error, response, body) {
    if (!error && response.statusCode == 200) {
        var info = JSON.parse(body);
        // console.log(info);
        var id = info.server.id;
        console.log('*** execute DELETE SUCCEEDED:' + id + ' ***');
    } else {
        console.log('*** execute DELETE FAILED ***');
        console.log(body);
    }
}

exports.handler = (event, context, _callback) => {
    console.log('*** start: deleting not reporting server from newrelic  more than '+ expireDays + ' days ago ***');
    request(options, callback);
    _callback(null, 'all done: delete records more than '+ expireDays + ' days ago.');
};

対象の日数は環境変数で変えられる

process.env.* で環境変数を参照できるので、これを使って。
expireDays で指定できる(指定なしの場合は7日で処理)ように。
あんまり変える需要はないかもですが

スクリーンショット 2017-04-28 17.27.27.png

see also

Feature Idea: Auto Delete Non-Reporting Servers – Feature Ideas / Feature Ideas: APM/UI – New Relic Online Technical Community

最初はこのワンライナーを、気がついたときに手動で行ってました。

続きを読む

AWS Lambdaをつかって、CloudWatchが監視した値を超えたら、slackに通知する仕組みをつくろう on Python

はじめに

社内で「らむだが〜らむだが」と話していて、元C#の僕からするとプログラミング言語の何かかな?と思っていましたが、違いましたね。(C#にはラムダ式というのがある。)

社内では、AWS Lambda上にnode.jsランタイムを配置して、slack通知していましたが、nodeのversionが古くてそろそろサポートやめるよ、と言われたので、Pythonに切り替えることにしました。(nodeより、Pythonのほうが知っている人が社内にいたので)

AWS Lambdaでは、よくあるような処理はテンプレートとして用意されているので、結構簡単にできます。手順をまとめてみます。

設定手順

おおまかに下記の手順でやります。

  1. Lambda関数作成
  2. SNSの作成とLambdaとの連携
  3. EC2とSNSの連携
  4. コーディング
  5. テスト

構成図

最終的にこんな感じになります。

aws.png

※ 構成図はdraw.ioを使って書いてみました

手順

1. Lambda関数の作成

今回のメインとなるLambda関数を作成しておきます。指示に従っていけば簡単に作れます。
作り直しや削除も簡単にできるのでご安心を。

サービス一覧から、Lambdaを選択。

①.png

Lambda関数の作成を開始します。

②.png

フィルターに、slackと入力するとslackの雛形ができますが、今回はブランクで作成します。

③.png

トリガーはあとで設定するので、からのままにしておきます。

④.png

2. SNSの作成とLambdaとの連携

SNS = Simple Notification Service。何らかの処理や状態変更を受取、メッセージにして通知してくれるサービスです。
メールや今回のようなLamdaへメッセージを通知できます。
サービス自体は、2017/04/06現在翻訳はされていませんが、簡単な英語なので問題ないと思います。

SNSを作成し、subscriptionをくっつけます。

Subscriptionsを作成する。

AWS_SNS.png

先程作成したLambda関数を選択します

AWS_SNS.png

3. EC2(Cloud Watch)とSNSの連携

EC2の画面から、アラームを設定します。

EC2_Management_Console.png

4. コーディング

各種設定を行います

設定するものは下記です。
* Lambda関数名
* コード
* 環境変数
* 関数ハンドラーとロール
etc

今回は下記のように設定してみました。

Lambda_Management_Console.png

@コード

lambda_handler.py
from __future__ import print_function

import boto3
import json
import logging
import os

from urllib2 import Request, urlopen, URLError, HTTPError

# slackの設定
SLACK_CHANNEL = os.environ['SLACK_CHANNEL']
HOOK_URL      = os.environ['HOOK_URL']

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


def lambda_handler(event, context):
    logger.info("Event: " + str(event))
    message = json.loads(event['Records'][0]['Sns']['Message'])
    logger.info("Message: " + str(message))

    alarm_name  = message['AlarmName']
    description = message['AlarmDescription']
    new_state   = message['NewStateValue']
    reason      = message['NewStateReason']

    if new_state == 'OK':
        emoji = ":+1:"
    elif new_state == 'ALARM':
        emoji = ":exclamation:"

    slack_message = {
        'channel': SLACK_CHANNEL,
        'text': "*%s %s: %s*n %sn %s" % (emoji, new_state, alarm_name, description, reason)
    }

    req = Request(HOOK_URL, json.dumps(slack_message))
    try:
        response = urlopen(req)
        response.read()
        logger.info("Message posted to %s", slack_message['channel'])
    except HTTPError as e:
        logger.error("Request failed: %d %s", e.code, e.reason)
    except URLError as e:
        logger.error("Server connection failed: %s", e.reason)

@環境変数+あるふぁ

Lambda_Management_Console_1.png

5. テスト

Lambdaの画面から、下記を実施します。

  1. アクション→テストイベントの設定→テストイベント入力
  2. 保存してテスト

手前味噌ですが、テストデータはこちらを参考にしていただければと。
* 参考:AWS Lambdaをつかった、CloudWatch監視 -> slack通知の際のテストデータ

実行結果

sandboxチャンネルに無事通知が飛びました。絵文字も入っていますね。

Slack_-_eversense.png

おわりに

簡単なプログラムでslack通知が実現できました。適切にメトリクス設定して、快適なAWSライフを。(通知しても、見をとしてしまったりしたら意味ないけれど…)

なお、Lambdaからは同一リージョンで作成したSNSしか設定できないようなので、リージョンをまたぐ場合は各リージョンのSNSをからLambdaを選択してあげます。

当初、Lambdaの環境変数を利用しようと思いましたが、base64周りでエラーが出てしまったので使えていません。これ使えると、Lambda関数が簡単に使いまわせるので次の機会にはきちんと対応しようと思います。

続きを読む

初心者が一攫千金を目指してBitcoin自動取引botを作るよ! その4【サーバレス化】

サーバレスアーキテクチャ.png
今回は前回作った「bitcoin自動取引Botもどき」を
AWSのLambdaを利用して今流行のサーバレス構成にしました!
クラウドにあるので、私のPCが止まっても半永久的に動き続けます。まさにBotですね!

クラウドの構成について、
サーバレスを選んだ理由は利用料がすごく安い + 管理が楽と聞いたからです。[参考]

Picture67.png

実際に今回使ったサービスを見てみると、、、
下記の構成で月額1$!!
Presentation4.png

試算

対象サービス 見積もり料金
Lambda $0
DynamoDB $0
cloudwatch $1.00?

Lambda
・Lambda では 1 か月に 1,000,000 件の無料リクエストおよび 400,000 GB-秒のコンピューティング時間が無料利用枠となっています。[参考]

▶1分に1回1ヶ月実行すると、1 x 60m x 24h x 31day = 44,640回なので余裕

DynamoDB
・AWS 無料利用枠には、Amazon DynamoDB の 25 GB のストレージ、2 億リクエストまでの処理が含まれます。[参考]

2 億…( ゜Д゜)それは個人で超えられるのか・・・
(1 時間あたりの書き込み、読み込みキャパシティーにもよるらしいのですが、
 今回はそこまで激しい使い方をしないので気にしません。)

cloudwatch
: 作成された100万回のカスタムイベントに対して$ 1.00[参考]

▶1分に1回1ヶ月実行すると、1 x 60m x 24h x 31day = 44,640回なので$ 1.00?

▶1週間くらい動かしてみて、一向に増える気配がない…
  1ヶ月立って0円だったら0円に直します。

仕様

動きは相変わらず、
安くなったら買う、そして、高くなったら売る!みたいな愚直なBotのままです。
早く直したいですが、いいアルゴリズムを思いつきません。。。。

①前回の取引額をDBから取ってくる(この金額が判断の基準となる)
②前回の取引が残っていた場合キャンセルする
②Bitcoinを持っていてかつ値上がりした場合、売る!(そして、取引額をDBに保存する)
③日本円を持っていてかつ値下がりした場合、買う!(そして、取引額をDBに保存する)

実装

上記アーキテクチャ図のAWS内の3つを実装します。(この3つを設定するだけで動きます!!)
aaaaa.PNG

  1. DynamoDB

    • テーブル作成
  2. Lambda
    • コーディング
    • コード実行環境へデプロイ
  3. CloudWach
    • スケジューラに上記で設定したLambdaをセットする

1.DynamoDB

Picture1.png

今回使用するコード実行環境のLamdbaは逐次実行で値を保存できないので、
データベースを使用します。

下記2つのテーブルを作成します。

①last_transaction_price – 取引した価格を記録する
②sequence - 成立した取引回数を記録する

①last_transaction_price – 取引した価格を記録する

①-1.[テーブルを作成]より、下記テーブルを作成

・テーブル名:last_transaction_pric
・プライマリキー:trade_number (数値型)

①-2.[項目の作成]よりレコード登録(最初の1件だけ手動で登録する)
キャプチャ3.PNG

※「last_transaction_price」には前回の取引額を入れる
 (覚えていないor取引したことない場合は、現在価格を入れる)

②sequence - 成立した取引回数を記録する

①-1.[テーブルを作成]より、下記テーブルを作成

・テーブル名:sequence
・プライマリキー:name (文字列)

①-2.[項目の作成]よりレコード登録(最初の1件だけ手動で登録する)
キャプチャ4.PNG

2.Lambda

Picture2.png

①コーディング
②ロール作成
③Lambda作成
④デプロイ
⑤テスト

①コーディング

構成
今回、色々増えています。
[]・・・フォルダ

[zaif]
 |- main.py
 |- [config] – zaif_keys.json - zaifのkey,secretを保管
 |- [aws] – dynamodb_utils.py - awsのDynamoDBのAPIを使いやすいようにまとめたクラス
 |- [define] – common_define.py - (主にDynamoDBで使う)文字列を定義してまとめたクラス
 |- [exception] – error.py - 独自エラーをまとめたクラス郡
 |- …<略> - その1でインストールしているので

※他で使うPythonファイルを作るときは、同じフォルダに「__init__.py」を入れないと読み込めません。(ファイルの中身は空で問題ないです)
(上記の構成では記載を割愛しています)
例:キャプチャ.PNG

■[config] – zaif_keys.json
初心者が一攫千金を目指してBitcoin自動取引botを作るよ! その3参照

■[aws] – dynamodb_utils.py
・DynamoDBを操作する基本的なメソッドをまとめてます。
main.pyから引用して使用しています。

dynamodb_utils.py
# coding:utf-8

import boto3
from exception.error import *  # 独自定義のエラーを定義したクラス


class DynamoDBUtils:
    def __init__(self):
        self.dynamodb = boto3.resource('dynamodb')

    # 指定したテーブルのレコードをすべて取得(上限 1 MB )
    def scan(self, table_name):
        if isinstance(table_name, str) is False or isinstance(table_name, unicode):
            raise IllegalArgumentError('item is not str/unicode')

        table = self.dynamodb.Table(table_name)
        response = table.scan()

        print('▼[trace][DynamoDB access]scan')
        print('table_name: ' + table_name)
        print('response:')
        print(str(response))

        return response

    # テーブルを指定して受け取ったjson(item)をレコードとして挿入または上書き
    def put_item(self, table_name, item):
        if isinstance(table_name, str) is False or isinstance(table_name, unicode):
            raise IllegalArgumentError('item is not str/unicode')
        if isinstance(item, dict) is False:
            raise IllegalArgumentError('item is not dict')

        table = self.dynamodb.Table(table_name)
        response = table.put_item(
            Item=item
        )

        print('▼[trace][DynamoDB access]put_item')
        print('table_name: ' + table_name)
        print('put_item: ' + str(item))
        print('response:')
        print(str(response))

        return response

    # テーブルとプライマリーキーを指定してレコードを取得
    def get_item_by_partition_key(self, table_name, partition_key, partition_key_value):
        if isinstance(table_name, str) is False or isinstance(table_name, unicode):
            raise IllegalArgumentError('item is not str/unicode')

        table = self.dynamodb.Table(table_name)
        response = table.get_item(
            Key={partition_key: partition_key_value}
        )

        print('▼[trace][DynamoDB access]get_item_by_partition_key')
        print('table_name: ' + table_name)
        print('partition_key: ' + str(partition_key) + ', partition_key_value: ' + str(partition_key_value))
        print('response:')
        print(str(response))

        return response

    # テーブルとプライマリーキー・ソートキーを指定してレコードを取得
    def get_item_by_partition_key_and_sort_key(self, table_name, partition_key, partition_key_value,
                                               sort_key, sort_key_value):
        if isinstance(table_name, str) is False or isinstance(table_name, unicode):
            raise IllegalArgumentError('item is not str/unicode')

        table = self.dynamodb.Table(table_name)
        response = table.get_item(
            Key={partition_key: partition_key_value,
                 sort_key: sort_key_value}
        )

        print('▼[trace][DynamoDB access]get_item_by_partition_key_and_sort_key')
        print('table_name: ' + table_name)
        print('partition_key: ' + str(partition_key) + ', partition_key_value: ' + str(partition_key_value))
        print('sort_key: ' + str(sort_key) + ', sort_key_value: ' + str(sort_key_value))
        print('response:')
        print(str(response))

        return response

    # テーブルとプライマリーキーと更新項目を指定してレコードを更新
    def update_by_partition_key(self, table_name, partition_key, partition_key_value, update_key, update_value):
        if isinstance(table_name, str) is False or isinstance(table_name, unicode):
            raise IllegalArgumentError('item is not str/unicode')

        table = self.dynamodb.Table(table_name)
        response = table.update_item(
            Key={
                partition_key: partition_key_value
            },
            UpdateExpression='SET ' + update_key + ' = :uk',
            ExpressionAttributeValues={
                ':uk': update_value
            }
        )

        print('▼[trace][DynamoDB access]update_by_partition_key')
        print('table_name: ' + table_name)
        print('partition_key: ' + str(partition_key) + ', partition_key_value: ' + str(partition_key_value))
        print('update_key: ' + str(update_key) + ', update_value: ' + str(update_value))
        print('response:')
        print(str(response))

        return response

    # テーブルとプライマリーキーを指定してレコードを削除
    def delete_by_partition_key(self, table_name, partition_key, partition_key_value):
        if isinstance(table_name, str) is False or isinstance(table_name, unicode):
            raise IllegalArgumentError('item is not str/unicode')

        table = self.dynamodb.Table(table_name)
        response = table.delete_item(
            Key={partition_key: partition_key_value}
        )

        print('▼[trace][DynamoDB access]delete_by_partition_key')
        print('table_name: ' + table_name)
        print('partition_key: ' + str(partition_key) + ', partition_key_value: ' + str(partition_key_value))
        print('response:')
        print(str(response))

        return response

■[define] – common_define.py
・主にDynamoDBで使う文字列を定義してまとめたクラスです。
・あとからカラム名等を変更したくなったときに楽できるようにまとめて管理します。

common_define.py
# -*- coding: utf-8 -*-
class CommonDefine:
    def __init__(self):
        pass

    # dynamoDB table name
    LAST_TRANSACTION_TABLE = "last_transaction_price"
    SEQUENCE = "sequence"

    # dynamoDB key name
    TRADE_NUMBER = "trade_number"
    LAST_TRANSACTION_PRICE = "last_transaction_price"
    SEQUENCE_NAME = "name"
    COUNT_NUMBER = "count_number"
    ORDER_ID = "order_id"

    # other
    TRADE_COUNT = "trade_count"

■[exception] – error.py
・独自エラーをまとめたクラス郡です。
・出したいエラー名が標準で用意されていないときに使用します。

error.py
# -*- coding: utf-8 -*-
class ResourceNotFoundError(Exception):
    # 取得対象のデータが存在しない場合の例外

    def __init__(self, message):
        self.message = message

    def __str__(self):
        return "Requested resource " + self.message + " not found."


class IllegalArgumentError(Exception):
    # 入力された引数が不正な場合の例外

    def __init__(self, message):
        self.message = message

    def __str__(self):
        return "Illegal argument (" + self.message + ")"

②ロール作成

・Lambdaを実行する上で必要な権限を割り振ったロールを作成します。
キャプチャ33.PNG

1.IAM ▶ ロール ▶ 「新しいロールの作成」を選択
2.ロールタイプの選択: AWS Lambda
3.ポリシーのアタッチ:AmazonDynamoDBFullAccess , AWSLambdaFullAccess
4.ロール名を入力して、「ロールの作成」

③Lambda作成

・実際にコードを実行するLambdaを作ります。
キャプチャaaaaa.PNG

1.Lambda ▶ 関数 ▶ 「Lambda関数の作成」を選択
2.左側の「関数の設定」タブをクリック
3.名前(なんでもよい)を入力する
  ランタイム:python2.7を選択
  既存のロール
:「②ロール作成」で作成したロールを選択
  詳細設定 ▶ タイムアウト:10秒へ値を増やす
4.「次へ」 ▶ 「作成」

④デプロイ

③Lambda作成で作成したLambdaにコードを乗せます。

1.「設定」タブを開き、ハンドラをmain.lambda_handlerへ修正
  (main.pyを呼び出して実行するという意味)
キャプチャaws.PNG
2.①コーディングで作成したコード郡をzipで固める
  ※[zaif]-[zaif]-main.pyとならないように注意!(正:[zaif]-main.py)
3.「コード」タブを選択し、ファイルをアップロードする
キャプチqャ.PNG

⑤テスト

「保存してテスト」ボタンを押し、実行結果: 成功となればOK!
aaaaキャプチャ.PNG

3.CloudWach

Picture3.png
・上記で作成したLambdaが定期実行されるように設定します。
キャプチ.PNG

1.CloudWatch ▶ ルール ▶ ルールの作成
2.「スケジュール」▶ 値を1へ変更
  「ターゲットの追加」▶機能上記で作成したLambda関数*
キャaプチャ.PNG
3.「詳細の設定」▶名前*を入力 ▶ 「ルールの設定」

おわりに

今回は手順が長かったですね。。。お疲れ様でした!
今回の設定で、半永久的にBitCoinを取引するBotができましたね!
さあ、あとは儲けるロジックを考えるだけ!!(๑•̀ㅂ•́)و✧
試行錯誤していきます٩(•౪• ٩)

引用

けものフレンズ ロゴジェネレータ

▶世の中には素晴らしいものを作る方がいらっしゃいますね。
 ありがたく利用させていただきました。ありがとうございました。

続きを読む

ECSにZabbix環境をデプロイする【cloudpack大阪ブログ】

cloudpack大阪の佐々木です。
Zabbix環境をコンテナとしてECS上につくってみたので、まとめときます。

デプロイ方法

ecs-cliとdocker-compose使ってデプロイします。

基本的なやり方はこちら
http://qiita.com/taishin/items/076a7699c787da68e396

docker-coposeはv2が使えるらしいので、今回はv2で記述しています。
http://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/cmd-ecs-cli-compose.html

Amazon ECS CLI の最新バージョンでは、Docker 構成ファイル構文のバージョン 1 と 2 がサポートされています。

v2のリファレンス
https://docs.docker.com/compose/compose-file/compose-file-v2/

docker-compose.yml

docker-compose.yml
version: '2'

services:
  mysql-server:
    image: mysql
    mem_limit: 268435456
    environment:
      - MYSQL_DATABASE=zabbix
      - MYSQL_USER=zabbix
      - MYSQL_PASSWORD=zabbix
      - MYSQL_ROOT_PASSWORD=zabbix
    volumes:
        - /ecs/zabbix_mysql:/var/lib/mysql
    logging:
      driver: awslogs
      options:
        awslogs-group: "ECS-zabbix"
        awslogs-region: "us-east-1"
        awslogs-stream-prefix: "zabbix"

  zabbix-server-mysql:
    image: zabbix/zabbix-server-mysql:ubuntu-3.2-latest
    mem_limit: 134217728
    environment:
      - DB_SERVER_HOST=mysql-server
      - MYSQL_USER=zabbix
      - MYSQL_PASSWORD=zabbix
      - MYSQL_DATABASE=zabbix
    logging:
      driver: awslogs
      options:
        awslogs-group: "ECS-zabbix"
        awslogs-region: "us-east-1"
        awslogs-stream-prefix: "zabbix"
    links:
      - mysql-server
    ports:
      - 10050:10050

  zabbix-web-nginx-mysql:
    image: taishin/zabbix-web:latest
    mem_limit: 134217728
    environment:
      - DB_SERVER_HOST=mysql-server
      - ZBX_SERVER_HOST=zabbix-server-mysql
      - MYSQL_USER=zabbix
      - MYSQL_PASSWORD=zabbix
      - MYSQL_DATABASE=zabbix
      - TZ=Asia/Tokyo
      - COMPOSE_HTTP_TIMEOUT=200
    logging:
      driver: awslogs
      options:
        awslogs-group: "ECS-zabbix"
        awslogs-region: "us-east-1"
        awslogs-stream-prefix: "zabbix"
    links:
      - mysql-server
      - zabbix-server-mysql
    ports:
      - 8090:80

順番に説明します。

コンテナ

下記の3つで構成するようにしました。
– mysql
– zabbix-server
– zabbix-web

Zabbixのコンテナはこちらのを使用します。
https://hub.docker.com/u/zabbix/

zabbix-webはそのまま使うとグラフの日本語が文字化けしますので、日本語フォントを入れて、Buildします。
Dokcerfileは下記のようになります。

FROM zabbix/zabbix-web-nginx-mysql:ubuntu-3.2-latest
RUN apt-get update -y && apt-get install -y fonts-ipafont
RUN ln -s /usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf /usr/share/zabbix/fonts/ipagp.ttf
RUN sed -i -e 's/graphfont/ipagp/g' /usr/share/zabbix/include/defines.inc.php

ecs-cliで使うdocker-composeはbuildに対応していないので、あらかじめビルドしてどこかにアップしておく必要があります。
http://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/cmd-ecs-cli-compose.html

重要
build ディレクティブは現在サポートされていません。

今回は自分のdockerhubにアップしておきました。

メモリ制限

メモリ制限は下記に記述でハード制限ができます。ソフト制限はできないっぽいです。

    mem_limit: 268435456

永続化データ

MySQLのデータは永続化したいので、ボリュームを作成します。
下記の記述でホストOSの/etc/zabbix_mysqlがコンテナの/var/lib/mysqlにマウントされます。

    volumes:
        - /ecs/zabbix_mysql:/var/lib/mysql

マネジメントコンソールで見るとこんな感じです。

Kobito.NcjJB2.png

Kobito.dg0AbK.png

amazon-ecs-optimizedのAMIを使う場合は下記も注意です。
http://qiita.com/taishin/items/cff9c3212d0f697653e4

Log

コンテナが出力するログはCloudwatch Logsに転送します。
http://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/using_awslogs.html

docker-compose v1のときは

log_driver: awslogs

こんな記述でしたが、v2では下記のような表記になります。

    logging:
      driver: awslogs
      options:
        awslogs-group: "ECS-zabbix"
        awslogs-region: "us-east-1"
        awslogs-stream-prefix: "zabbix"

ロググループはあらかじめ作成しておく必要があります。

ストリームプレフィックスですが、

  • awslogs-stream-prefix をつけない場合

Kobito.V9TWgn.png

コンテナごとに乱数のストリーム名になります。

  • awslogs-stream-prefix をつけた場合
    Kobito.GSxPru.png

ストリーム名が下記のフォーマットになるので、付けておいた方が分かりやすいです。

prefix-name/container-name/ecs-task-id

ポート

ホストの8090番ポートをコンテナの80番ポートに転送しています。

    ports:
      - 8090:80

ちなみに固定ポートではなく、ダイナミックポートにする場合は下記のように書きます。

    ports:
      - :80

デプロイ

ecs-cli compose service up です。

$ ecs-cli compose service up                                                  [10:47:02]
WARN[0000] Skipping unsupported YAML option...           option name=networks
WARN[0000] Skipping unsupported YAML option for service...  option name=networks service name=zabbix-server-mysql
WARN[0000] Skipping unsupported YAML option for service...  option name=networks service name=zabbix-web-nginx-mysql
WARN[0000] Skipping unsupported YAML option for service...  option name=networks service name=mysql-server
INFO[0001] Using ECS task definition                     TaskDefinition="ecscompose-zabbix-docker:40"
INFO[0002] Created an ECS service                        service=ecscompose-service-zabbix-docker taskDefinition="ecscompose-zabbix-docker:40"
INFO[0002] Updated ECS service successfully              desiredCount=1 serviceName=ecscompose-service-zabbix-docker
INFO[0003] Describe ECS Service status                   desiredCount=1 runningCount=0 serviceName=ecscompose-service-zabbix-docker
INFO[0018] ECS Service has reached a stable state        desiredCount=1 runningCount=1 serviceName=ecscompose-service-zabbix-docker

マネージメントコンソールでタスク定義をいじるようり、docker-composeの方がだいぶ楽ですね。

続きを読む

Mastodonのrake mastodon:dailyをLambdaで定期実行する

ドキュメント
https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Production-guide.md
これやらないとこういうことが起きるので大事。
http://cryks.hateblo.jp/entry/2017/04/18/125351
まさに自分のサーバーで起きて大変だった…。

環境

前回のAWS EC2 Container Service
http://qiita.com/kawax/items/5307b58e549dd9cc3928

これでcronはどうやるんだろうと調べたけど結構簡単だった。

ECSタスク

aws-daily.yml

version: '2'
services:
  web:
    image: {image}
    env_file: .env.production
    command: bundle exec rake mastodon:daily
    mem_limit: 536870912
    ports:
      - "3000"
ecs-cli compose -f aws-daily.yml --project-name mastodon-daily create

Lambda

ランタイム:Node.js 6.10
ブランク関数で新規に作っていく。

トリガーで CloudWatch イベント - スケジュール を選択。
ルール名やルールの説明は適当なものを。
スケジュール式は rate(1 day)

ロールはカスタムロールの作成から新規ロールを作った後で
管理ポリシーAmazonEC2ContainerServiceFullAccessを付ける。
インラインポリシーoneClick_lambda_basic_executionも付いてるはず。
この辺は後でLambda 関数が実行できるポリシーを付ける。

コード

clusterとtaskDefinitionを自分のものに書き換える。

var AWS = require('aws-sdk');
var ecs = new AWS.ECS();

exports.handler = (event, context, callback) => {
    var params = {
        cluster: "mastodon", 
        taskDefinition: "ecscompose-mastodon-daily"
    };
    ecs.runTask(params, function(err, data) {
        if (err) console.log(err, err.stack); // an error occurred
        else     console.log(data);           // successful response
    });
};

テストしてみてエラーが出てなければトリガーを有効化して終わり。
エラーが出てる場合はロールを確認する。

続きを読む