現在のインスタンス料金を取得する script: RDS 編

http://qiita.com/bells17/items/5326d11edc6acc4feea2
の RDS 版です

注意点として取得するデータのエンジンを Aurora に絞ってます

rds.rb
require 'json'
require 'bigdecimal'

results = {}
json_data = open(ARGV[0]) {|io| JSON.load(io) }

# product 情報を取得
json_data['products'].keys.each do |skuNo|
    product = json_data['products'][skuNo]

    if (product['productFamily'] == 'Database Instance' and
          product['attributes']['locationType'] == 'AWS Region' and
          product['attributes']['location'] == 'Asia Pacific (Tokyo)' and
          product['attributes']['databaseEngine'] == 'Amazon Aurora') # Aurora だけに絞ってます (エンジンが mysql か postgresql かは無いっぽい??)

        results[product['sku']] = {
            sku: product['sku'],
            location: product['attributes']['location'],
            instanceType: product['attributes']['instanceType'],
            instanceFamily: product['attributes']['instanceFamily'],
            vcpu: product['attributes']['vcpu'],
            physicalProcessor: product['attributes']['physicalProcessor'],
            clockSpeed: product['attributes']['clockSpeed'],
            memory: product['attributes']['memory'],
            networkPerformance: product['attributes']['networkPerformance'],
            currentGeneration: product['attributes']['currentGeneration'],
            price_unit: 'USD'
        }

    end
end


# price

# on demand
json_data['terms']['OnDemand'].keys.each do |skuNo|
    if (results[skuNo])
        results[skuNo][:price_per_hour] = Proc.new {
            skuTerm = json_data['terms']['OnDemand'][skuNo][json_data['terms']['OnDemand'][skuNo].keys[0]]
            priceInfo = skuTerm['priceDimensions'][skuTerm['priceDimensions'].keys[0]]
            BigDecimal(priceInfo['pricePerUnit']['USD']).floor(2).to_f.to_s
        }.call
        results[skuNo][:price_per_day] = (BigDecimal(results[skuNo][:price_per_hour]) * BigDecimal("24")).floor(2).to_f.to_s
        results[skuNo][:price_per_month] = (BigDecimal(results[skuNo][:price_per_day]) * BigDecimal("30")).floor(2).to_f.to_s
    end
end

## reserved 
json_data['terms']['Reserved'].keys.each do |skuNo|
    if (results[skuNo])

        plans = json_data['terms']['Reserved'][skuNo].values.select do |plan|
            plan['termAttributes']['PurchaseOption'] == "All Upfront" # "All Upfront" のものだけ取得したい
        end

        results[skuNo][:price_reserved_1year_purchased_all_upfront] = plans.find { |plan|
            plan['termAttributes']['LeaseContractLength'] == '1yr'
        }['priceDimensions'].values.find {|priceDimension|
            priceDimension['description'] == "Upfront Fee"
        }['pricePerUnit']['USD']

        results[skuNo][:price_reserved_3year_purchased_all_upfront] = plans.find { |plan|
            plan['termAttributes']['LeaseContractLength'] == '3yr'
        }['priceDimensions'].values.find {|priceDimension|
            priceDimension['description'] == "Upfront Fee"
        }['pricePerUnit']['USD']

    end
end

# sort
sorted_result = {}
results.values.each do |row|
    sorted_result[row[:currentGeneration]] ||= {}
    sorted_result[row[:currentGeneration]][row[:instanceFamily]] ||= []
    sorted_result[row[:currentGeneration]][row[:instanceFamily]].push row
end

results = []
['Yes', 'No'].each do |currentGeneration| # 現行世代のものから並べる
    next unless sorted_result[currentGeneration]
    sorted_result[currentGeneration].keys.sort.each do |instanceFamily| # インスタンスファミリー毎に並べる
        results.concat sorted_result[currentGeneration][instanceFamily].sort_by { |row| row[:price_per_hour] }
    end
end

p results.to_json

上記を保存して以下のように実行する

curl https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonRDS/current/index.json > price-AmazonRDS.json
ruby rds.rb price-AmazonRDS.json | sed -e s/^"// | sed -e s/"$// | sed -e 's/\"/"/g' | jq .

以下のような結果が取れる

[
  {
    "sku": "H7JQN46Z6VDZ3K5V",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "db.t2.small",
    "instanceFamily": "General purpose",
    "vcpu": "1",
    "physicalProcessor": "Intel Xeon Family",
    "clockSpeed": "Up to 3.3 GHz",
    "memory": "2 GiB",
    "networkPerformance": "Low to Moderate",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "0.06",
    "price_per_day": "1.44",
    "price_per_month": "43.2",
    "price_reserved_1year_purchased_all_upfront": "403",
    "price_reserved_3year_purchased_all_upfront": "776"
  },
  {
    "sku": "MK8ETWDCPSK52PEV",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "db.t2.medium",
    "instanceFamily": "General purpose",
    "vcpu": "2",
    "physicalProcessor": "Intel Xeon Family",
    "clockSpeed": "Up to 3.3 GHz",
    "memory": "4 GiB",
    "networkPerformance": "Low to Moderate",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "0.12",
    "price_per_day": "2.88",
    "price_per_month": "86.4",
    "price_reserved_1year_purchased_all_upfront": "792",
    "price_reserved_3year_purchased_all_upfront": "1530"
  },
  {
    "sku": "8Z6GS5F6NKX37Q5E",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "db.r3.large",
    "instanceFamily": "Memory optimized",
    "vcpu": "2",
    "physicalProcessor": "Intel Xeon E5-2670 v2 (Ivy Bridge)",
    "clockSpeed": "2.5 GHz",
    "memory": "15.25 GiB",
    "networkPerformance": "Moderate",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "0.35",
    "price_per_day": "8.4",
    "price_per_month": "252.0",
    "price_reserved_1year_purchased_all_upfront": "1704",
    "price_reserved_3year_purchased_all_upfront": "3433"
  },
  {
    "sku": "PQP78BGE4C2HXDQF",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "db.r3.xlarge",
    "instanceFamily": "Memory optimized",
    "vcpu": "4",
    "physicalProcessor": "Intel Xeon E5-2670 v2 (Ivy Bridge)",
    "clockSpeed": "2.5 GHz",
    "memory": "30.5 GiB",
    "networkPerformance": "Moderate",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "0.7",
    "price_per_day": "16.8",
    "price_per_month": "504.0",
    "price_reserved_1year_purchased_all_upfront": "3408",
    "price_reserved_3year_purchased_all_upfront": "6867"
  },
  {
    "sku": "2WTMTR9HDDT7AA73",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "db.r3.2xlarge",
    "instanceFamily": "Memory optimized",
    "vcpu": "8",
    "physicalProcessor": "Intel Xeon E5-2670 v2 (Ivy Bridge)",
    "clockSpeed": "2.5 GHz",
    "memory": "61 GiB",
    "networkPerformance": "High",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "1.4",
    "price_per_day": "33.6",
    "price_per_month": "1008.0",
    "price_reserved_1year_purchased_all_upfront": "6815",
    "price_reserved_3year_purchased_all_upfront": "13733"
  },
  {
    "sku": "VRNJP9SPPRH2KM8M",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "db.r3.4xlarge",
    "instanceFamily": "Memory optimized",
    "vcpu": "16",
    "physicalProcessor": "Intel Xeon E5-2670 v2 (Ivy Bridge)",
    "clockSpeed": "2.5 GHz",
    "memory": "122 GiB",
    "networkPerformance": "High",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "2.8",
    "price_per_day": "67.2",
    "price_per_month": "2016.0",
    "price_reserved_1year_purchased_all_upfront": "13631",
    "price_reserved_3year_purchased_all_upfront": "27466"
  },
  {
    "sku": "NC3BZ293ZJFBVUT5",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "db.r3.8xlarge",
    "instanceFamily": "Memory optimized",
    "vcpu": "32",
    "physicalProcessor": "Intel Xeon E5-2670 v2 (Ivy Bridge)",
    "clockSpeed": "2.5 GHz",
    "memory": "244 GiB",
    "networkPerformance": "10 Gigabit",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "5.6",
    "price_per_day": "134.4",
    "price_per_month": "4032.0",
    "price_reserved_1year_purchased_all_upfront": "27261",
    "price_reserved_3year_purchased_all_upfront": "54932"
  }
]

