CloudFrontキャッシュを削除するシェルスクリプトを書いてみた。

AWS CLI を使えば、 AWS Console へアクセスすることなく
CloudFrontキャッシュの削除も行えます。

削除対象が多い場合はその対象を記したjsonファイルを用意して、
AWS CLI からそのjsonファイルを利用して CloudFrontキャッシュ の削除を行うのですが、
毎回書き換えが必要な項目がjsonファイル内にあります。

手作業で毎回書き換えてましたがさすがに面倒になってきたので
シェルスクリプトを書きました。

Macユーザーなので Macでの作業想定で書きます。

概要

AWS Consoleアクセスすれば、
Cloud Frontのキャッシュ手動削除はできるのですが、
面倒なので AWS CLI を使ってシェルスクリプトから
削除できるようにしたものです。

必要な準備

AWS CLI のローカルインストール

詳しいインストール手順はこちら

macOSの場合は Python 2.6.3 以降が必要なので

$ curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
$ sudo python get-pip.py

で pip をインストールしてから

$ sudo pip install awscli

して

$ aws help

でヘルプが出ればOKです。

AWS CLI の設定

CLI利用するのに専用の configure 設定を一度行う必要があります。
これも上記URLに設定方法があるのですが、

  • AWS Access Key ID(以下 AKID)
  • AWS Secret Access Key(以下 SAK)
  • region name
  • output format

の4つが必要です。
この設定プロファイルは

~/.aws/credentials # AKID と SAK

~/.aws/config # region name と output format

に入ります。

プロファイル名を付ければ複数のプロファイルを格納できます。
この2ファイルは1回設定しちゃえばあとはいじらなくてOKです!

↓こんな感じ

~/.aws/credentials
[profile hogehoge]
aws_access_key_id=****************      # AKID
aws_secret_access_key=****************  # SAK
~/.aws/config
[profile hogehoge]
region=ap-northeast-1                   # region name
output=json                             # output format

invalidation-batch の準備

削除対象を指定するバッチファイル(json)を作成します。
↓こんな感じです。

invbatch.json
{
    "Paths"  : {
        "Quantity": 5,
        "Items"  : [
            "/",
            "/sp/",
            "/css/main.css",
            "/js/libs.js",
            "/js/main.js"
        ]
    },
    "CallerReference": "YYYYMMDD-HHNNSS"
}

Itemsに削除対象を DocumentRoot からのパスで指定します。
ワイルドカード()も使えます。
この指定方法は **AWS Console
* での指定方法と一緒です。

注意点

CallerReference は毎回ユニークな値を入れておかなければいけません。
これが意外と面倒なのでこの部分の書き換え込みでシェルスクリプト化しました。

Note:
削除対象が変われば ItemsQuantity も変えなきゃです、、

このファイルは削除対象の変更がなければ、1回設定しちゃえばあとはいじらなくてOKです!

キャッシュ削除用シェルスクリプト設定

シェルスクリプトに実行権限$ chmod a+x ***を与えておいて、実行します。
中身は↓こんな感じです。

clear_cache.sh
#!/bin/bash

# 設定用変数
dist_id="*******************"   # AWS CloudFrontのDistribution ID
batch_json="invbatch.json"      # nvalidation-batchファイル名
profile="hogehoge"              # プロファイル名
url="http://hogehoge.jp"        # サイトURL

# 実行部分
echo "Move directory ..."
cd `dirname $0`
echo "Invbalidation-batch json update"
echo `less $batch_json_base | jq '.CallerReference |= "'$profile'_'$time_stamp'"'` > $batch_json
echo "CloudFront cache clear ..."
echo `aws cloudfront create-invalidation --distribution-id $dist_id --invalidation-batch "file://$batch_json" --profile $profile`

※ jq使ってjsonの書き換えを行なっているのでjqのインストールが必要です。

jqのインストール方法
https://stedolan.github.io/jq/download/

コード内のコメントにありますが、

  • AWSのCloudFrontDistribution ID
  • nvalidation-batch ファイル名
  • プロファイル名
  • サイトURL

が必要です。

このファイルは1回設定しちゃえばあとはいじらなくてOKです!

キャッシュ削除シェルスクリプト実行

$ ./clear_cache_shupure.sh
Move directory ...
Invbalidation-batch json update
CloudFront cache clear ...
{ "Invalidation": { "Status": "InProgress", "InvalidationBatch": { "Paths": { "Items": [ "/images/girls/*.png", "/", "/images/sakabar/*.png", "/images/events/*.jpg", "/sp/gekijo/", "/sp/sakabar/", "/gekijo/", "/sp/", "/images/girls/0924.png", "/css/main.css", "/sakabar/" ], "Quantity": 11 }, "CallerReference": "shupure_1506515279" }, "Id": "I2N11BY0PH6UI9", "CreateTime": "2017-09-27T12:28:03.125Z" }, "Location": "https://cloudfront.amazonaws.com/2017-03-25/distribution/E1JZGHRP8PFLBD/invalidation/I2N11BY0PH6UI9" }
CURL check ...
 % Total  % Received % Xferd Average Speed  Time  Time   Time Current
                 Dload Upload  Total  Spent  Left Speed
 0 50672  0   0  0   0   0   0 --:--:-- --:--:-- --:--:--   0
 X-Amz-Cf-Id: eHRIZxCp2k5-aecSw1ABCI-OB7aPtK_EEs4y0elK8bucqB6e1B-zrA==

