デプロイしてExecJS::RuntimeError: SyntaxError: Unexpected character ‘`’ が出たとき

【バージョン】

ruby 2.3.1
Rails 5.0.4

【やろうとしたこと】

とあるjsファイルを作成してcapistranoを使用してAWSのサーバーにデプロイした。
すると「ExecJS::RuntimeError: SyntaxError: Unexpected character ‘`’ 」とエラーが出てしまった。

以下 エラー時のJSファイル。

  function buildHTML(product){
    var html = `<a class="item-link" href="/products/${product.id}">`
  if ("受付中" == $(product.status).selector) {
  html += `<p class="order_now">${product.status}</p>`+
  `<p class="no_underline">残り  ${product.rem_num}</p>`
  }
  if ("受付終了" == $(product.status).selector) {
  html += `<p class="order_end">${product.status}</p>`+
  `<p class="no_underline">申し込み上限に達しました</p>`
  }
  html += `<section class="items">`+
    `<figure class="item-image">`+
      `<img>`+
        `<img height="223" width="340" alt="プロダクト写真" src = "${product.image.url}" >`+
    `</figure>`+
    `<h2 class="top_product_title">${product.title}</h2>`+
    `<div class="info">`+
      `<p class="place">`+
       `<i class="fa fa-map-marker"></i>`+
        `${product.area}`+
      `</p>`+
      `<p class="price">`+
        `<i class="fa fa-shopping-bag"></i>`+
        `¥ ${product.price}`+
      `</p>`+
    `</div>`+
  `</section>`+
`</a>`;
    return html;
  }
....以下略

ExecJS::RuntimeError: SyntaxError: Unexpected character ‘`’ 」

「`(バッククォート)」を使うんじゃあない、と言われている。
なるほど。
じゃあシングルクォートか、ダブルクォートを使えばいいんだなと思ったがそうもいかなかった。
${product.status}の変数の中身が表示されないのだ。

どうしよーと思ってたら、ちょっと書き方を変えたら解決した。
考えてみればシンプル。
以外にインターネットで漁ってても同じようなエラーが出てこなかったので書いてみる。

(こんなことに躓くのはやはり開発経験2ヶ月の初心者だからか・・・)

以下はエラー解消後のJSファイル。