続きを読む

Let’s EncryptでSSLの設定〜自動更新

最近ちょくちょく使う機会が増えたのでメモ。
EC2、nginxを使用する前提です。

ダウンロード

$ sudo curl https://dl.eff.org/certbot-auto -o /usr/bin/certbot-auto
$ sudo chmod 700 /usr/bin/certbot-auto

証明書と鍵の発行

AWSのセキュリティグループでportの80(http)と443(https)を解放し、以下を実行する。

$ sudo /usr/bin/certbot-auto certonly --standalone --debug -d example.com -m example@example.com --agree-tos -n

  • –debug
    AWSは--debugがないと実行できない。

  • –standalone
    特にrootディレクトリを指定しないので --standalone オプションを設定。

  • -d
    ドメインを指定する。

  • -m
    メールアドレスを指定する。トラブルがあった場合や更新期限が近くなった際にメールが送られてる。

  • –agree-tos
    規約同意。

  • -n
    もろもろの対話入力をスキップ。

IP制限などしたい場合は解放したportを再度すぐ閉じる

nginxに証明書と鍵の設定

/etc/letsencrypt/live/ 以下に証明書と鍵が発行されるのでこれをnginxに設定

upstream example_server {
  server 127.0.0.1:3000;
}

server {
  listen 80;

  # Allow accessing /ping without https. Useful when placing behind load balancer.
  location /ping {
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass       http://example_server;
  }

  location / {
    # Enforce SSL.
    return 301 https://$host$request_uri;
  }
}

server {
  listen 443 ssl;
  ssl on;
  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

  gzip on;
  gzip_types *;
  gzip_proxied any;

  location / {
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_pass       http://example_server;
    proxy_redirect   off;
  }
}

cronにletsencrypt自動更新、nginx再起動を設定

$ sudo vi /etc/cron.d/letsencrypt
0 1 * * * /usr/bin/certbot-auto renew --force-renew && /etc/init.d/nginx reload

参考

http://qiita.com/takahiko/items/a08895550727b95b6c36

続きを読む

現在のインスタンス料金を取得する script: Redshift 編