CloudFront キャッシュ削除のレスポンスjsonが改行されてないのが気持ち悪いですが、
InProgressが表示されていればキャッシュ削除スタートしてます。

キャッシュ削除されてるかはcurlなどでレスポンスヘッダを確認してください。

$ curl -I http://hogehoge.jp

続きを読む

AWS CLIからS3オブジェクトをキー名の部分一致と最終更新日の範囲指定で抽出する

やりたいこと

同じprefixを持つ100個くらいのS3オブジェクト群から、プレフィクスの完全一致、キー名の部分一致、最終更新日の範囲指定により抽出して一括で操作したい。

こまったこと

  • S3のマネージメントコンソールでは、プレフィクス指定抽出のみ可能。

解決策

  • AWS CLIを使う。

    • aws cli s3api list-object の –prefix と –query で条件を指定して取得してパイプし、jq等でキー名を抽出する。

      • –query はJMESPathによる表記で、条件結合、部分一致、ソートなどが可能。

コマンド例

  • AWS CLIとjqのセットアップ済み環境を想定。
$ aws s3api list-object --bucket my_bucket --prefix 'my_prefix' --query 'Contents[?contains(Key, `“部分一致させる文字列“`) && LastModified >= `2017-08-20`][Key]' | 
jq 'flatten | "s3://my_bucket/" + .[]' | 
xargs -I{} echo 'target: {}'
#=> target: s3://my_bucket/my_prefix/2017-08-20.log
#=> target: s3://my_bucket/my_prefix/2017-08-21.log

情報源

続きを読む

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

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

注意点として OS を Linux、 tenancy を Shared に絞っています

ec2.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)' and
          product['attributes']['operatingSystem'] == 'Linux' and
          product['attributes']['tenancy'] == 'Shared')

        results[product['sku']] = {
            sku: product['sku'],
            location: product['attributes']['location'],
            instanceType: product['attributes']['instanceType'],
            instanceTypePrefix: product['attributes']['instanceType'].split('.')[0],
            instanceFamily: product['attributes']['instanceFamily'],
            vcpu: product['attributes']['vcpu'],
            memory: product['attributes']['memory'],
            storage: product['attributes']['storage'],
            clockSpeed: product['attributes']['clockSpeed'],
            networkPerformance: product['attributes']['networkPerformance'],
            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]][row[:instanceTypePrefix]] ||= []
    sorted_result[row[:currentGeneration]][row[:instanceFamily]][row[:instanceTypePrefix]].push row
end

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

p results.to_json

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

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

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

[
  {
    "sku": "Q4QTSF7H37JFW9ER",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "c3.large",
    "instanceTypePrefix": "c3",
    "instanceFamily": "Compute optimized",
    "vcpu": "2",
    "memory": "3.75 GiB",
    "storage": "2 x 16 SSD",
    "clockSpeed": "2.8 GHz",
    "networkPerformance": "Moderate",
    "ecu": "7",
    "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": "753",
    "price_reserved_3year_purchased_all_upfront": "1528"
  },
  {
    "sku": "HTNXMK8Z5YHMU737",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "c3.xlarge",
    "instanceTypePrefix": "c3",
    "instanceFamily": "Compute optimized",
    "vcpu": "4",
    "memory": "7.5 GiB",
    "storage": "2 x 40 SSD",
    "clockSpeed": "2.8 GHz",
    "networkPerformance": "Moderate",
    "ecu": "14",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "0.25",
    "price_per_day": "6.0",
    "price_per_month": "180.0",
    "price_reserved_1year_purchased_all_upfront": "1505",
    "price_reserved_3year_purchased_all_upfront": "3032"
  },
  {
    "sku": "YR67H6NVBRN37HRZ",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "c3.2xlarge",
    "instanceTypePrefix": "c3",
    "instanceFamily": "Compute optimized",
    "vcpu": "8",
    "memory": "15 GiB",
    "storage": "2 x 80 SSD",
    "clockSpeed": "2.8 GHz",
    "networkPerformance": "High",
    "ecu": "28",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "0.51",
    "price_per_day": "12.24",
    "price_per_month": "367.2",
    "price_reserved_1year_purchased_all_upfront": "3012",
    "price_reserved_3year_purchased_all_upfront": "8132"
  },
...
]

続きを読む

現在のインスタンス料金を取得する 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"
  }
]

続きを読む