$(function() {
var itemlink = $(".item-link");
var all = "all"

  function buildHTML(product){
    var html = '<a class="item-link" href="/products/' + product.id + '">'
  if ("受付中" == $(product.status).selector) {
  html += '<p class="order_now">' + product.status + '</p>'+
  '<p class="no_underline">残り' + product.rem_num + '</p>'
  }
  if ("受付終了" == $(product.status).selector) {
  html += '<p class="order_end">' + product.status + '</p>'+
  '<p class="no_underline">申し込み上限に達しました</p>'
  }
  html += '<section class="items">'+
    '<figure class="item-image">'+
      '<img>'+
        '<img height="223" width="340" alt="プロダクト写真" src = "' + product.image.url + '" >'+
    '</figure>'+
    '<h2 class="top_product_title">' + product.title + '</h2>'+
    '<div class="info">'+
      '<p class="place">'+
       '<i class="fa fa-map-marker"></i>'
         + product.area +
      '</p>'+
      '<p class="price">'+
        '<i class="fa fa-shopping-bag"></i>'+
        '¥' + product.price +
      '</p>'+
    '</div>'+
  '</section>'+
'</a>';
....以下略

変更前

「`(バッククォート)」
「${product.status}」

変更後

「'(シングルクォート)」
「+ product.status +」

これで問題なくデプロイできた。
もっとうまいやり方があるらしい。
何かのgemを消すとうまくいくとか。
知ってたら誰か教えてください。

続きを読む

AnsibleでAWS操作 Certificate Manager編

AnsibleでAWS操作シリーズ

  1. aws-cliインストール編
  2. EC2インスタンス編
  3. S3バケット編
  4. CloudFrontディストリビューション編
  5. Simple Email Service編
  6. Certificate Manager編

関連記事

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

やりたかったこと

  • ACMにて独自ドメインへのSSL証明書を発行
  • SSL証明書をCloudFrontに設定
  • GUIを使わずに黒い画面でコマンドを「ッターーン!」してかっこつけたい

やったこと

前提

  • CIサーバー(ansible実行サーバー)構築済み
  • CLIサーバー(aws-cli実行サーバー)構築済み
  • Ansibleインストール済み
  • aws-cliインストール済み
  • 各サーバーへのSSH接続設定済み
  • 独自ドメイン取得済み
  • SESの設定済み
  • CFとS3の設定済み

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

作業フロー

1. SSL証明書発行リクエストの作成

command
ansible-playbook -i inventory/production create-aws-acm-request.yml

戻り値の値を控えます。

2. SESに保存された認証確認メールから認証用URLを取得し、ブラウザからapproveする

command
ansible-playbook -i inventory/production view-aws-acm-notice-mail-url.yml

戻り値のURLにブラウザからアクセスする必要あり

3. CloudFrontの設定を更新して、httpsでアクセス出来るようにする

command
ansible-playbook -i inventory/production update-aws-cf.yml


## ディレクトリ構成
```text

├── ansible.cfg
├── create-aws-acm-request.yml
├── templates
│   └── production
│       └── cf
│           └── update.j2
├── inventory
│   └── production
│       └── inventory
├── roles
│   ├── create-aws-acm-request
│   │   └── tasks
│   │       └── main.yml
│   ├── update-aws-cf
│   │   └── tasks
│   │       └── main.yml
│   └── view-aws-acm-notice-mail-url
│       └── tasks
│           └── main.yml
├── update-aws-cf.yml
├── view-aws-acm-notice-mail-url.yml
└── vars
    └── all.yml

Ansible構成ファイル

inventory

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

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

[all:vars]
ENV=production

vars

vars/all.yml
AWS:
  ACM:
    ARN: ${ACMリクエストのARN}
    REGION: us-west-2
    VERIFICATION_MAIL_OBJECT: ${認証メールオブジェクト名}
  CF:
    DISTRIBUTION:
      ID: ${ディストリビューションID}
      ORIGIN_ID: ${オリジンID}
      E_TAG: ${Eタグ}
  S3:
    BUCKET:
      NAME: ${バケット名}
    END_POINT: ${S3バケットの静的ホスティングエンドポイント}
DOMAIN:
  MAIN:
    NAME: ${ドメイン名}
  SUB:
    NAME: ${サブドメイン名}

templates

production/cf/update.j2
{
  "DistributionConfig": {
    "Comment": "Https Bucket.",
    "CacheBehaviors": {
      "Quantity": 0
    },
    "IsIPV6Enabled": true,
    "Logging": {
      "Bucket": "",
      "Prefix": "",
      "Enabled": false,
      "IncludeCookies": false
    },
    "WebACLId": "",
    "Origins": {
      "Items": [
        {
          "OriginPath": "",
          "CustomOriginConfig": {
            "OriginSslProtocols": {
              "Items": [
                "TLSv1",
                "TLSv1.1",
                "TLSv1.2"
              ],
              "Quantity": 3
            },
            "OriginProtocolPolicy": "http-only",
            "OriginReadTimeout": 30,
            "HTTPPort": 80,
            "HTTPSPort": 443,
            "OriginKeepaliveTimeout": 5
          },
          "CustomHeaders": {
            "Quantity": 0
          },
          "Id": "{{ AWS.CF.ID }}",
          "DomainName": "{{ AWS.S3.END_POINT }}"
        }
      ],
      "Quantity": 1
    },
    "DefaultRootObject": "index.html",
    "PriceClass": "PriceClass_All",
    "Enabled": true,
    "DefaultCacheBehavior": {
      "TrustedSigners": {
        "Enabled": false,
        "Quantity": 0
      },
      "LambdaFunctionAssociations": {
        "Quantity": 0
      },
      "TargetOriginId": "{{ AWS.CF.ID }}",
      "ViewerProtocolPolicy": "redirect-to-https",
      "ForwardedValues": {
        "Headers": {
          "Quantity": 0
        },
        "Cookies": {
          "Forward": "all"
        },
        "QueryStringCacheKeys": {
          "Quantity": 0
        },
        "QueryString": true
      },
      "MaxTTL": 604800,
      "SmoothStreaming": false,
      "DefaultTTL": 604800,
      "AllowedMethods": {
        "Items": [
          "HEAD",
          "GET"
        ],
        "CachedMethods": {
          "Items": [
            "HEAD",
            "GET"
          ],
          "Quantity": 2
        },
        "Quantity": 2
      },
      "MinTTL": 604800,
      "Compress": false
    },
    "CallerReference": "2017-07-29_06-25-01",
    "ViewerCertificate": {
      "SSLSupportMethod": "sni-only",
      "ACMCertificateArn": "{{ AWS.ACM.ARN }}",
      "MinimumProtocolVersion": "TLSv1",
      "Certificate": "{{ AWS.ACM.ARN }}",
      "CertificateSource": "acm"
    },
    "CustomErrorResponses": {
      "Quantity": 0
    },
    "HttpVersion": "http2",
    "Restrictions": {
      "GeoRestriction": {
        "RestrictionType": "none",
        "Quantity": 0
      }
    },
    "Aliases": {
      "Items": [
        "{{ DOMAIN.MAIN.NAME }}"
      ],
      "Quantity": 1
    }
  },
  "Id": "{{ AWS.CF.DISTRIBUTION.ID }}",
  "IfMatch": "{{ AWS.CF.DISTRIBUTION.E_TAG }}"
}

playbook

create-aws-acm-request.yml
- hosts: cliservers
  roles:
    - create-aws-acm-request
  vars_files:
    - vars/all.yml
update-aws-cf.yml
- hosts: cliservers
  roles:
    - update-aws-cf
  vars_files:
    - vars/all.yml
view-aws-acm-notice-mail-url.yml
- hosts: cliservers
  roles:
    - view-aws-acm-notice-mail-url
  vars_files:
    - vars/all.yml

tasks

role/create-aws-acm-request/tasks/main.yml
- name: "Create Certificate"
  shell: |
    aws acm request-certificate 
    --domain-name {{ DOMAIN.MAIN.NAME }} 
    --subject-alternative-names {{ DOMAIN.SUB.NAME }} 
    --domain-validation-options DomainName={{ DOMAIN.SUB.NAME }},ValidationDomain={{ DOMAIN.MAIN.NAME }} 
    --region={{ AWS.ACM.REGION }} 
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/view-aws-acm-notice-mail-url/tasks/main.yml
- name: Download Verification Mail Object
  shell: |
    aws s3 cp 
    s3://${AWS.S3.BUCKET.NAME}/{{ AWS.ACM.VERIFICATION_MAIL_OBJECT }} 
    {{ TEMP.DIRECTORY }}.{{ AWS.ACM.VERIFICATION_MAIL_OBJECT }}
  register: result
  changed_when: False

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

- name: View URL
  shell: |
    grep https {{ TEMP.DIRECTORY }}.{{ AWS.ACM.VERIFICATION_MAIL_OBJECT }} | grep context
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/update-aws-cf/tasks/main.yml
- name: Create Json
  template: 
    src={{ ENV }}/cf/update.j2
    dest={{ TEMP.DIRECTORY }}/update.json
  tags:
    - always

- name: Update Distribution
  shell: |
    aws cloudfront update-distribution 
    --cli-input-json file://files/{{ ENV }}/cf/update.json
  register: result
  changed_when: False

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

終わりに

これで今までhttpでアクセスしてたS3バケット(CloudFront経由)にアクセスすると、httpsにリダイレクトされた上で正常に表示されると思います。

AWS+CloudFrount+ACM+S3を使えばほぼ無料でSSL証明証の発行や紐付けが出来、自動更新の設定や複数ドメインのSSL証明書をまとめて管理することが出来ます。

自分は以下のようなWordpressプラグインとAWSサービスで静的サイトを運用していますが、今のところ問題なく運用出来ています。

Wordpress+staticpress+staticpresss3+S3+CloudFront+ACM

Wordpressを静的サイト化することで一部機能が制限されてしまいますが、セキュリティ観点がかなり向上します。

また、制限される部分も特に不要な機能だと思いますし、JavaScript等である程度保管することも可能なので動的サイトを切り捨てて静的サイトにするメリットは十分あると思いますので、興味のある方は是非お試し下さい♪

じゃあの。

続きを読む

aws-sdk for Rubyを使ってCloudFrontのinvalidationをリクエストする

S3にjsonをあげて、取得するときはCloudFront経由にする構成を作っています。
jsonを更新/削除する際にCloudFrontのキャッシュを削除したかったのですが、余り記事が引っかからなかったので備忘までにメモしておきます。

前提

  • Ruby 2.3.0
  • aws-sdk 2.10.22

※ sdkのversionが1の場合は、Clientのクラスやapiのパラメータが異なりますので、こちらをご覧ください。

コード

Railsアプリケーションで削除するclassを作ったので、そのまま貼ります。

lib/clients/cloud_front.rb
module Clients
class CloudFront
  include Singleton

  @@instance = Aws::CloudFront::Client.new(
    region: ENV['AWS_DEFAULT_REGION'],
    access_key_id: ENV['AWS_ACCESS_KEY_ID'],
    secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']
  )

  # CloudFrontのInvalidationを作成する
  # @see http://docs.aws.amazon.com/sdkforruby/api/Aws/CloudFront/Client.html#create_invalidation-instance_method
  #
  # @param [Array[String]] paths キャッシュオブジェクトのパスの配列
  # @return [String] 結果のメッセージ
  def self.create_invalidation(paths=[])
    # 非同期のAPIをcall
    ret = @@instance.create_invalidation({
      distribution_id: ENV['AWS_CLOUD_FRONT_DISTRIBUTION_ID'],
      invalidation_batch: {
        paths: {
          quantity: paths.size,
          items: paths
        },
        # api callを同定するためのユニークな文字列
        # @see http://docs.aws.amazon.com/cloudfront/latest/APIReference/API_CreateInvalidation.html#API_CreateInvalidation_RequestSyntax
        caller_reference: Time.now.to_s
      }
    })

    # invalidationの作成に成功したときは、
    # レスポンスのinvalidation.statusにInProgressが入る
    if ret.blank? or ret.invalidation.blank? or ret.invalidation.status != 'InProgress'
      Rails.logger.warn "Fail to delete cache of #{paths.join(', ')}"
      Rails.logger.warn "Response from CloudFront: #{ret.inspect}"
      return 'キャッシュの削除に失敗しました。'
    end

    'キャッシュの削除を開始しました。しばらく待って確認してください。'
  end

end
end

補足1: メソッドのレスポンスについて

invalidationのcallは元々非同期ですし、削除に失敗したからと言ってアプリケーションを止めたくなかったので、私の場合はメッセージを返してコントローラーでflashに入れるようにしています。場合によっては例外出してもいいかもしれません。

補足2: メソッドの引数について

引数経由で渡しているオブジェクトのpathですが、同じversionのS3クライアント(Aws::S3::Resource)やWeb consoleとは異なり、冒頭の’/’が必須になるのでご注意ください。

  • NG

    • v1/hoge.json
  • OK
    • /v1/hoge.json

続きを読む

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

続きを読む

ETLをサーバレス化するAWS Glueについての概要

AWS Glueが一般人でも使えるようになったというので、どんなものかと、調べてみました。
一人で調べ物した結果なので、機能を正しく把握できているかいまいち自信がありませんが、
理解した限りで公開します。全体像を理解できるような、概念的な話が中心です。

概要

AWS Glueは、日々行われるデータ集約やETL処理を自動化、およびサーバレス化するサービスです。

いま、未加工のCSVやJSONによるログデータや、
アプリケーションで使用している既存のデータベースなどがあるものの、
そのままでは分析が難しく、データ分析のために整備された領域が求められているとします。

AWS Glueの文脈では、前者をデータストア、後者をデータカタログと位置づけます。
データカタログは主に、フルマネージドなHDFS上のストレージ領域です。
たとえば、Amazon Athenaからデータカタログを分析することができます。

AWS Glueは以下の3要素からなります。

  • データ分析の中央リポジトリでありデータを一元管理するデータカタログ
  • 様々なデータストアからデータカタログにデータを集約するクローラ
  • データカタログ内のデータをETLするジョブ

AWS Glueによって、データ分析基盤のサーバレス化を進めることができます。たとえば、 (Customer's Application)-> S3 -(Glue Crawler)-> Data Catalog -> (Athena) は、データ収集から分析・可視化までをエンドツーエンドでサーバレスに構築する一例です。ここで、データの加工が必要であるならば、Data Catalog -(Glue Job)-> Data Catalogを加えればよいでしょう。

AWS Glueはフルマネージドであり、その処理はスケールアウトするため、ユーザはデータ規模やインフラ運用を意識することなく、データを加工するスクリプト(ETLの”T”に対応)の作成に集中することができます。ほかにも、AWS Glueは、データカタログ上のテーブルメタデータのバージョン管理機能や、クローラでの入力データからのスキーマ自動推論機能、クラシファイアでの検査に基づきスキーマの変更を検知する機能などを備えています。

クローラ

AWS Glueにおけるクローラとは、データストアのデータを、
データカタログに移住させるために使われる機能です。

クローラの目的は、散在する複数のデータストアそれぞれを見張らせ、
最新のデータを発見し、それらのデータをデータカタログへと集約し、データカタログを最新に保つことにあります。

クローラは、クラシファイアという要素を通じて、カラム名変更、型変換などの簡単な変換処理を行ったり、
半構造データをテーブルの形式に整えたり、スキーマの変更を検知できたりします。
クラシファイアは、デフォルトのものを使うことも、自分でカスタマイズすることもできます。

作成されたクローラには、ジョブ実行方法(オンデマンドか、スケジュールベースか、イベントベースか)が定義されています。
たとえば、クローラを定期実行させておくことで、データカタログがデータストアに対しおおむね最新であることが保証されます。

ジョブ

クローラを使って単にデータをデータカタログへと移住させただけでは、
クエリを叩けてもデータが使いにくく、ユーザにとって分析が難しい場合があります。
このとき、より分析に適した形にするために、ETL処理が必要です。

AWS Glueにおけるジョブとは、抽出・変換・ロード(ETL)作業を実行するビジネスロジックです。
ジョブが開始されると、そのジョブに対応するETL処理を行うスクリプトが実行されます。
こちらもクローラと同様に定期実行などの自動化が可能です。

ユーザは、ジョブ作成者として、抽出元(データソース)、およびロード先(データターゲット)を定義します。
ただし、データソースおよびデータターゲットは、どちらもデータカタログ上のデータです。
ユーザは、ジョブ処理環境を調整したり、生成されるスクリプトをビジネスニーズに基づいて編集したりします。

最終的に、Apache Spark API (PySpark) スクリプトが生成されます。
こうして作成されたジョブは、データカタログで管理されます。

参考文献

AWS Glue 概要

クローラ

ジョブ

続きを読む

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

参考

続きを読む

AnsibleでAWS操作 CloudFrontディストリビューション編

AnsibleでAWS操作シリーズ

  1. aws-cliインストール編
  2. EC2インスタンス編
  3. S3バケット編
  4. CloudFrontディストリビューション編

関連記事

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

やりたかったこと

  • CloudFrontディストリビューションの作成
  • S3バケットとの連携
  • GUIを使わずに黒い画面でコマンドを「ッターーン!」してかっこつけたい

やったこと

前提

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

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

作業フロー

1. CloudFrontディストリビューションを新規作成

command
ansible-playbook -i inventory/production create-aws-cf-distribution.yml

ディレクトリ構成


├── ansible.cfg
├── create-aws-cf-distribution.yml
├── files
│   └── production
│       └── cf
│           └── distribution.json
├── inventory
│   └── production
│       └── inventory
├── roles
│   └── create-aws-cf-distribution
│       └── tasks
│           └── main.yml
└── vars
    └── all.yml

Ansible構成ファイル

inventory

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

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

[all:vars]
ENV=production

vars

vars/all.yml
AWS:
  S3:
    BUCKET:
      NAME: ${バケット名}

files

files/production/cf/distribution.json
{
  "Id": "",
  "IfMatch": "",
  "DistributionConfig": {
    "Comment": "For S3 Bucket.",
    "CacheBehaviors": {
      "Quantity": 0
    },
    "IsIPV6Enabled": true,
    "Logging": {
      "Bucket": "",
      "Prefix": "",
      "Enabled": false,
      "IncludeCookies": false
    },
    "WebACLId": "",
    "Origins": {
      "Items": [
        {
          "S3OriginConfig": {
            "OriginAccessIdentity": ""
          },
          "OriginPath": "",
          "CustomHeaders": {
            "Quantity": 0
          },
          "Id": "${一意となるID ※1}",
          "DomainName": "${S3バケットの静的サイトホスティングのエンドポイント}"
        }
      ],
      "Quantity": 1
    },
    "DefaultRootObject": "index.html",
    "PriceClass": "PriceClass_All",
    "Enabled": true,
    "DefaultCacheBehavior": {
      "TrustedSigners": {
        "Enabled": false,
        "Quantity": 0
      },
      "LambdaFunctionAssociations": {
        "Quantity": 0
      },
      "TargetOriginId": "${※1で指定したID}",
      "ViewerProtocolPolicy": "allow-all",
      "ForwardedValues": {
        "Headers": {
          "Quantity": 0
        },
        "Cookies": {
          "Forward": "all"
        },
        "QueryStringCacheKeys": {
          "Quantity": 0
        },
        "QueryString": true
      },
      "MaxTTL": ${最大有効TTL},
      "SmoothStreaming": false,
      "DefaultTTL": ${デフォルトTTL},
      "AllowedMethods": {
        "Items": [
          "HEAD",
          "GET"
        ],
        "CachedMethods": {
          "Items": [
            "HEAD",
            "GET"
          ],
          "Quantity": 2
        },
        "Quantity": 2
      },
      "MinTTL": ${最小TTL},
      "Compress": false
    },
    "CallerReference": "${一意となる文字列}",
    "ViewerCertificate": {
      "CloudFrontDefaultCertificate": true,
      "MinimumProtocolVersion": "SSLv3",
      "CertificateSource": "cloudfront"
    },
    "CustomErrorResponses": {
      "Quantity": 0
    },
    "HttpVersion": "http2",
    "Restrictions": {
      "GeoRestriction": {
        "RestrictionType": "none",
        "Quantity": 0
      }
    },
    "Aliases": {
      "Items": [
        "${割り当てたいドメイン ※Route53にて紐付ける際に利用}"
      ],
      "Quantity": 1
    }
  }
}

playbook

create-aws-cf-distribution
- hosts: cliservers
  roles:
    - create-aws-cf-distribution
  vars_files:
    - vars/all.yml

tasks

role/create-aws-cf-distribution/tasks/main.yml
- name: Create Distribution
  shell: |
    aws cloudfront create-distribution \
    --cli-input-json file://files/{{ ENV }}/cf/distribution.json
  register: result
  changed_when: False

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

終わりに

これで、S3バケットをCloudFront経由で表示することが可能になります。
キャッシュを有効利用出来るので、 表示スピードが上がりストレスフリー なページを構築出来ます。

設定用のJsonが少し複雑なので、本記事のJsonをベースに調べながらカスタマイズしてもらえればなと思います。

ただ、このままだとCloudFrontのTTLが効いているのでオブジェクトを更新しても 即時反映 がされません。
また、URLについても、 xxxx.cloudfront.net のようなドメインになっています。

前者については、AWS LambdaによってオブジェクトがPutされたことをトリガーにInvalidationをかけることで解決可能です。(毎回手動でやるのは 運用コスト がかかるので自動化をお勧めします)

後者については、Route53を設定することで独自ドメインによるアクセスが可能になります。

上記の作業についても、aws-cliからの動作確認は済んでいるので時間があるときに記事にまとめようと思っています♪

じゃあの。

続きを読む

AnsibleでAWS操作 S3バケット編

AnsibleでAWS操作シリーズ

  1. aws-cliインストール編
  2. EC2インスタンス編
  3. S3バケット編
  4. CloudFrontディストリビューション編

関連記事

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

やりたかったこと

  • S3バケットの作成/削除
  • S3バケットへのオブジェクトの転送/削除
  • S3バケットを静的Webサイトホスティング化
  • GUIを使わずに黒い画面でコマンドを「ッターーン!」してかっこつけたい

やったこと

前提

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

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

作業フロー

1. S3バケットを新規作成

command
ansible-playbook -i inventory/production create-aws-s3-bucket.yml

2. S3バケットをWebサイトホスティング化

command
ansible-playbook -i inventory/production setup-aws-s3-bucket.yml

3. オブジェクトを転送

command
ansible-playbook -i inventory/production upload-aws-s3-object.yml

4. オブジェクトの転送確認

command
ansible-playbook -i inventory/production view-aws-s3-object.yml

5. オブジェクトの削除

command
ansible-playbook -i inventory/production delete-aws-s3-object.yml

6. バケットの削除

command
ansible-playbook -i inventory/production delete-aws-s3-bucket.yml

ディレクトリ構成


├── ansible.cfg
├── create-aws-s3-bucket.yml
├── delete-aws-s3-bucket.yml
├── delete-aws-s3-object.yml
├── files
│   └── production
│       └── s3
│           └── sample.txt
├── inventory
│   └── production
│       └── inventory
├── roles
│   ├── create-aws-s3-bucket
│   │   └── tasks
│   │       └── main.yml
│   ├── delete-aws-s3-bucket.yml
│   │   └── tasks
│   │       └── main.yml
│   ├── delete-aws-s3-object.yml
│   │   └── tasks
│   │       └── main.yml
│   ├── setup-aws-s3-bucket.yml
│   │   └── tasks
│   │       └── main.yml
│   ├── upload-aws-s3-bucket.yml
│   │   └── tasks
│   │       └── main.yml
│   └── view-aws-s3-object.yml
│       └── tasks
│           └── main.yml
├── upload-aws-s3-object.yml
├── setup-aws-s3-bucket.yml
├── vars
│   └── all.yml
└── view-aws-s3-object.yml

Ansible構成ファイル

inventory

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

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

[all:vars]
ENV=production

vars

vars/all.yml
AWS:
  S3:
    BUCKET:
      NAME: ${バケット名}

playbook

create-aws-s3-bucket
- hosts: cliservers
  roles:
    - create-aws-s3-bucket
  vars_files:
    - vars/all.yml
delete-aws-s3-bucket
- hosts: cliservers
  roles:
    - delete-aws-s3-bucket
  vars_files:
    - vars/all.yml
delete-aws-s3-object
- hosts: cliservers
  roles:
    - delete-aws-s3-object
  vars_files:
    - vars/all.yml
setup-aws-s3-bucket
- hosts: cliservers
  roles:
    - setup-aws-s3-bucekt
  vars_files:
    - vars/all.yml
upload-aws-s3-object
- hosts: cliservers
  roles:
    - update-aws-s3-bucket
  vars_files:
    - vars/all.yml
view-aws-s3-object
- hosts: cliservers
  roles:
    - view-aws-s3-object
  vars_files:
    - vars/all.yml

tasks

role/create-aws-s3-bucket/tasks/main.yml
- name: Create Bucket
  shell: "aws s3 mb s3://{{ AWS.S3.BUCKET.NAME }}"
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/delete-aws-s3-bucket/tasks/main.yml
- name: "Remove Bucket"
  shell: "aws s3 rb s3://{{ AWS.S3.BUCKET.NAME }}"
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/delete-aws-s3-bucket/tasks/main.yml
- name: "Delete Bucket"
  shell: "aws s3 rb s3://{{ AWS.S3.BUCKET.NAME }}"
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/delete-aws-s3-object/tasks/main.yml
- name: "Delete Object"
  shell: "aws s3 rm s3://{{ AWS.S3.BUCKET.NAME }}/sample.txt"
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/setup-aws-s3-bucket/tasks/main.yml
- name: "Setup Bucket"
  shell: |
    aws s3 website s3://{{ AWS.S3.BUCKET.NAME }} \
    --index-document index.html
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/upload-aws-s3-object/tasks/main.yml
- name: "Upload Object"
  shell: "aws s3 cp files/{{ ENV }}/s3/sample.txt s3://{{ AWS.S3.BUCKET.NAME }}"
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/view-aws-s3-object/tasks/main.yml
- name: "View Objects"
  shell: "aws s3 ls s3://{{ AWS.S3.BUCKET.NAME }}"
  register: result
  changed_when: False

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

終わりに

S3のバケット作成やファイル操作も 簡単 に行うことが可能で、 静的サイト として利用する場合のためのindexドキュメントの指定などのオプションもあります。

また、今回は触れませんでしたが転送したファイル単位で パーミッション の設定も出来るので柔軟性もあります。

また、CloudFrontと組み合わせればキャッシュを有効利用してパフォーマンス向上を図ることも可能です。

S3バケットは 静的コンテンツのホスティング簡易ファイルサーバーWordpressの記事をhtml化 したりとかなり汎用性が高く、 料金もかなり良心的 なのでどんどん利用していきましょう♪

じゃあの。

続きを読む

AWSのAutoScalingでインスタンスを起動する際にコマンドを実行したい

タイトルの通り、AWSのAutoScalingでスケールアウトさせる際に、起動するインスタンス上でコマンドを実行したい場合についてです。
インスタンスを起動する際に、ソースコードを最新にしたいとか、webサーバーのプロセスを立ち上げたいと言う時ありますよね。
以下の前提のもと、ここでは最もシンプルであろうという方法を記載していきます。

前提

  • 既に利用可能なAMIを作成済み
  • AutoScalingグループも作成済み
  • ブラウザ上のマネジメントコンソールから設定

では、AWSのEC2サービスの中の起動設定の作成を行います。
1.AMIの選択、2.インスタンスタイプの選択を済ませ、3.詳細設定まで進みます。

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

ここのユーザーデータの項目にインスタンス起動時に渡してあげたいコマンドを記載すればOKです。
気を付けなければいけない点は

  • 渡されたコマンドはrootユーザーとして実行される
  • インタラクティブな操作をコマンドの実行途中ではさむようなものは実行できない

です。
このユーザーデータのテキストボックス内に下記のような感じで記述してしまえば完了です。

#!/bin/bash
cd /path/to/myproject
sudo -u ec2-user /usr/bin/git pull origin master
sudo -u ec2-user /usr/local/bin/gunicorn myproject.wsgi -D

ユーザーデータは様々な形式で渡せるようです
https://cloudinit.readthedocs.io/en/latest/topics/format.html

続きを読む

AWSのS3のデータを集計する

S3は簡単に言うとAWSのクラウドストレージサービスであり、大量にデータを保存することができます。

・Amazon S3 とは何ですか? http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/Welcome.html

今回S3上のデータを集計してグラフにして表示する必要があったため、それについてのスクリプトについて説明します。

作業環境:
macOS Sierra バージョン10.12.5
Python 2.7.10

boto

まずPythonからAWSにアクセスするためにbotoを使う必要があります。
最新バージョンはboto3なのでboto3をインストール。

$ pip install boto3

boto3をインストールしてPythonにインポートしたあと、以下のようにしてS3にアクセスします。さらに取ってきたいバケットの名前を指定してデータを取ってきます。

import boto3

s3_resource = boto3.resource('s3')
bucket = s3_resource.Bucket('bucket_name')

バケットの中にもたくさんデータがあるので、取ってきたいオブジェクトがある場合はfilterで指定します。全部必要な場合はallで取ってきます。

obj = bucket.objects.filter(Prefix='filter_word')
obj = bucket.objects.all()

オブジェクトのデータをダウンロードします。Filenameはローカルで保存するときの名前です。拡張子はS3に置いてあるファイルと同じにします。

bucket.download_file(Key=obj.key, Filename='file_name')

これでS3のデータがダウンロードできました。あとはファイルオープンし、データの形式に従ってリストや辞書を使って集計していきます。

グラフ化

次に集計したデータをグラフ化します。集計したデータは以下のような形式でテキストファイルとして保存しています。時間ごとに名前1〜6のその時の数値を記録しています。

時間1
名前1:数値1-1
名前2:数値1-2
名前3:数値1-3
名前4:数値1-4
名前5:数値1-5
名前6:数値1-6
時間2
名前1:数値2-1
名前2:数値2-2
名前3:数値2-3
名前4:数値2-4
名前5:数値2-5
名前6:数値2-6
時間3
・・・・・・・・・
・・・・・・・・
・・・・・・・
・・・・・

これをグラフ化するのにPandasとmatplotlibを使います。PandasはPyhtonでデータを扱いやすくしてくれるライブラリです。データフレーム形式にすることで簡単にグラフを作成することができます。matplotlibはグラフを描画するのに使います。どちらもpipでインストールしてきます。

データフレームは直接代入しても作れますが、辞書からも作成できるので辞書を作成してからデータフレーム形式にします。集計データのテキストファイルを開き辞書を作り、キーに名前、値に数値をリストにして入れていきます。入力し終わったらデータフレーム形式にしてグラフを作成します。

import pandas as pd

with open('data.txt')as f:
    line = f.readline()
    while line:
        results = line.rstrip()
        if ':' in results:
            data = results.split(':')
            results_dict[data[0]].append(int(data[1]))
        line=f.readline()

#辞書をデータフレームにする
my_df = pd.DataFrame.from_dict(results_dict)
#グラフの作成
my_df.plot(title='graph_title')

これでグラフが作成できました。最後にmatplotlibを使って表示します。

import matplotlib.pyplot as plt

plt.show()

その他

もっとお洒落なグラフを作成するのにseabornというライブラリが使えます。
公式ページにいろんなグラフと使い方が載っています。
https://seaborn.pydata.org/examples/index.html

続きを読む