今までリザーブド購入のために料金をいちいち AWS の料金ページまでアクセスして料金を確認してたんだけど AWS Price List API (http://docs.aws.amazon.com/ja_jp/awsaccountbilling/latest/aboutv2/price-changes.html) を利用して現在の料金の取得を自動化したかったので現在のインスタンス料金を取得する script を書いてみた

1111111料金_-_Amazon_Redshift___AWS.png

とりあえずインスタンスの種類が少なくて楽そうだったので今回は Redshift のものを対象に取得

前提条件として

  • Tokyo リージョンだけわかればよかったので Tokyo リージョンだけに絞ってる
  • 時間あたりの料金がわかる
  • リザーブドインスタンスの時の料金がわかる
  • インスタンスのスペックがわかる

あたりがわかるように書いた

とりあえず動いたものを貼ってるのでコードはきれいじゃない

redshift.rb
require 'json'
require 'bigdecimal'

results = {}
json_data = open(ARGV[0]) {|io| JSON.load(io) }

# product 情報を取得
json_data['products'].keys.each do |skuNo|
    product = json_data['products'][skuNo]

    if (product['productFamily'] == 'Compute Instance' and
          product['attributes']['locationType'] == 'AWS Region' and
          product['attributes']['location'] == 'Asia Pacific (Tokyo)')

        results[product['sku']] = {
            sku: product['sku'],
            location: product['attributes']['location'],
            instanceType: product['attributes']['instanceType'],
            instanceFamily: product['attributes']['instanceType'].split('.')[0],
            vcpu: product['attributes']['vcpu'],
            memory: product['attributes']['memory'],
            storage: product['attributes']['storage'],
            io: product['attributes']['io'],
            ecu: product['attributes']['ecu'],
            currentGeneration: product['attributes']['currentGeneration'],
            price_unit: 'USD'
        }

    end
end


# price

## on demand
json_data['terms']['OnDemand'].keys.each do |skuNo|
    if (results[skuNo])
        results[skuNo][:price_per_hour] = Proc.new {
            skuTerm = json_data['terms']['OnDemand'][skuNo][json_data['terms']['OnDemand'][skuNo].keys[0]]
            priceInfo = skuTerm['priceDimensions'][skuTerm['priceDimensions'].keys[0]]
            BigDecimal(priceInfo['pricePerUnit']['USD']).floor(2).to_f.to_s
        }.call
        results[skuNo][:price_per_day] = (BigDecimal(results[skuNo][:price_per_hour]) * BigDecimal("24")).floor(2).to_f.to_s
        results[skuNo][:price_per_month] = (BigDecimal(results[skuNo][:price_per_day]) * BigDecimal("30")).floor(2).to_f.to_s
    end
end


## reserved 
json_data['terms']['Reserved'].keys.each do |skuNo|
    if (results[skuNo])

        plans = json_data['terms']['Reserved'][skuNo].values.select do |plan|
            plan['termAttributes']['PurchaseOption'] == "All Upfront" # "All Upfront" のものだけ取得したい
        end

        results[skuNo][:price_reserved_1year_purchased_all_upfront] = plans.find { |plan|
            plan['termAttributes']['LeaseContractLength'] == '1yr'
        }['priceDimensions'].values.find {|priceDimension|
            priceDimension['description'] == "Upfront Fee"
        }['pricePerUnit']['USD']

        results[skuNo][:price_reserved_3year_purchased_all_upfront] = plans.find { |plan|
            plan['termAttributes']['LeaseContractLength'] == '3yr'
        }['priceDimensions'].values.find {|priceDimension|
            priceDimension['description'] == "Upfront Fee"
        }['pricePerUnit']['USD']

    end
end


# sort
sorted_result = {}
results.values.each do |row|
    sorted_result[row[:currentGeneration]] ||= {}
    sorted_result[row[:currentGeneration]][row[:instanceFamily]] ||= []
    sorted_result[row[:currentGeneration]][row[:instanceFamily]].push row
end

results = []
['Yes', 'No'].each do |currentGeneration| # 現行世代のものから並べる
    sorted_result[currentGeneration].keys.sort.each do |instanceFamily| # インスタンスファミリー毎に並べる
        results.concat sorted_result[currentGeneration][instanceFamily].sort_by { |row| row[:price_per_hour] }
    end
end

# sorted_result.values.each do |targetGenerationInstances|
#   targetGenerationInstances.keys.sort.each { |instanceFamily|
#       # binding.pry
#       results.concat targetGenerationInstances[instanceFamily].sort_by { |row| row[:price_per_hour]}
#   }
# end

p results.to_json

上記を保存して以下のように実行する

curl https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonRedshift/current/index.json > price-AmazonRedshift.json
ruby redshift.rb price-AmazonRedshift.json | sed -e s/^\"// | sed -e s/\"$// | sed -e 's/\\"/"/g' | jq .

以下のような結果が取れる

[
  {
    "sku": "6REDMMEE7FXXH5Y6",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "dc1.large",
    "instanceFamily": "dc1",
    "vcpu": "2",
    "memory": "15 GiB",
    "storage": "0.16TB SSD",
    "io": "0.20 GB/s",
    "ecu": "7",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "0.31",
    "price_per_day": "8",
    "price_per_month": "240",
    "price_reserved_1year_purchased_all_upfront": "1645",
    "price_reserved_3year_purchased_all_upfront": "2885"
  },
  {
    "sku": "CNP4R2XZ8N7RJJA8",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "dc1.8xlarge",
    "instanceFamily": "dc1",
    "vcpu": "32",
    "memory": "244 GiB",
    "storage": "2.56TB SSD",
    "io": "3.70 GB/s",
    "ecu": "104",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "6.09",
    "price_per_day": "147",
    "price_per_month": "4410",
    "price_reserved_1year_purchased_all_upfront": "33180",
    "price_reserved_3year_purchased_all_upfront": "46160"
  },
  {
    "sku": "YWHTRJBA2KAFS857",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "ds2.xlarge",
    "instanceFamily": "ds2",
    "vcpu": "4",
    "memory": "31 GiB",
    "storage": "2TB HDD",
    "io": "0.40 GB/s",
    "ecu": "14",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "1.19",
    "price_per_day": "29",
    "price_per_month": "870",
    "price_reserved_1year_purchased_all_upfront": "6125",
    "price_reserved_3year_purchased_all_upfront": "7585"
  },
  {
    "sku": "Q8X9U7UKTJV2VGY8",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "ds2.8xlarge",
    "instanceFamily": "ds2",
    "vcpu": "36",
    "memory": "244 GiB",
    "storage": "16TB HDD",
    "io": "3.30 GB/s",
    "ecu": "116",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "9.52",
    "price_per_day": "229",
    "price_per_month": "6870",
    "price_reserved_1year_purchased_all_upfront": "49020",
    "price_reserved_3year_purchased_all_upfront": "60630"
  },
  {
    "sku": "ZURKE2HZ3JZC6F2U",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "ds1.xlarge",
    "instanceFamily": "ds1",
    "vcpu": "2",
    "memory": "15 GiB",
    "storage": "2TB HDD",
    "io": "0.30 GB/s",
    "ecu": "4.4",
    "currentGeneration": "No",
    "price_unit": "USD",
    "price_per_hour": "1.19",
    "price_per_day": "29",
    "price_per_month": "870",
    "price_reserved_1year_purchased_all_upfront": "6125",
    "price_reserved_3year_purchased_all_upfront": "7585"
  },
  {
    "sku": "PDMPNVN5SPA5HWHH",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "ds1.8xlarge",
    "instanceFamily": "ds1",
    "vcpu": "16",
    "memory": "120 GiB",
    "storage": "16TB HDD",
    "io": "2.40 GB/s",
    "ecu": "35",
    "currentGeneration": "No",
    "price_unit": "USD",
    "price_per_hour": "9.52",
    "price_per_day": "229",
    "price_per_month": "6870",
    "price_reserved_1year_purchased_all_upfront": "49020",
    "price_reserved_3year_purchased_all_upfront": "60630"
  }
]

続きを読む

aws-sam-localだって!?これは試さざるを得ない!

2017-08-11にaws-sam-localのベータ版がリリースされました。
単に「早速試したで!」と言う記事を書いても良かったのですが、少し趣向を変えてサーバレス界隈の開発環境のこれまでの推移を語った上で、aws-sam-localの使ってみた感想もお話しようかと思います。

サーバレス開発環境の今昔

サーバレス自体がかなり最近になって生まれた風潮なので昔とか言うのも問題はあるかと思いますが、とにかくサーバレスなるものの開発環境について私にわかる範囲でお話しようと思います。誤りや不正確な点については編集リクエストやコメントを頂けると幸いです。

なお、サーバレスという言葉は一般的な名詞ですが、私がAWS上でしかサーバレスに触れていないため、AzureやGCPなどには触れず、もっぱらLambdaの話になってしまうことをあらかじめご了承ください。

Lambdaのデプロイは辛かった

Lambdaの基本的なデプロイ方法はZIPで固めてアップロードです。
直接ZIPをLambdaに送るか、あらかじめS3に置いておいてLambdaにはそのURLを教えるかといった選択肢はありましたが、手動でZIPに固めてAWSに送るという手順は不可避でした。なんかもうすでに辛い。

さらに言うとLambdaに送りつけられるのはLambdaで実行するコードだけ。性質上Lambdaは単体で使われることはほとんどなく、他のサービスと連携することがほとんどなのにその辺は自分で管理するしかありませんでした。辛い。

CloudFormationで管理することは可能でしたが、CloudFormationテンプレートを書くのがかなりダルいことと、CloudFormationの更新とZIPのアップロードを別途行う必要があって手順が煩雑化しやすいため、「もうええわ」と手動管理してることが多かったと思われます。

また、ローカル環境で実行するには一工夫必要でした。

颯爽登場!Serverlessフレームワーク

そんな時に颯爽と現れたのがServerlessフレームワークでした。
ServerlessフレームワークにおいてはLambdaファンクション及び関連するリソースを独自のyamlファイルで管理します。結局は一度CloudFormationテンプレートに変換されるのですが、CloudFormationテンプレートよりも単純な形式で記述できたのが流行った一因かと思います。また、sls deployコマンドでLambdaのコードのアップロードおCloudFormationスタックの更新を一括で行ってくれたため、デプロイの手順は従来よりもはるかに簡略化されたかと思われます。

Lambdaテストしづらい問題

デプロイに関する問題はServerlessフレームワークや、ほぼ同時期に現れたSAMによって改善されましたが、開発プロセスにおいて大きな課題がありました。

テストし辛ぇ…

上記の通りLambdaは性質上他のサービスと連携することが多いため、その辺をローカル環境でどうテストするかに多くの開発者が頭を抱えました。対策として

  1. モッククラスを作って、実際のサービスのような振る舞いをさせる
  2. プロダクションとは別のリージョンに環境を再現して、そこで実行する

といった方法がありましたが、それぞれ

  1. モッククラスの実装がすこぶるダルい 下手したらロジック本体より時間かかる
  2. クラウドにデプロイしないとテストできないため、時間がかかる

といったデメリットがありました。

LocalStackとaws-sam-local

サーバレス開発者の嘆きを聞いたAtlassianがローカル環境でAWSのサービスのエンドポイントを再現するなんとも素敵なツールを作り上げました。それがLocalStackです。
再現されているサービスの数が物足りなく感じられたり、サードパーティ製であることに一抹の不安を覚えたりする人もいるかと思いますが、これ以上を求めるなら自分で作るぐらいしかないのが現状かと思います。

そしてaws-sam-local。こちらはLocalStackと少し趣が異なります。LocalStackが連携するサービスのエンドポイントを再現して提供するのに対して、aws-sam-localは実行環境の提供という意味合いが強いです。そして重要なことはAWSの公式がサポートしているということです。公式がサポートしているということです。大事なことなので(ry
「実行するのはローカルでNode.jsなりPythonなりで動かせばええやん」と思いがちですが、ランタイムのバージョンなどを本番環境と確実に揃えられるのは大きな利点です。
まだベータ版が出たばっかなので今後に期待といったところでしょう

aws-sam-local触ってみた

それでは実際に触ってみましょう。
ちなみに当方環境は

  • OS: macOS Sierra 10.12.6
  • Docker for Mac: 17.06.0-ce-mac19

です。

事前準備とインストール

公式のInstallationの項目を読み進めますが、事前にDockerを使えるようにしておく必要があります。

Macだったら普通にDocker For Macをインストールしておけば問題ありません。
一方Windowsだと

スクリーンショット 2017-08-16 14.48.18.png

まさかのDocker Toolbox推奨。 Docker For Windowsェ…
そしてaws-sam-localのインストールですが、私は-gオプション排斥論者なのでローカルインストールします

npm install aws-sam-local

実装

今回はこちらを参考にAPIゲートウェイから呼び出すLambdaを実装します。
ほぼ丸パクリですが一部アレンジしてますのでソースものっけます。

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM Local test
Resources:
  HelloWorld:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs6.10
      Events:
        GetResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: put

ランタイムをnodejs6.10に変更してます。
新しく作る場合にわざわざ古いバージョンを使う必要もありませんので。

余談ですが、WebStormのCloudFormation用のプラグインは今の所SAMには対応してないのか、Type: AWS::Serverless::Functionのところにめっちゃ赤線を引かれます。

index.js
/**
 * Created by yuhomiki on 2017/08/16.
 */

"use strict";

const os = require("os");
console.log("Loading function");


const handler = (event, context, callback) => {
  return callback(null, {
    statusCode: 200,
    headers: { "x-custom-header" : "my custom header value" },
    body: "Hello " + event.body + os.EOL
  });
};

exports.handler = handler;

完全に書き方の趣味の問題です。
内容は参考ページのものと全く同じです。

package.json
{
  "name": "sam_test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "invoke-local": "sam local invoke HelloWorld -e event.json",
    "validate": "sam validate",
    "api-local": "sam local start-api"
  },
  "author": "Mic-U",
  "license": "MIT",
  "dependencies": {
    "aws-sam-local": "^0.1.0"
  }
}

aws-sam-localをローカルインストールしているので、package.jsonのscriptsに追記しています。

実行

それでは実行してみましょう

上記のpackage.jsonに記載した

  • invoke-local
  • validate
  • api-local

を実行していきます。

invoke-local

Lambdaファンクションをローカル環境で実行します。
Lambdaファンクションに渡すevent変数はワンライナーで定義することも可能ですが、あらかじめJSONファイルを作っといた方が取り回しがいいです。

json.event.json
{
  "body": "MIC"
}

実行結果はこんな感じ

スクリーンショット 2017-08-16 15.25.42.png

まず最初にdocker pullしてランタイムに応じたDockerイメージをダウンロードします。
その後はコンテナ内でLambdaファンクションを実行し、最後にcallbackに与えた引数を出力といった流れです。
ログの形式がすごくLambdaですね。あとタイムゾーンもUTCになっていますね。
メモリの使用量をローカルで確認できるのは嬉しいですね。

-dオプションをつけることでデバッグもできるようです。
公式のgithubにはご丁寧にVSCodeでデバッグしてる様子がgifで上げられてます。

validate

テンプレートファイルのチェックをします。
デフォルトではカレントディレクトリのtemplate.yamlファイルをチェックしますが、-tオプションで変更することが可能です。

失敗するとこんな感じに怒られます。

スクリーンショット 2017-08-16 15.33.47.png

成功した時は「Valid!」とだけ言ってきます。きっと必要以上に他人に関わりたくないタイプなのでしょう。

api-local

sam local start-apiコマンドはローカル環境にAPIサーバを立ち上げます。
ホットリロード機能がついてるので、立ち上げっぱなしでもソースを修正したら自動で反映されます。いい感じですね。

スクリーンショット 2017-08-16 15.40.58.png

立ち上がるとこんなメッセージが出るので、あとはCURLなりPostManなりで煮るなり焼くなり好きにしましょう。

CURLの結果はこんな感じ
スクリーンショット 2017-08-16 15.51.39.png

所感

Lambdaのローカル実行環境を公式が用意したことに大きな意義があるかと思います。
Dockerさえあればすぐに使えることと、SAMテンプレートを書かざるをえないのでInfrastructure as Codeが自然と根付いていくのも個人的には好感を持てます。

ただし、まだベータ版なこともあって機能的にもの足りない部分があるのも事実です。
具体的にはやはりDynamoDBとかもテンプレートから読み取ってDockerコンテナで用意してくれたらなーと思います。LocalStackやDynamoDB Localでできないこともないでしょうが、DynamoDB Localに関してはテンプレートからテーブル作ったりするの多分無理なのでマイグレーション用のコードを書くことになりますし、LocalStackに関しては実はあまり真面目に使ったことないのでわかりませんが環境構築に一手間かかりそう。ていうかできれば一つのツールで完結させたい。

SAMしかりaws-sam-localしかり、AWS側としてもより開発がしやすくなるような環境づくりをしていくような姿勢を見せているので、今後のアップデートに期待したいところですね。

続きを読む

AWS + Nginx + PHP + Laravel

nginx + php + LaravelをAWS上に構築してみる

nginx

  • インストールと起動
$ sudo yum -y install nginx
・・・・・
完了しました!

$ sudo service nginx start
Starting nginx:                                            [  OK  ]
  • バージョンやconfigurationの内容を知りたいときは下記コマンド
$ nginx -V
  • configurationで使いそうなやつメモ
設定 説明 デフォルト
–error-log-path HTTPアクセスログのエラーのパス /var/log/nginx/error.log
–http-log-path HTTPアクセスログのパス /var/log/nginx/access.log
–conf-path nginxの設定ファイルのパス /etc/nginx/nginx.conf
–http-proxy-temp-path プロキシを実行している場合、ここで指定したディレクトリが一時ファイルの格納パスになる /var/lib/nginx/tmp/proxy
  • モジュールで気になるところメモあたり(他にもあったけど、メモるの面倒でdown)
モジュール名 説明 利用場面 デフォルト
http_ssl https対応(OpenSSLライブラリが必要)。 プロキシ 有効
http_realip L7ロードバランサなどの後に配置する場合有効にする必要あり。複数のクライアントが同一IPアドレスから通信してくるように見える環境で使用。 プロキシ 有効
http_geoip クライアントのIPアドレスに基づく地理的位置に応じた処理を行うための様々な変数を設定 Web、プロキシ 有効
http_stub_status Nginx自身の統計情報の収集を手助けする Web、プロキシ 有効

※有効化(–with-<モジュール名>module)、無効化(–without-<モジュール名>module)

PHP7のインストール

  • CentOS6用のPHP7のリポジトリを追加(これがないとインストールできないくさい)
$ sudo yum install --enablerepo=webtatic-testing 
                 php70w php70w-devel php70w-fpm php70w-mysql 
                 php70w-mbstring php70w-pdo
  • 他にも必要であればインストールしておく(json系とか)

nginxとphpの紐付け

  • index.phpのセット

    • /var/www/default ディレクトリ作成
    • ここにindex.phpを配置 (最初はとりあえずphpinfoを吐くだけ)
  • /etc/php-fpm.d/www.confの編集 (backupを取った上で編集)
$ diff -uN www.conf.backup_20160710 www.conf
--- www.conf.backup_20160710    2016-07-10 08:00:45.267704077 +0000
+++ www.conf    2016-07-10 08:01:38.451085053 +0000
@@ -5,9 +5,11 @@
 ; Note: The user is mandatory. If the group is not set, the default user's group
 ;       will be used.
 ; RPM: apache Choosed to be able to access some dir as httpd
-user = apache
+; user = apache
+user = nginx
 ; RPM: Keep a group allowed to write in log dir.
-group = apache
+; group = apache
+group = nginx
  • /etc/nginx/nginx.confの編集 (backupを取った上で編集)
$ diff -uN nginx.conf.backup_20160710 nginx.conf
--- nginx.conf.backup_20160710  2016-07-10 07:49:38.694839828 +0000
+++ nginx.conf  2016-07-10 07:59:49.564346085 +0000
@@ -32,13 +32,14 @@
     # for more information.
     include /etc/nginx/conf.d/*.conf;

-    index   index.html index.htm;
+    index   index.php index.html index.htm;

     server {
         listen       80 default_server;
         listen       [::]:80 default_server;
         server_name  localhost;
-        root         /usr/share/nginx/html;
+        #root         /usr/share/nginx/html;
+        root         /var/www/default;

         # Load configuration files for the default server block.
         include /etc/nginx/default.d/*.conf;
@@ -46,8 +47,17 @@
         location / {
         }

-        # redirect server error pages to the static page /40x.html
+        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
         #
+        location ~ .php$ {
+            root           /var/www/default;
+            fastcgi_pass   127.0.0.1:9000;
+            fastcgi_index  index.php;
+            fastcgi_param  SCRIPT_FILENAME  /var/www/default$fastcgi_script_name;
+            include        fastcgi_params;
+        }
+
+        # redirect server error pages to the static page /40x.html
         error_page 404 /404.html;
             location = /40x.html {
         }
@@ -64,16 +74,6 @@
         #    proxy_pass   http://127.0.0.1;
         #}

-        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
-        #
-        #location ~ .php$ {
-        #    root           html;
-        #    fastcgi_pass   127.0.0.1:9000;
-        #    fastcgi_index  index.php;
-        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
-        #    include        fastcgi_params;
-        #}
-
  • 再起動して、phpinfoページが見れればOK (http://<>)
$ sudo service php-fpm start
Starting php-fpm:                                          [  OK  ]
$ sudo service nginx restart
Stopping nginx:                                            [  OK  ]
Starting nginx:                                            [  OK  ]
  • ついでにサーバ起動時などに自動で起動するものも設定
$ sudo chkconfig nginx on
$ sudo chkconfig php-fpm on

nginxとphp-fpmの接続をsocketにする

  • php-fpmの設定変更
$ diff -uN www.conf.backup_20160710 www.conf
--- www.conf.backup_20160710    2016-07-10 08:00:45.267704077 +0000
+++ www.conf    2016-07-10 08:19:03.630366042 +0000
@@ -19,7 +21,8 @@
 ;                            (IPv6 and IPv4-mapped) on a specific port;
 ;   '/path/to/unix/socket' - to listen on a unix socket.
 ; Note: This value is mandatory.
-listen = 127.0.0.1:9000
+; listen = 127.0.0.1:9000
+listen = /var/run/php-fpm/php-fpm.sock

@@ -32,6 +35,8 @@
 ;                 mode is set to 0660
 ;listen.owner = nobody
 ;listen.group = nobody
+listen.owner = nginx
+listen.group = nginx
 ;listen.mode = 0660
  • nginxの設定変更
$ diff -uN nginx.conf.backup_20160710 nginx.conf
--- nginx.conf.backup_20160710  2016-07-10 07:49:38.694839828 +0000
+++ nginx.conf  2016-07-10 08:20:37.741301066 +0000
@@ -46,8 +47,17 @@
-            fastcgi_pass   127.0.0.1:9000;
+            fastcgi_pass   unix:/var/run/php-fpm/php-fpm.sock;
  • 再起動
$ sudo service php-fpm restart
Stopping php-fpm:                                          [  OK  ]
Starting php-fpm:                                          [  OK  ]
$ sudo service nginx restart
Stopping nginx:                                            [  OK  ]
Starting nginx:                                            [  OK  ]

Laravel5を入れてみる

  • Composerをインストール
$ curl -sS https://getcomposer.org/installer | php
$ sudo mv /home/ec2-user/composer.phar /usr/local/bin/composer
  • Laravelのインストール
$ sudo /usr/local/bin/composer global require "laravel/installer"
Changed current directory to /root/.composer
Using version ^1.3 for laravel/installer
./composer.json has been created
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing symfony/process (v3.1.2)
    Downloading: 100%         

  - Installing symfony/polyfill-mbstring (v1.2.0)
    Downloading: 100%         

  - Installing symfony/console (v3.1.2)
    Downloading: 100%         

  - Installing guzzlehttp/promises (1.2.0)
    Downloading: 100%         

  - Installing psr/http-message (1.0)
    Downloading: 100%         

  - Installing guzzlehttp/psr7 (1.3.1)
    Downloading: 100%         

  - Installing guzzlehttp/guzzle (6.2.0)
    Downloading: 100%         

  - Installing laravel/installer (v1.3.3)
    Downloading: 100%         

symfony/console suggests installing symfony/event-dispatcher ()
symfony/console suggests installing psr/log (For using the console logger)
Writing lock file
Generating autoload files
  • php-xmlのインストール (laravelで必要になる)
$ sudo yum install --enablerepo=webtatic-testing php70w-xml
  • プロジェクト作成
$ pwd
/var/www/default
$ sudo /usr/local/bin/composer create-project --prefer-dist laravel/laravel darmaso
Installing laravel/laravel (v5.2.31)
  - Installing laravel/laravel (v5.2.31)
    Downloading: 100%         

Created project in darmaso
> php -r "copy('.env.example', '.env');"
Loading composer repositories with package information
Updating dependencies (including require-dev)
・・・・・ (下記の結果と同じ)

$ cd darmaso
$ sudo /usr/local/bin/composer install
Loading composer repositories with package information
Updating dependencies (including require-dev)
・・・・・
Writing lock file
Generating autoload files
> IlluminateFoundationComposerScripts::postUpdate
> php artisan optimize
Generating optimized class loader

※php-xmlをインストールしておかないと、下記のようなエラーが出るので注意
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - phpunit/phpunit 4.8.9 requires ext-dom * -> the requested PHP extension dom is missing from your system.
・・・・・
    - Installation request for phpunit/phpunit ~4.0 -> satisfiable by phpunit/phpunit[4.0.0, 4.0.1, 4.0.10, 4.0.11, 4.0.12, 4.0.13, 4.0.14, 4.0.15, 4.0.16, 4.0.17, 4.0.18, 4.0.19, 4.0.2, 4.0.20, 〜
・・・・・
  To enable extensions, verify that they are enabled in those .ini files:
    - /etc/php.ini
    - /etc/php.d/bz2.ini
    - /etc/php.d/calendar.ini
・・・・・
  You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode.
  • Applicationキーの生成 (composerでインストールした場合セットされているらしいが念のため)
$ sudo php artisan key:generate
Application key [base64:YVeCf2A+5IjUbk2qVL4HhPiecBdYuo8irJrEYjJKZWY=] set successfully.
  • Laravel用にnginx設定を修正し、再起動
$ diff -uN nginx.conf.backup_20160710 nginx.conf
+        #root         /var/www/default;
+        root         /var/www/default/darmaso/public;
・・・・・
         location / {
+            try_files $uri $uri/ /index.php?$query_string;
         }
・・・・・
+            #root           /var/www/default;
+            root           /var/www/default/darmaso/public;
・・・・・
+            #fastcgi_param  SCRIPT_FILENAME  /var/www/default$fastcgi_script_name;
+            fastcgi_param  SCRIPT_FILENAME  /var/www/default/darmaso/public$fastcgi_script_name;

$ sudo service php-fpm restart
$ sudo service nginx restart
  • これで動作確認するとエラーになるので下記の設定をしてみる
$ sudo chmod -R 777 storage/
$ sudo chmod -R 777 vendor/

※本来は、サーバアカウントをちゃんと定義してやるべきだが、今回は試しなのでこのままでOKとする

  • 一部の設定を変えてみる
config/app.php
$ diff -uN config/app.php.backup_20160710 config/app.php
--- config/app.php.backup_20160710  2016-07-10 09:37:07.881735079 +0000
+++ config/app.php  2016-07-10 09:40:54.263419145 +0000
@@ -52,7 +52,7 @@
     |
     */

-    'timezone' => 'UTC',
+    'timezone' => 'Asia/Tokyo',

     /*
     |--------------------------------------------------------------------------
@@ -65,7 +65,7 @@
     |
     */

-    'locale' => 'en',
+    'locale' => 'jp',

     /*
     |--------------------------------------------------------------------------
@@ -78,7 +78,7 @@
     |
     */

-    'fallback_locale' => 'en',
+    'fallback_locale' => 'jp',

これで構築した環境にアクセスしたところ、無事いけました!
設定内容が荒いところもありますが、上記まででPHP+Nginx自体はいけちゃいますね。

Nginxの設定はあまり大したことはできませんでしたが、今後は色々と勉強してみようと思いますmm

参考

続きを読む

Running Kubernetes Cluster on AWS

Following this guide https://kubernetes.io/docs/getting-started-guides/kops/, set up Kubernetes cluster on AWS using kops.

1. Install kops

According to the guide, kops is:

kops helps you create, destroy, upgrade and maintain production-grade, hig… 続きを読む

AWS Batch を動かしてみる

概要

AWS Batchを一通り動かしてみる。

↓の続きです
AWSでバッチ処理をするときの方法を考える

実践

※参考にしたサイト
API Gateway + LambdaでAWS BatchのJobを実行する

1. AWS Batchを作成

AWS Batchを開きます。

スクリーンショット 2017-08-14 17.20.26.jpg

色々入力するところがあるけど全部デフォルトでいいです。
スクリーンショット 2017-08-14 19.04.19.jpg

スクリーンショット 2017-08-14 19.48.45.jpg
そしてCreate。

スクリーンショット 2017-08-14 17.25.46.jpg
実行直後はPriorityが1になっていればとりあえず問題ないはずです。
しばらく経つとするとRUNABLEに入り、SUCCEEDEDに入ればJob完了です。

LambdaからBatchを起動する際にBatchのjobQueueArnが必要なのでJob queuesから確認してメモしておきます。
スクリーンショット 2017-08-14 20.29.01.jpg

2. AWS Batchを起動するLambdaの作成

まずLambda用の新規ロール作成からです。
下記のようなポリシーのロールを作成しておきます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": [
                "batch:SubmitJob"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

ロールを作成したらLambdaを作っていきます。
スクリーンショット 2017-08-14 17.30.50.jpg

設計図は使用せずに 「一から作成」 を選択。
スクリーンショット 2017-08-14 17.31.03.jpg

トリガーは追加せずに「次へ」。
スクリーンショット 2017-08-14 17.31.12.jpg

関数の設定です。
適当に名前を付けて、サンプル通りPython3.6を選びます。
スクリーンショット 2017-08-14 17.42.22.jpg

Lambdaの関数コードは下記に書き換え。

import boto3

def lambda_handler(event, context):

    client = boto3.client('batch')

    JOB_NAME = event['JobNeme']
    JOB_QUEUE = "arn:aws:batch:ap-northeast-1:xxxxxxxxxxxx:job-queue/first-run-job-queue"
    JOB_DEFINITION = "first-run-job-definition:1"

    response = client.submit_job(
        jobName = JOB_NAME,
        jobQueue = JOB_QUEUE,
        jobDefinition = JOB_DEFINITION
        )
    print(response)
    return 0

※8行目 JOB_QUEUE = <1.で作成したBtachのQueue arnに書き換え>

ロールは「テンプレートから新規作成し、テンプレートは “AWS Batchアクセス権限” を選びます」
スクリーンショット 2017-08-14 17.42.40.jpg

次へ
スクリーンショット 2017-08-14 17.42.49.jpg

ざっくり確認しておきます
スクリーンショット 2017-08-14 17.43.23.jpg

スクリーンショット 2017-08-14 17.43.29.jpg

おめでとうございます。
スクリーンショット 2017-08-14 17.44.07.jpg

3. LambdaのためのAPI Gatewayを作る

  1. 先に作成したLambdaのトリガータブを開き「+トリガーを追加」します
    スクリーンショット 2017-08-14 17.44.41.jpg

  2. API Gatewayをトリガーとして設定して送信します。
    スクリーンショット 2017-08-14 17.46.24.jpg

  3. メソッドの作成からPOSTを追加します。
    スクリーンショット 2017-08-14 17.47.34.jpg

  4. 先に作成したLambdaのリージョンを選び、Lambda関数を選択します。
    スクリーンショット 2017-08-14 17.49.06.jpg

  5. APIをデプロイします。
    スクリーンショット 2017-08-14 17.49.35.jpg

  6. 実験ですがProdで特に問題ないです。
    スクリーンショット 2017-08-14 17.49.44.jpg

  7. デプロイが完了するとURLが生成されます。
    スクリーンショット 2017-08-14 17.49.55.jpg

4. 動作確認

  1. 生成されたurlにcurlでPOSTしてみます。
$ curl -X POST -d '{ "JobName" : "test" }' https://xxxxxxxxxx.execute-api.us-east-2.amazonaws.com/prod/ToBatch/
  1. コマンドラインに応答はありませんが、BatchのJob queuesに新しいJobが入ってきます。
    スクリーンショット 2017-08-14 18.30.40.jpg

5. 完了

一旦これで実用的な形になったと思います。
あとはJobに自分の処理内容を乗せておけば、APIでいつでも実行可能となります。

続きを読む

EC2インスタンスの情報をインスタンス内部から取得する

EC2インスタンスのメタデータを取得

取得できるメタデータの一覧を表示する

$ curl -s http://169.254.169.254/latest/meta-data/   #最後の/を忘れないこと
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
hostname
instance-action
instance-id
instance-type
local-hostname
local-ipv4
mac
metrics/
network/
placement/
profile
public-hostname
public-ipv4
public-keys/
reservation-id
security-groups
services/

使い方の例

AZを取得する

$ curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone/
ap-northeast-1a

参考資料

インスタンスメタデータとユーザーデータ

続きを読む

ElasticseharchのEC2 Discovery Pluginでクラスタ組むのが捗るはなし

こんばんはー

今回、ElasticsearchのクラスタをAWS上に組む際に便利なブラグインを使ってみたいと思いますー
その名も「EC2 Discovery Plugin」☆彡

調べているとAWS Cloud Pluginの使い方を書いている人は結構いるのですが、5系から提供されているEC2 Discovery Pluginはあんまいないのかなーって思いました。
なので、EC2 Discovery Pluginについてつらつら書きたいと思います( ゚Д゚)ゞビシッ

そもそもEC2 Discovery Pluginは何が便利なん?

Elasticsearchのデフォルトで用意されているZen Discoveryを利用してクラスタを組む際は、Unicastでホスト名やIPアドレス指定で組みます。
ただ、クラウド上の運用を考えるとUnicastはつらい。。
そこで、EC2 Discovery Pluginを使うとSecurityGroupを元にクラスタが組まれるのですー(d゚ω゚d)オゥイェー
Multicast Discovery pluginというのもあったのですが、5系から廃止されました

早速ですが、構成の説明したら実装方法を説明したいと思いますー

インスタンス構成

  • InstanceType:t2.medium
  • OS:Amazon Linux AMI release 2017.03
  • Node: 3台
  • ClusterName: es-cluster

こんな感じの流れで書いちゃいますー

  1. AWS準備

  2. Install Oracle JDK 1.8

  3. Install Elasticsearch

  4. Install Kibana(1台のみ)

  5. Install X-pack Elasticsearch & kibana(1台のみ)

  6. Install EC2 Discovery Plugin

  7. 動作確認

AWS準備

SecurityGroup

ElasticsearchをインストールするインスタンスにアタッチするSecurityGroupです。
ソースは、環境に合わせて設定してください。

Type Protocol Port Source Description
Custom TCP 9200 xx Elasticsearchアクセスポート
Custom TCP 9300 xx ノード間のコミュニケーションポート
Custom TCP 5601 xx KibanaのHTTP用ポート
Custom TCP 22 xx SSH用ポート

IAM Role

以下のPolicyを作成し、インスタンスにアタッチしているIAM RoleにPolicyをアタッチしてください。

{
    "Statement": [
        {
            "Action": [
                "ec2:DescribeInstances"
            ],
            "Effect": "Allow",
            "Resource": [
                "*"
            ]
        }
    ],
    "Version": "2012-10-17"
}

AWS Configure

サーバにログインしたらAWS Configureの実行をします。
Access KeyとSecret keyは利用しないため、何も入力しません。

$ aws configure
AWS Access Key ID [None]:
AWS Secret Access Key [None]:
Default region name [None]: ap-northeast-1
Default output format [None]: json

下準備が終わったので、ここからElasticsearch周りを進めたいと思いますー

Install Oracle JDK 1.8

$ java -version
java version "1.7.0_141"
OpenJDK Runtime Environment (amzn-2.6.10.1.73.amzn1-x86_64 u141-b02)
OpenJDK 64-Bit Server VM (build 24.141-b02, mixed mode)

### Install Java1.8
$ sudo yum -y install java-1.8.0-openjdk-devel

### alternativesコマンドでJavaのバージョンを切り替える
$ sudo alternatives --config java

There are 2 programs which provide 'java'.

  Selection    Command
-----------------------------------------------
*+ 1           /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java
   2           /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java

Enter to keep the current selection[+], or type selection number: 2

### バージョン確認
$ java -version
openjdk version "1.8.0_141"
OpenJDK Runtime Environment (build 1.8.0_141-b16)
OpenJDK 64-Bit Server VM (build 25.141-b16, mixed mode)

Install Elasticsearch

Elasticsearch5.5.1を入れちゃいたいと思いますー

Install Elasticsearch

### PGP Keyをダウンロード&インストール
$ sudo rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch

### リポジトリの登録
$ sudo vim /etc/yum.repos.d/elastic.repo
[elasticsearch-5.x]
name=Elasticsearch repository for 5.x packages
baseurl=https://artifacts.elastic.co/packages/5.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md

### Elasticsearchインストール
$ sudo yum -y install elasticsearch

### サービス起動登録
$ sudo chkconfig --add elasticsearch

### 確認
$ sudo chkconfig --list | grep elasticsearch
elasticsearch   0:off   1:off   2:on    3:on    4:on    5:on    6:off

### elasticsearch.yml設定変更
$ sudo vim /etc/elasticsearch/elasticsearch.yml
+ cluster.name: es-cluster
+ network.host: 0.0.0.0
+ http.port: 9200

### サービス起動
$ sudo service elasticsearch start
Starting elasticsearch:                                    [  OK  ]

### 起動確認
$ curl http://localhost:9200
{
  "name" : "fDpNQ4m",
  "cluster_name" : "es",
  "cluster_uuid" : "mayxoDENThSmrUltkXyRWg",
  "version" : {
    "number" : "5.5.1",
    "build_hash" : "19c13d0",
    "build_date" : "2017-07-18T20:44:24.823Z",
    "build_snapshot" : false,
    "lucene_version" : "6.6.0"
  },
  "tagline" : "You Know, for Search"
}

Install Kibana

モニタリングしたいのでKibana5.5.1をインストールします。

### Install Kibana
$ sudo yum install -y kibana
### サービス登録
$ sudo chkconfig --add kibana
$ chkconfig --list | grep kibana
kibana          0:off   1:off   2:on    3:on    4:on    5:on    6:off
### Kibana設定
$ sudo vim /etc/kibana/kibana.yml
+ server.host: 0.0.0.0
+ elasticsearch.url: "http://ES_IP_ADDR"
### サービス停止状態のままにしておきます(X-Pack入れたあとに設定変更後に起動します)
$ service kibana status
kibana is not running

Install X-Pack on Elasticsearch

Elasticsearchのノード状態などをモニタリングしたいため、X-Pack5.5をインストールします。

Install X-Pack on Elasticsearch

$ sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install x-pack
-> Downloading x-pack from elastic
[=================================================] 100%  
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@     WARNING: plugin requires additional permissions     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
* java.io.FilePermission \.pipe* read,write
* java.lang.RuntimePermission accessClassInPackage.com.sun.activation.registries
* java.lang.RuntimePermission getClassLoader
* java.lang.RuntimePermission setContextClassLoader
* java.lang.RuntimePermission setFactory
* java.security.SecurityPermission createPolicy.JavaPolicy
* java.security.SecurityPermission getPolicy
* java.security.SecurityPermission putProviderProperty.BC
* java.security.SecurityPermission setPolicy
* java.util.PropertyPermission * read,write
* java.util.PropertyPermission sun.nio.ch.bugLevel write
* javax.net.ssl.SSLPermission setHostnameVerifier
See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html
for descriptions of what these permissions allow and the associated risks.

Continue with installation? [y/N]y
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@        WARNING: plugin forks a native controller        @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
This plugin launches a native controller that is not subject to the Java
security manager nor to system call filters.

Continue with installation? [y/N]y
-> Installed x-pack

動作確認するのですが、Securityが有効になったため、エラーが返ってきます。
なので、今回は、Securityを無効にします。
(モニタリング用途で使いたいだけなので―)

ちなみに、X-Packについては、クラスメソッドさんが詳しく書いてますー
クラスメソッド:Elastic Stack X-Pack

X-Pack Security無効化

無効化することで、レスポンスが返ってきます。

### Security無効化
$ vim /etc/elasticsearch/elasticsearch.yml
+ xpack.security.enabled: false

### 動作確認
$ curl -u elastic http://localhost:9200
{
  "name" : "fDpNQ4m",
  "cluster_name" : "es",
  "cluster_uuid" : "mayxoDENThSmrUltkXyRWg",
  "version" : {
    "number" : "5.5.1",
    "build_hash" : "19c13d0",
    "build_date" : "2017-07-18T20:44:24.823Z",
    "build_snapshot" : false,
    "lucene_version" : "6.6.0"
  },
  "tagline" : "You Know, for Search"
}

### 再起動
$ service kibana restart
kibana stopped.
kibana started

Install X-Pack on Kibana

KibanaのX-Pack5.5もインストールします。

Install X-Pack on Kibana

$ sudo /usr/share/kibana/bin/kibana-plugin install x-pack
Attempting to transfer from x-pack
Attempting to transfer from https://artifacts.elastic.co/downloads/kibana-plugins/x-pack/x-pack-5.5.1.zip
Transferring 119276972 bytes....................
Transfer complete
Retrieving metadata from plugin archive
Extracting plugin archive
Extraction complete

Install EC2 Discovery Plugin

ここからEC2 Discovery Pluginをインストールします。
やっとここまできましたね!

$ sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install discovery-ec2
-> Downloading discovery-ec2 from elastic
[=================================================] 100%  
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@     WARNING: plugin requires additional permissions     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
* java.lang.RuntimePermission accessDeclaredMembers
* java.lang.RuntimePermission getClassLoader
See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html
for descriptions of what these permissions allow and the associated risks.

Continue with installation? [y/N]y
-> Installed discovery-ec2

Elasticsearch.ymlに設定します。

$ sudo vim /etc/elasticsearch/elasticsearch.yml
cluster.name: es-cluster
discovery.zen.hosts_provider: ec2
discovery.ec2.groups: "SECURITY_GROUP_NAME" or "SECURITY_GROUP_ID"
discovery.ec2.availability_zones: [ "ap-northeast-1a", "ap-northeast-1c" ]
cloud.aws.region: ap-northeast-1

### サービス再起動
service elasticsearch restart

### クラスタ状態確認
$ curl http://localhost:9200/_cluster/health?pretty
{
  "cluster_name" : "es-cluster",
  "status" : "green",
  "timed_out" : false,
  "number_of_nodes" : 1,
  "number_of_data_nodes" : 1,
  "active_primary_shards" : 0,
  "active_shards" : 0,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 0,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 100.0
}

2台目&3台目いくよ!!

2台目も上記と同じで設定を実施しますので割愛しちゃいますm(_ _)m
SecurityGroupは、1台目と同じのを使ってください。
また、ElasticsearchのX-Packは、3台すべてに入れてください。
Kibanaは、インストールしないです。

もろもろ設定して、Elasticsearchを再起動するとログにクラスタが組み込まれたことがわかります。

クラスタに組み込まれているかをログから確認

### Master側のログ
[20xx-xx-xxxx:xx:xx][INFO ][o.e.c.s.ClusterService   ] [fDpNQ4m] added {{gdRR6r1}{gdRR6r17T5iaGPmaO9wgTA}{6l3QNArhSkyBVu08jTfwYQ}{IP_ADDR}{IP_ADDR:9300},}, reason: zen-disco-receive(from master [master {dKgmQfLLg}{IP_ADDR}{IP_ADDR:9300} committed version [15]])

### Node側のログでマスタを検出したことがわかる
$ tail -f /var/log/elasticsearch/es.log
[20xx-xx-xxxx:xx:xx][INFO ][o.e.c.s.ClusterService   ] [gdRR6r1] detected_master {dKgmQfL}{dKgmQfLASM-35x0X0ulSXg}{XE4jgXEzQwqgkSfRypgoLg}{IP_ADDR}{IP_ADDR:9300}, added {{fDpNQ4m}{fDpNQ4mrRR2vN7wiTVjfIg}{aqPbmbhbTkm1X40gH1tylw}{IP_ADDR}{IP_ADDR:9300},{dKgmQfL}{dKgmQfLASM-35x0X0ulSXg}{XE4jgXEzQwqgkSfRypgoLg}{IP_ADDR}{IP_ADDR:9300},}, reason: zen-disco-receive(from master [master {dKgmQfL}{dKgmQfLASM-35x0X0ulSXg}{XE4jgXEzQwqgkSfRypgoLg}{IP_ADDR}{IP_ADDR:9300} committed version [15]])

Kibanaのモニタリングで確認するよ

Kibana [http://KIBANA_IP_ADDR:5601] にアクセスします。

「Monitoring」をクリックし、ElasticsearchにNodeが3台あることがわかりますね。
ステータス:Green

kibana01.png

「Nodes」をクリックし、Nodeの状態を確認できます。

kibana02.png

無事にクラスタを組むことができましたヽ(*゚д゚)ノ

補足#01

AWS Configureの設定とIAM Roleにポリシーをアタッチしないと以下のエラーがでます。
ちなみに、作成したクラスタ名でログが作成されちゃいます。

$ sudo /var/log/elasticsearch/es-cluster.log
[20xx-xx-xxxx:xx:xx][INFO ][o.e.d.e.AwsEc2UnicastHostsProvider] [node_id] Exception while retrieving instance list from AWS API: Unable to execute HTTP request: connect timed out

補足#02

既に存在しているNodeIDのAMIから起動すると、NodeIDがかぶっているためクラスタに組み込むことができないです。。
その際のログが以下です。

### AMIから起動したノード
$ tail -f /var/log/elasticsearch/es.log
[20xx-xx-xxxx:xx:xx][INFO ][o.e.d.z.ZenDiscovery     ] [gdRR6r1] failed to send join request to master [{fDpNQ4m}{fDpNQ4mrRR2vN7wiTVjfIg}{eHfV5HLkRrKo8_FXfgyHDA}{IP_ADDR}{IP_ADDR:9300}{ml.enabled=true}], reason [RemoteTransportException[[fDpNQ4m][IP_ADDR:9300][internal:discovery/zen/join]]; nested: IllegalArgumentException[can't add node {gdRR6r1}{gdRR6r17T5iaGPmaO9wgTA}{hzzXQXB8TM-xQVn9Uj8e2A}{IP_ADDR}{IP_ADDR:9300}{ml.enabled=true}, found existing node {gdRR6r1}{gdRR6r17T5iaGPmaO9wgTA}{9pWjYpL5Tq23yIte7WzMiw}{IP_ADDR}{IP_ADDR:9300}{ml.enabled=true} with the same id but is a different node instance]; ]

さいごに

EC2 Discovery Pluginいかがでしたか?
簡単にクラスタを組むことができたんじゃないかなと思います。

ただ、個人的には、運用を考えると自動復旧できる構成にする必要があると思ってます。
今の状態だとElasticsearchのクラスタ化する時にNodeIDがかぶっちゃうので、AMIからの自動復旧がむずかしい状態です。
なので、Elasticsearchをインストールする前のAMIからプロビジョニングして、クラスタに組み込む必要があるかなと。
(Elasticsearchインストール時にNodeIDが振られる)

うーん。。NodeIDを変更する方法が他にあればいいのですが、誰か知っている人いたら教えてくださいm(_ _)m

てことで、今回は、ElasticsearchのプラグインのEC2 Discovery Pluginについて書かせて頂きました。
ありがとうございましたー

続きを読む

SORACOM BeamでバイナリデータをAPIGateway経由でS3にアップロードする

はじめに

あるお客様からLTEモジュールからS3に画像ファイルをアップロードしたとの要望があったので、いろいろな方法を模索したのですが、AWS-CLIなんて積めないし、HTTPリクエストのヘッダーに必要なAWS認証情報を作成できないし、といった感じでどうやって実現しようかと悩んでいました。(AWS-CLIやAWS-SDKのすばらしさに感動したw)
そんなとき「SORACOM Beamを使ってAPIGateway経由で何とかならんかね?バイナリデータのサポートもされたことだし」という神のお告げが降ってきたので実装してみた

どんな感じ?

apigateway_post.PNG

SORACOM Beamって?

下記公式ページから抜粋

SORACOM Beam(以下、Beam)は、IoT デバイスにかかる暗号化等の高負荷処理や接続先の設定を、クラウドにオフロードできるサービスです。
Beam を利用することによって、クラウドを介していつでも、どこからでも、簡単に IoT デバイスを管理することができます。
大量のデバイスを直接設定する必要はありません。

(参考:https://soracom.jp/services/beam/)

S3バケットの作成

まず画像ファイルをアップロードするバケットを作成しましょう
詳しい手順は割愛しますが、バケットポリシーを設定しないとPutObjectできないのでこんな感じで設定しておきます

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AddPerm",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::[Bucket Name]/*"
        }
    ]
}

APIGatewayの作成

次にAPIGatewayを作成します

  1. APIGatewayのコンソール画面を開き、「Create API」をクリックします
  2. 「API name」に任意の文字列を入力し「Create API」で作成します
  3. リソースを作成します。「Actions」から「Create Resource」を選択し「upload」というリソースを作成します
  4. 「/upload」の子階層に「/{key}」といリソースを作成します。「/{key}」はURLパスパラメータとなり、今回はS3にPutする際のKey情報となります
  5. 「/{key}」を選択した状態で「Actions」から「Create method」を選択し「POST」メソッドを作成します。画面はこのような感じになっていると思います。この時点で「/upload/{key}」に対してPOSTメソッドを受け付けるように設定されています
    apigateway_post_test.PNG
  6. 「Integration type:HTTP」「HTTP method:PUT」「Endpoint URL:S3のエンドポイント(ex:https://s3-ap-northeast-1.amazonaws.com/[Bucket Name]/{key})」を選択・入力し「save」をクリックします。そうするとPOSTメソッドが作成されます
    apigateway_post_method.PNG
    「Method Request」を選択して「API Key Required」を「ture」に変更します
  7. 「Actions」から「Deploy API」を選択し、APIをデプロイします。初めての場合は[New Stage]を選択して新しいステージを作成してください。「Invoke URL」を控えておいてください
  8. バイナリデータを受け取る設定をします。「Binary Support」を選択し、「Edit」をクリックして「application/octet-stream」と「image/png」を追加します
  9. 次にSORACOM BeamからAPIGatewayを利用するにあたってAPIキーが必要になるので、APIキーを作成してAPIGatewayと紐付けます。
  10. 「API Keys」を選択し、「Actions」から「Create API Key」をクリック。「Name」に任意の文字列を入力し、「Save」をクリックします。これでAPIキーが作成されました。「API Key」の文字列をどこかに控えておいてください
  11. 「Usage Plans」を選択し、「Create」をクリック。各必須項目に任意の値を入力して「next」をクリック
  12. 「Add API Stage」をクリックして、先ほどデプロイしたAPIGatewayを選択し「Next」をクリック
  13. 「Add API Key to Usage Plan」を選択し、先ほど作成したAPIキーを入力し「Done」をクリック
  14. これで、APIGatewayとAPIキーが結びつきました

SORACOM Beamの設定

最後にSORACOM Beamの設定をします
1. SORACOMのコンソール画面を開きグループを作成します
2. 作成したグループのBeam設定を開き、プラスのアイコンから「HTTPエントリポイント」を選択します
3. 下記内容を入力・設定していきます

設定名:任意の文字列
エントリポイント
  パス:/
転送先
  ホスト名:APIGatewayのInvoke URL
  パス:/upload/test.png
ヘッダ操作
  IMSIヘッダ付与:ON
  署名ヘッダ付与:ON
  事前共有鍵:作成しておいたものを選択。新たに作成する場合は後ほど記述します
カスタムヘッダ:「アクション:置換」「ヘッダ名:X-API-KEY」「値:APIキーの文字列」

※事前共有鍵の作成
「認証情報ID」と「事前共有鍵」に任意の文字列を入力し「登録」をクリックしてください
soracom_key.PNG

これで「http://beam.soracom.io:8888/ 」に対してリクエストを送信すると、APIGatewayにPOSTメソッドが送信されます

送信テスト

SIMのグループをBeamが設定されているグループに変更し、デバイスにSSHでログインします
テスト送信用のpngファイル(下記コマンドでは「test.png」)があるディレクトリに移動し、下記コマンドを入力しS3に画像がアップロードされているかを確認してください

curl -X POST --data-binary "@test.png" -H  "Content-Type: application/octet-stream" http://beam.soracom.io:8888/

成功していれば、S3の対象バケット内に「test.png」がアップロードされていると思います

まとめ

最近よくIoT案件に関わらせていただいているのですが、そもそもセキュアな通信を前提としていないデバイスをクラウドなどと接続したいという要望が増えてきているように感じてきました
SORACOMさんのようなSIMに認証情報を持たせてセキュアな通信を担保してくれるようなサービスを最大限に活用してそういった要望にこたえていきたいと思う今日この頃でした
ただクラウド側での処理が複雑になればなるほどどこかで破綻するんじゃないかなーとも感じていたりいなかったり

あと、SORACOMBeamでAPIGatewayのパスを変数的に持たせることってできないんだろうか
APIGateway側でURLパスパラメータを使ってS3へアップロードするキー名を決めているので、今回のように固定にしてしまうと、アップロードされる画像がずっと上書きされてしまうので、あまりよろしくない・・・
S3のイベントで画像がアップロードされたタイミングでLambdaキックしてリネームしてアップロードしなおすという手もあるけど・・・
Beamの仕様を漁るか・・・
もし、何かよい方法があればコメント等で教えてください!

ではまた!

続きを読む