Amazon RDSの情報からmysql_config_editorを一気に設定するスニペット

AWS CLIとJQを使ってmysql_config_editorのコマンドを生成するスニペットです。
PWは自分で入れる感じです。

$ aws rds describe-db-instances | jq -r '.DBInstances[] | ["mysql_config_editor set", "--login_path="+.DBInstanceIdentifier, "--host="+.Endpoint.Address, "--user="+.MasterUsername, "--password"] | join(" ")' | xargs -I{} sh -c 'echo {}; {}'
mysql_config_editor set --login_path=hogedb --host=hogedb.aaaaaaaa.ap-northeast-1.rds.amazonaws.com --user=masteruser --password
Enter password:

続きを読む

現在のインスタンス料金を取得する 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"
  }
]

続きを読む

既存のDynamoDBのテーブルをjsonフォーマットに変換するワンライナー

はじめに

  • AWS cliでテーブル作っていたのですが、jsonで作っておくべきだった・・・。と大変後悔しました。
  • 既存のDynamoDBのテーブルからjsonを出力するスクリプトの記事を見つけたのですが、GlobalSecondaryIndex、LocalSecondaryIndexが無いテーブルの場合はエラーになってしまいますので少し改良したのでご紹介します。
  • このスクリプトをうまく利用することでテーブルの差分をみるのに使えたりするのでオススメです。

スクリプト

  • ここではDynamoDBLocalにあるAssetというテーブルをAsset.jsonというファイルに出力しています。
  • describe-tableでは余計なものがいっぱいついてくるので要素を削除しています。
aws dynamodb describe-table --table-name Asset --endpoint-url http://localhost:8000 |
jq '.Table' |
jq 'del(.TableArn)' |
jq 'del(.GlobalSecondaryIndexes[]?.ItemCount)' |
jq 'del(.GlobalSecondaryIndexes[]?.IndexStatus)' |
jq 'del(.GlobalSecondaryIndexes[]?.IndexArn)' |
jq 'del(.GlobalSecondaryIndexes[]?.IndexSizeBytes)' |
jq 'del(.GlobalSecondaryIndexes[]?.ProvisionedThroughput.NumberOfDecreasesToday)' |
jq 'del(.GlobalSecondaryIndexes[]?.ProvisionedThroughput.LastIncreaseDateTime)' |
jq 'del(.GlobalSecondaryIndexes[]?.ProvisionedThroughput.LastDecreaseDateTime)' |
jq 'del(.LocalSecondaryIndexes[]?.IndexStatus)' |
jq 'del(.LocalSecondaryIndexes[]?.IndexArn)' |
jq 'del(.LocalSecondaryIndexes[]?.ItemCount)' |
jq 'del(.LocalSecondaryIndexes[]?.IndexSizeBytes)' |
jq 'del(.LocalSecondaryIndexes[]?.ProvisionedThroughput.NumberOfDecreasesToday)' |
jq 'del(.LocalSecondaryIndexes[]?.ProvisionedThroughput.LastIncreaseDateTime)' |
jq 'del(.LocalSecondaryIndexes[]?.ProvisionedThroughput.LastDecreaseDateTime)' |
jq 'del(.ProvisionedThroughput.NumberOfDecreasesToday)' |
jq 'del(.ProvisionedThroughput.LastIncreaseDateTime)' |
jq 'del(.ProvisionedThroughput.LastDecreaseDateTime)' |
jq 'del(.TableSizeBytes)' |
jq 'del(.TableStatus)' |
jq 'del(.ItemCount)' |
jq 'del(.IndexArn)' |
jq 'del(.CreationDateTime)' > Asset.json

出力されたスクリプトを利用してcreate-tableする

aws dynamodb create-table --endpoint-url http://localhost:8000 --table-name Asset --cli-input-json file://Asset.json

続きを読む

dynamodb-autoscaling検証

概要

先日のアップデートでdynamodbでautoscalingの機能が実装されました。
https://aws.amazon.com/jp/blogs/news/new-auto-scaling-for-amazon-dynamodb/

NoSQLデータベースのベンチマークツールであるycsbを用いてdynamodb-autoscalingの動作を検証できます。
今回は新規に追加された項目「ターゲット使用率」がどのような影響を及ぼすかについて検証します。

環境準備

環境準備に伴い、下記を参考にさせていただきました。

http://qiita.com/noralife/items/35a956f682b1aca475f6
http://dev.classmethod.jp/cloud/aws/attack_to_dynamodb_using_ycsb/
http://imai-factory.hatenablog.com/entry/2013/04/05/010858

amazon linux version

[ec2-user@dynamo-ec2 ycsb-0.12.0]$ cat /etc/system-release
Amazon Linux AMI release 2017.03

openjdk-develインストール

$ sudo yum -y install java-1.7.0-openjdk-devel

ycsb導入

・ダウンロード
$ wget https://github.com/brianfrankcooper/YCSB/releases/download/0.12.0/ycsb-0.12.0.tar.gz

・展開、移動
$ tar xfz ycsb-0.12.0.tar.gz
$ cd ycsb-0.12.0/

・ファイル用のディレクトリ準備
$ mkdir -p dynamodb/conf/

・ベンチマーク対象テーブル(testtable)のプライマリキーを確認しておく
$ aws dynamodb describe-table --table-name testtable | jq -r '.Table.AttributeDefinitions[].AttributeName'
name

YCSB概要

テストデータをdynamodbに読み込ませて

./bin/ycsb.sh load dynamodb -P workloads/dyamodb -P dynamodb/conf/dynamodb.properties

テストデータを使って負荷をかける。

./bin/ycsb.sh run dynamodb -P workloads/dyamodb -P dynamodb/conf/dynamodb.properties

設定ファイルは下記の3つ

■dynamodb/conf/dynamodb.properties
対象のDynamoDBの情報を設定。変更する部分はプライマリキーくらいかと。
クレデンシャル情報が記載されたファイルもこの中で指定する。

$ cat dynamodb/conf/dynamodb.properties
dynamodb.awsCredentialsFile = dynamodb/conf/AWSCredentials.properties
dynamodb.primaryKey = name
dynamodb.endpoint = http://dynamodb.ap-northeast-1.amazonaws.com

■dynamodb/conf/AWSCredentials.properties
クレデンシャル情報を記載。

$ cat dynamodb/conf/AWSCredentials.properties
accessKey = XXXXXXXXXXXXXXXXXXXX
secretKey = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

■workloads/(任意のファイル名)
投入するデータ、実施するベンチマークのステータスを設定。
値はテンプレートworkloads/workload_templateを参考。
※operationcountは1,000,000以上が良いかと。10,000で実行したらスケーリングする前に終わってしまいました……

$ cat workloads/dyamodb
workload=com.yahoo.ycsb.workloads.CoreWorkload #デフォルト

recordcount=1000 #テーブルにセットするレコード数
operationcount=2000000 #load時のオペレーション数

insertstart=0 #デフォルト
fieldcount=10 #フィールド数、デフォルト
fieldlength=100 #レコードの長さ、デフォルト

readallfields=true #デフォルト
writeallfields=false #デフォルト

table=testtable #テーブル名

fieldlengthdistribution=constant #デフォルト

#load時のオペレーション比率(read80%, update15%, insert5%)
readproportion=0.8
updateproportion=0.15
insertproportion=0.05

#以下テンプレートの値のまま設定
readmodifywriteproportion=0
scanproportion=0
maxscanlength=1000
scanlengthdistribution=uniform
insertorder=hashed
requestdistribution=zipfian
hotspotdatafraction=0.2
hotspotopnfraction=0.8

measurementtype=histogram
histogram.buckets=1000
timeseries.granularity=1000

検証開始

初期のdynamodbキャパシティ設定

1000レコード書き込むので、書き込み容量ユニット(以下WCU)を10確保。
WCU1だとスロットルが発生して時間がかかります……。

初期設定.PNG

テーブルにテストデータを読み込ませる

1回だけ。10分ほどかかります。

$ ./bin/ycsb.sh load dynamodb -P workloads/dyamodb -P dynamodb/conf/dynamodb.properties

auto-scaling設定

いよいよauto-scaling設定です。
RCU/WCUを下げるオペレーションは最大9回/日になっていますが条件があります。
↓の記事を参考にさせていただきました。
http://qiita.com/mokrai/items/6864b8a723a2728565fc
検証する場合は無駄に下げないように初期値を考慮しましょう。

as1.PNG

・ターゲット使用率
スケーリングの基準になる値です。以降で検証します。

・IAMロール
[新しいロール]を選択すれば問題ありません。
下記の管理ポリシーが付与されたロールが作成されます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:DescribeTable",
                "dynamodb:UpdateTable",
                "cloudwatch:PutMetricAlarm",
                "cloudwatch:DescribeAlarms",
                "cloudwatch:GetMetricStatistics",
                "cloudwatch:SetAlarmState",
                "cloudwatch:DeleteAlarms"
            ],
            "Resource": "*"
        }
    ]
}

ロールからもわかるように、auto-scalingはcloudwatchの値を評価の基準としています。
auto-scaling設定中は動的なcloudwatchメトリクスが作成され、auto-scaling設定解除後に削除されます。

ターゲット使用率70%の場合

下記設定でcloudwatchの推移を確認します。

auto-scaling設定 RCU WCU
ターゲット使用率(%) 70 70
min(ユニット) 10 10
max(ユニット) 10000 10000

キャパシティの推移

推移は下記の通り

RCU
70rcu.PNG

WCU
70wcu.PNG

ターゲット使用率20%の場合

下記設定でcloudwatchの推移を確認します。

auto-scaling設定 RCU WCU
ターゲット使用率(%) 20 20
min(ユニット) 10 10
max(ユニット) 10000 10000

20rcu.PNG

20wcu.PNG

終わりに

負荷のかけ方が一定の場合はキャパシティーの増減が安定したら変化しないので、
そのあたりの調整が必要だと感じました。
cloudwatchメトリクスが動的な値で作成され、それらが基準になってスケーリングされています。
サポートに確認したところ、閾値を超えてのキャパシティ設定もできるとのことですが、その場合は次のタイミングでスケーリングされてしまうとのことです(未検証)。

続きを読む

aws-cli コマンド一覧(随時追記)

概要

ついつい忘れがちなので自分で使ったことあるコマンドを随時追記していきます。

※全てのコマンドをまとめているわけではないので、当記事に書いていないコマンドについては公式ドキュメントをご覧ください。
公式ドキュメント

また、aws-cliのレスポンスは基本的にJsonのため(設定で変更可能らしいが)、 jq というJsonフォーマット用ツールを合わせて使うと幸せになれます。

設定ファイル

cliを利用する上では以下のファイルが必要。
aws-cliをインストール後に特定のコマンドを打つことで生成することが出来る。

cofig

cliを使う上での設定を記載したファイル。

~/.aws/config
[default]
output = json
region = ap-northeast-1

credentials

AWSに接続するための認証情報

~/.aws/config
[default]
output = json
region = ap-northeast-1

コマンド

configure

aws-cliを使う上でのファイルを生成する

configure

設定ファイルを生成します。
Document

command
aws configure

set

cliの設定を追加します。
Document

command
aws configure set preview.cloudfront true

iam

IAM関連を操作するコマンド

create-user

IAMユーザーを新規作成します。
Document

引数 概要
user-name IAMユーザー名
command
aws iam create-user \
--user-name sample-user

delete-user

IAMユーザーを削除します。
※ポリシーが設定されている場合はエラーになります。
Document

引数 概要
user-name IAMユーザー名
command
aws iam delete-user \
--user-name sample-user

get-user

IAMユーザーを取得します。
Document

引数 概要
user-name IAMユーザー名
command
aws iam get-user \
--user-name sample-user

put-user-policy

IAMユーザーにポリシーを追加します。
Document

引数 概要
user-name IAMユーザー名
policy-name ポリシー名
policy-document ポリシールールが記載されたJsonファイルのパス
command
aws iam put-user-policy \
--user-name sample-user \
--policy-name sample-policy \
--policy-document  file://user_policy.json
user_policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt1501770149214",
      "Action": "cloudwatch:*",
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}

※Amazonのポリシージェネレーターを使うことで作成可能です。
ポリシージェネレーター

delete-user-policy

IAMユーザーに設定されているポリシーを削除します。
Document

引数 概要
user-name IAMユーザー名
policy-name ポリシー名
command
aws iam delete-user-policy \
--user-name sample-user \
--policy-name sample-policy 

create-access-key

IAMユーザーのアクセスキーを新規作成します。
Document

引数 概要
user-name IAMユーザー名
command
aws iam create-access-key \
--user-name sample-user 

list-users

IAMユーザーの一覧を取得します。
Document

command
aws iam list-usera 

create-role

Roleを新規作成します。
Document

引数 概要
user-name IAMユーザー名
command
aws iam create-role \
--role-name sample-role \
--assume-role-policy-document file://role.json
role.json
{
  "Version": "2012-10-17",
  "Statement": [
     {
       "Action": "sts:AssumeRole",
       "Principal": {
         "Service": "lambda.amazonaws.com"
        },
        "Effect": "Allow",
        "Sid": ""
     }
  ]
}

attach-role-policy

Roleにポリシーを追加します。
Document

引数 概要
user-name IAMユーザー名
policy-arn リソース名
command
aws iam attach-role-policy \
--role-name sample-role \
--policy-arn "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"

s3

S3関連を操作するコマンド

mb

バケットを新規作成します。
Document

command
aws s3 mb s3://sample.bucket

website

バケットを静的Webサイトホスティング化する
Document

引数 概要
index-document ディレクトリに対してリクエストが来た際に自動で追加する接尾辞
command
aws s3 website s3://sample.bucket \
--index-document index.html

s3api

Amazon S3 API関連を操作するコマンド

put-bucket-notification-configuration

バケットにオブジェクトがPutされた時の通知設定
Document

引数 概要
bucket 対象バケット名
notification-configuration 通知設定ファイル
command
aws s3api put-bucket-notification-configuration \
--bucket sample.bucket \
--notification-configuration file://config.json
config.json
{
    "LambdaFunctionConfigurations": [
        {
            "LambdaFunctionArn": "arn:aws:lambda:xxxx",
            "Id": "sample_config",
            "Events": [
                "s3:ObjectCreated:*"
            ]
        }
    ]
}

cloudfront

CloudFront関連を操作するコマンド

create-distribution

CloudFrontを新規作成します。
Document

引数 概要
cli-input-json 新規作成用Jsonファイルのパス ※file://${パス}にすること
command
aws cloudfront create-distribution \
--cli-input-json file://distribution.json
distribution.json
{
  "Id": "",
  "IfMatch": "",
  "DistributionConfig": {
    "Comment": "${コメント}",
    "CacheBehaviors": {
      "Quantity": 0
    },
    "IsIPV6Enabled": true,
    "Logging": {
      "Bucket": "",
      "Prefix": "",
      "Enabled": false,
      "IncludeCookies": false
    },
    "WebACLId": "",
    "Origins": {
      "Items": [
        {
          "S3OriginConfig": {
            "OriginAccessIdentity": ""
          },
          "OriginPath": "",
          "CustomHeaders": {
            "Quantity": 0
          },
          "Id": "${ID}",
          "DomainName": "${ドメイン名}"
        }
      ],
      "Quantity": 1
    },
    "DefaultRootObject": "index.html",
    "PriceClass": "PriceClass_All",
    "Enabled": true,
    "DefaultCacheBehavior": {
      "TrustedSigners": {
        "Enabled": false,
        "Quantity": 0
      },
      "LambdaFunctionAssociations": {
        "Quantity": 0
      },
      "TargetOriginId": "${S3のバケット名}",
      "ViewerProtocolPolicy": "allow-all",
      "ForwardedValues": {
        "Headers": {
          "Quantity": 0
        },
        "Cookies": {
          "Forward": "all"
        },
        "QueryStringCacheKeys": {
          "Quantity": 0
        },
        "QueryString": true
      },
      "MaxTTL": 300,
      "SmoothStreaming": false,
      "DefaultTTL": 300,
      "AllowedMethods": {
        "Items": [
          "HEAD",
          "GET"
        ],
        "CachedMethods": {
          "Items": [
            "HEAD",
            "GET"
          ],
          "Quantity": 2
        },
        "Quantity": 2
      },
      "MinTTL": 300,
      "Compress": false
    },
    "CallerReference": "${一意となる文字列}",
    "ViewerCertificate": {
      "CloudFrontDefaultCertificate": true,
      "MinimumProtocolVersion": "SSLv3",
      "CertificateSource": "cloudfront"
    },
    "CustomErrorResponses": {
      "Quantity": 0
    },
    "HttpVersion": "http2",
    "Restrictions": {
      "GeoRestriction": {
        "RestrictionType": "none",
        "Quantity": 0
      }
    },
    "Aliases": {
      "Items": [
        "${エイリアス名}"
      ],
      "Quantity": 1
    }
  }
}

ec2

EC2関連を操作するコマンド

create-security-group

セキュリティグループを新規作成します。
Document

引数 概要
group-name セキュリティグループ名
description 説明
command
aws ec2 create-security-group \
--group-name sample-sg-group \
--description 'サンプル用のセキュリティグループ'

authorize-security-group-ingress

セキュリティグループのルールを定義します。
Document

引数 概要
group-name セキュリティグループ名
description 説明
port 対象のポート番号
cidr 許可するIPアドレス
command
aws ec2 authorize-security-group-ingress \
--group-name sample-sg-group \
--protocol tcp \
--port 22 \
--cidr "0.0.0.0/0" 

allocate-address

ElasticIPを新規作成します。
Document

引数 概要
domain vpsを使う際には「vpc」を指定する
command
aws ec2 allocate-address \
--domain vpc

run-instances

インスタンスを新規作成し起動します。
Document

引数 概要
image-id インスタンスのベースとなるマシンイメージ
count 起動するインスタンスの数
instance-type 作成するインスタンスの種別
key-name 使用するキーペア名
security-groups 紐付けるセキュリティブループ名(複数可)
command
aws ec2 run-instances \
--image-id ami-3bd3c45c \
--count 1 \
--instance-type t2.micro \
--key-name sample_key_pair \
--security-groups sample-sg-group

create-tags

対象のインスタンスにタグを新規追加します。
Document

引数 概要
resources 対象のインスタンスID
tags Key/Valueの形式で追加していく
instance-type 作成するインスタンスの種別
key-name 使用するキーペア名
security-groups 紐付けるセキュリティブループ名(複数可)
command
aws ec2 create-tags \
--resources i-xxxx \
--tags Key=Name,Value=sample-instance

associate-address

ElasticIPとインスタンスを紐付けます。
Document

引数 概要
allocation-id 対象のElasticIP ID
instance 対象のインスタンスID
command
aws ec2 associate-address \
--allocation-id eipalloc-xxxx \
--instance i-xxxx

release-address

ElasticIPを解放します。
Document

引数 概要
allocation-id 対象のElasticIP ID
command
aws ec2 release-address \
--allocation-id eipalloc-xxxx

modify-instance-attribute

インスタンスの属性を更新します。
Document

引数 概要
groups セキュリティブループID(複数可)
instance-id 対象のインスタンスID
command
aws ec2 modify-instance-attribute  \
--groups sample-sg-group  \
--instance-id i-xxxx

terminate-instances

インスタンスを停止して削除します。
Document

引数 概要
instance-ids 対象のインスタンスID(複数可)
command
aws ec2 terminate-instances \
--instance-ids i-xxxx

lambda

Lambda関連を操作するコマンド

create-function

Lambda関数を新規作成します。
Document

引数 概要
function-name Lambda関数名
zip-file zipパス
role 他のAWSサービスに接続するためのロール
handler 実行するためのコード内関数
runtime タイムアウトの定義(秒)
timeout アップロードするzipへのパス
memory-size Lambda関数実行時のメモリ割り当て
command
aws lambda create-function \
--function-name SampleFunction \
--zip-file fileb://SampleFunction.zip \
--role arn:aws:iam::xxxx:role/sample-role \
--handler SampleFunction.handler \
--runtime python2.7 \
--timeout 10 \
--memory-size 1024

delete-function

Lambda関数を削除します。
Document

引数 概要
function-name Lambda関数名
command
aws lambda delete-function \
--function-name SampleFunction 

add-permission

Lambda関数への権限を追加います。
Document

引数 概要
function-name Lambda関数名
statement-id 一意となるステートメント名
action 許可させるLambdaアクション
principal プリンシパルの設定
source-arn 関数呼び出しを許可するAmazonリソース名
command
aws lambda add-permission \
--function-name "sample-function" \
--statement-id "sample-statement" \
--action "lambda:InvokeFunction" \
--principal "s3.amazonaws.com" \
--source-arn "arn:aws:s3:::sample.bucket"

続きを読む

AnsibleでAWS操作 ②EC2インスタンス作成編

AnsibleでAWS操作シリーズ

  1. aws-cliインストール編
  2. EC2インスタンス作成編

やりたかったこと

  • EC2インスタンスに紐付けるSecurityGroupの作成/設定
  • EC2インスタンスに紐付けるElastic IPの作成/設定/削除
  • EC2インスタンスの作成/削除
  • Elastic IPの関連付け
  • GUIを使わずに黒い画面でコマンドを「ッターーン!」してかっこつけたい

やったこと

前提

  • CIサーバー(ansible実行サーバー)構築済み
  • CLIサーバー(aws-cli実行サーバー)構築済み
  • Ansibleインストール済み
  • aws-cliインストール済み
  • jqインストール済み
  • 各サーバーへのSSH接続設定済み

${~}は各環境に合わせて値を設定してください。

作業フロー

  1. create-aws-ec2-ssh-security-group.ymlを実行

    • 戻り値のGroupIdの値をvars/all.ymlのSECURITY_GROUP_IDに指定
  2. create-aws-ec2-instance.ymlを実行

    • 戻り値のInstanceIdAllocationIdをそれぞれINSTANCE_IDELASTIC_IP_IDに指定
  3. setup-aws-ec2-instance.ymlを実行

jqで取得した各種IDを使って一つのtaskでやろうと思いましたが、Instance作成直後はElasticIPの紐付けが出来ないっぽいので、playbookを分けています。

ディレクトリ構成


├── ansible.cfg
├── create-aws-ec2-instance.yml
├── create-aws-ec2-ssh-security-group.yml
├── inventory
│   └── production
│       └── inventory
├── roles
│   ├── create-aws-ec2-elastic-ip
│   │   └── tasks
│   │       └── main.yml
│   ├── create-aws-ec2-instance
│   │   └── tasks
│   │       └── main.yml
│   ├── create-aws-ec2-ssh-security-group
│   │   └── tasks
│   │       └── main.yml
│   ├── setup-aws-ec2-elastic-ip
│   │   └── tasks
│   │       └── main.yml
│   └── setup-aws-ec2-instance
│       └── tasks
│           └── main.yml
├── setup-aws-ec2-instance.yml
├── vars
│   └── all.yml
└── view-aws-ec2-instance.yml

Ansible構成ファイル

inventory

inventory/production/inventory
[ciservers]
${CIサーバーホスト}

[cliservers]
${CLIサーバーホスト}

[all:vars]
ENV=production

vars

vars/all.yml
AWS_EC2:
  SECURITY_GROUP_ID: ${セキュリティグループID}
  SECURITY_GROUP_NAME: ${セキュリティグループ名}
  SECURITY_GROUP_DESCRIPTION: ${セキュリティグループ概要}
  IP_ADDRESS: ${IPアドレス}
  KEY_PAIR_NAME: ${キーペア名}
  AMI_ID: ${AMI ID}
  INSTANCE_TYPE: ${インスタンスタイプ}
  INSTANCE_ID: ${インスタンスID}
  INSTANCE_NAME: ${インスタンス名}
  ALLOCATION_ID: ${Elastic IP ID}

playbook

create-aws-ec2-ssh-security-group.yml
- hosts: cliservers
  roles:
    - create-aws-ec2-ssh-security-group
  vars_files:
    - vars/all.yml
create-aws-ec2-instance.yml
- hosts: cliservers
  roles:
    - create-aws-ec2-instance
    - create-aws-ec2-elastic-ip
  vars_files:
    - vars/all.yml
setup-aws-ec2-instance.yml
- hosts: cliservers
  roles:
    - setup-aws-ec2-instance
    - setup-aws-ec2-elastic-ip
  vars_files:
    - vars/all.yml

tasks

role/create-aws-ec2-ssh-security-group/tasks/main.yml
- name: "Create SSH Security Group"
  shell: |
    aws ec2 create-security-group \
    --group-name {{ AWS_EC2.SECURITY_GROUP_NAME }} \
    --description '{{ AWS_EC2.SECURITY_GROUP_DESCRIPTION }}'
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always

- name: "Setup SSH Security Group"
  shell: |
    aws ec2 authorize-security-group-ingress \
    --group-name {{ AWS_EC2.SECURITY_GROUP_NAME }} \
    --protocol tcp \
    --port 22 \
    --cidr "{{ AWS_EC2.IP_ADDRESS }}/0" \
    | jq '.'
  tags:
    - always
role/create-aws-ec2-instance/tasks/main.yml
- name: "Create Instanse"
  shell: |
    aws ec2 run-instances \
    --image-id {{ AWS_EC2.AMI_ID }} \
    --count 1 \
    --instance-type {{ AWS_EC2.INSTANCE_TYPE }}  \
    --key-name {{ AWS_EC2.KEY_PAIR_NAME }} \
    --security-groups {{ AWS_EC2.SECURITY_GROUP_NAME }} \
    | jq -r '.Instances[] | { InstanceId }'
  register: create_instance_result
  changed_when: False

- debug: var=create_instance_result.stdout_lines
  when: create_instance_result | success
  tags:
    - always
role/create-aws-ec2-elastic-ip/tasks/main.yml
- name: "Create Elastic IP"
  shell: |
    aws ec2 allocate-address \
    --domain vpc \
    | jq -r '{ AWS_EC2.ALLOCATION_ID }'
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/setup-aws-ec2-instance/tasks/main.yml
- name: "Create Tags"
  shell: |
    aws ec2 create-tags \
    --resources {{ AWS_EC2.INSTANCE_ID }} \
    --tags Key=Name,Value={{ AWS_EC2.INSTANCE_NAME }}
  register: result
  changed_when: False

- debug: var=result.stdout_lines # stdout => stdout_lines
  when: result | success
  tags:
    - always

role/setup-aws-ec2-elastic-ip/tasks/main.yml
- name: "Setup Elastic IP"
  shell: |
    aws ec2 associate-address \
    --allocation-id {{ AWS_EC2.ALLOCATION_ID }} \
    --instance {{ AWS_EC2.INSTANCE_ID }}
  register: result
  changed_when: False

- debug: var=result.stdout_lines # stdout => stdout_lines
  when: result | success
  tags:
    - always

実行コマンド

command
ansible-playbook -i inventory/production create-aws-ec2-ssh-security-group.yml
command
ansible-playbook -i inventory/production create-aws-ec2-instance.yml
command
ansible-playbook -i inventory/production setup-aws-ec2-instance.yml

正常に処理が終了したらインストール編で実行したplaybookを実行してみてください。
インスタンス一覧取得playbook

新しいインスタンスの情報が取得出来るはずです。

おまけ インスタンス及びElasticIPの削除

delete-aws-ec2-instance.yml
- hosts: cliservers
  roles:
    - delete-aws-ec2-instance
    - delete-aws-ec2-elastic-ip
  vars_files:
    - vars/all.yml

tasks

role/delete-aws-ec2-instance/tasks/main.yml
- name: "Delete EC2 Instanse"
  shell: |
    aws ec2 terminate-instances \
    --instance-ids {{ AWS_EC2.INSTANCE_ID }}
  register: result
  changed_when: False

- debug: var=result.stdout_lines # stdout => stdout_lines
  when: result | success
  tags:
    - always
role/delete-aws-ec2-instance/tasks/main.yml
- name: "Delete Elastic IP"
  shell: |
    aws ec2 release-address \
    --allocation-id {{ AWS_EC2.ALLOCATION_ID }}
  register: result
  changed_when: False

- debug: var=result.stdout_lines # stdout => stdout_lines
  when: result | success
  tags:
    - always
command
ansible-playbook -i inventory/production delete-aws-ec2-instance.yml

終わりに

インスタンスの作成や関連する設定もCLIから実行することが出来ます。
さらに各種設定値等もファイル内に残るので管理がしやすいです!

次は、S3バケットの作成をAnsible+aws-cliでやってみようと思います。

じゃあの。

続きを読む