ラベル cloudfront の投稿を表示しています。 すべての投稿を表示
ラベル cloudfront の投稿を表示しています。 すべての投稿を表示

2014年2月5日水曜日

EMRってなんじゃ?(ImpalaでCloudfrontの爆速ログ集計)




EMRがImpalaをサポートするようになりました。ImpalaはClouderaが提供するオープンソースのクエリエンジンで、Hiveより断然速いそうです。

例として、Cloudfrontのログを、タイムスタンプをJSTに直して1時間ごとのアクセス数の集計をしてみます。


ログバケットの確認


まずCloudFrontのログが以下のS3にたまっているとします。
s3://memorycraft-impala-input/cf/logs





EMRクラスタの起動


次に、EMRクラスタを起動します。
EMRのダッシュボードで「Create Cluster」をクリックし、新規クラスタ作成画面を表示します。

Cluster Configuration

Cluster nameに適当なクラスタ名を入力します。また、今回はEMRのログは出力しないのでLoggingのチェックはOFFなんかにしておきます。起動したインスタンスの名前をつける場合は、TagsのNameとしてインスタンス名をつけておきます。



Software Configuration

Hadoopのバージョンとアプリケーションのリストを設定します。
Applications to be installedのリストにはデフォルトでHiveとPigが追加されていますが、
AMI versionは現時点で最新の3.0,2を選ぶち、下のAdditional applicationsのドロップダウンでImpalaが選択できるようになります。
Impalaを選択し、「Configure and add」をクリックして追加します。



Hardware Configuration

ここでVPCやゾーンやインスタンス数を設定します。
ここでは、EC2-Classicで、ゾーンはA、インスタンス数はデフォルトにしてみます。



Security and Access

EC2 key pairで、hadoopインスタンスに接続するときのキーペア名を設定します。
ここが未定義だと、インスタンスに接続できないので注意が必要です。



Bootstrap Actions & Steps

ここでは、起動した後の挙動や起動パラメータなどを定義します。
今回は未設定のままでOKです。
最後に「Create cluster」ボタンをクリックして、クラスタを作成します。





EMRのインスタンスへの接続


起動完了するとクラスタ詳細画面で、Master public DNSに、マスタインスタンスのPublicDNSが表示されます。


そのPublicDNSに対して、先ほど選択した鍵を使ってhadoopユーザーでSSH接続します。
$ ssh -i ~/.ssh/keys/memorycraft.pem hadoop@ec2-54-199-41-72.ap-northeast-1.compute.amazonaws.com
The authenticity of host 'ec2-54-199-41-72.ap-northeast-1.compute.amazonaws.com (54.199.41.72)' can't be established.
RSA key fingerprint is 0f:ac:d5:8d:50:2b:7c:92:6a:ad:74:5e:f3:d1:52:05.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'ec2-54-199-41-72.ap-northeast-1.compute.amazonaws.com,54.199.41.72' (RSA) to the list of known hosts.

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2013.09-release-notes/
13 package(s) needed for security, out of 46 available
Run "sudo yum update" to apply all updates.
--------------------------------------------------------------------------------

Welcome to Amazon Elastic MapReduce running Hadoop and Amazon Linux.

Hadoop is installed in /home/hadoop. Log files are in /mnt/var/log/hadoop. Check
/mnt/var/log/hadoop/steps for diagnosing step failures.

The Hadoop UI can be accessed via the following commands:

  ResourceManager    lynx http://localhost:9026/
  NameNode           lynx http://localhost:9101/

--------------------------------------------------------------------------------
$
接続が完了しました。



S3データのコピー


現状、ImpalaでExternalテーブルにS3のパスを直接指定するとエラーになってしまいました。方法がわからなかったため、S3DistCpでHDFS上の/input/にコピーしてきます。
$ vim ./import.sh
#!/bin/bash

. /home/hadoop/impala/conf/impala.conf

hadoop jar /home/hadoop/share/hadoop/common/lib/EmrS3DistCp-1.0.jar -Dmapreduce.job.reduces=30 --src s3://memorycraft-impala-input/cf/logs/ --dest hdfs://$HADOOP_NAMENODE_HOST:$HADOOP_NAMENODE_PORT/input/ --outputCodec 'none'

$ chmod 755 ./import.sh
$ ./import.sh
14/02/02 20:12:41 INFO s3distcp.S3DistCp: Running with args: -Dmapreduce.job.reduces=30 --src s3://memorycraft-impala-input/cf/logs/ --dest hdfs://172.31.0.95:9000/input/ --outputCodec none
14/02/02 20:12:41 INFO s3distcp.S3DistCp: S3DistCp args: --src s3://memorycraft-impala-input/cf/logs/ --dest hdfs://172.31.0.95:9000/input/ --outputCodec none
14/02/02 20:12:45 INFO s3distcp.S3DistCp: Using output path 'hdfs:/tmp/8f7379de-1ebf-4f31-be50-bd17aa54f2d5/output'
14/02/02 20:12:46 INFO s3distcp.S3DistCp: GET http://169.254.169.254/latest/meta-data/placement/availability-zone result: ap-northeast-1a
14/02/02 20:12:46 INFO s3distcp.S3DistCp: Created AmazonS3Client with conf KeyId XXXXXXXXXXXXXX
〜(中略)〜
  Combine output records=0
  WRONG_LENGTH=0
  WRONG_MAP=0
  WRONG_REDUCE=0
 File Input Format Counters
  Bytes Read=3406
 File Output Format Counters
  Bytes Written=0
14/02/02 20:13:37 INFO s3distcp.S3DistCp: Try to recursively delete hdfs:/tmp/8f7379de-1ebf-4f31-be50-bd17aa54f2d5/tempspace

コピーの実行が終わりました。HDFSの内容を見ると、コピーされているのが分かります。
$ hadoop fs -ls /input/
Found 20 items
-rw-r--r--   1 hadoop supergroup       1728 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.21fcnUK5
-rw-r--r--   1 hadoop supergroup       1726 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.9jiEU8sU
-rw-r--r--   1 hadoop supergroup       1477 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.9pD0UZAy
-rw-r--r--   1 hadoop supergroup       4996 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.CECpH1q4
-rw-r--r--   1 hadoop supergroup       3636 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.DmeAD298
-rw-r--r--   1 hadoop supergroup        979 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.GhmYMPjI
-rw-r--r--   1 hadoop supergroup       1915 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.IvAE5n9h
-rw-r--r--   1 hadoop supergroup       1853 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.K3Crm40n
-rw-r--r--   1 hadoop supergroup       6120 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.KILmQ81g
-rw-r--r--   1 hadoop supergroup        969 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.KdRXIw4s
-rw-r--r--   1 hadoop supergroup       1731 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.MIAnZGc4
.....



Impalaの実行


Impalaはimpala-shellというコマンドで専用のプロンプトを起動して使います。hiveのhiveコマンドと同じようなものです。
$ impala-shell
Starting Impala Shell without Kerberos authentication
Connected to ip-10-146-59-193.ap-northeast-1.compute.internal:21000
Server version: impalad version 1.2.1 RELEASE (build 8c1da7709727f3545974009a4bb677a0004024ec)
Welcome to the Impala shell. Press TAB twice to see a list of available commands.

Copyright (c) 2012 Cloudera, Inc. All rights reserved.

(Shell build version: Impala Shell v1.2.1 (8c1da77) built on Sun Dec  1 20:57:24 PST 2013)
[ip-10-146-59-193.ap-northeast-1.compute.internal:21000] >

まずは、入力テーブルをつくります。ファイルの場所はS3DistCpのコピー先に指定した/input/を指定します。
[ip-10-146-59-193.ap-northeast-1.compute.internal:21000] >
CREATE EXTERNAL TABLE IF NOT EXISTS input (
  cf_date STRING,
  cf_time STRING,
  x_edge_location STRING,
  sc_bytes INT,
  c_ip STRING,
  cs_method STRING,
  cs_host STRING,
  cs_uri_stem STRING,
  sc_status STRING,
  cs_referrer STRING, 
  cs_user_agent STRING,
  cs_uri_query STRING,
  cs_cookie STRING,
  x_edge_result_type STRING,
  x_edge_request_id STRING,
  x_host_header STRING,
  cs_protocol STRING,
  cs_bytes INT
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
LOCATION '/input/';

それでは、クエリを実行してみます。
[ip-10-146-59-193.ap-northeast-1.compute.internal:21000] >
SELECT
  w.jsttime,
  count(mydata)
FROM
  (
    SELECT
      SUBSTR(
        CAST(
          FROM_UTC_TIMESTAMP(
            FROM_UNIXTIME(
              UNIX_TIMESTAMP(CONCAT(cf_date, " ", cf_time), 'yyyy-MM-dd HH:mm:ss')
            ), 'JST'
          ) as STRING
        ), 1, 13
      ) AS jsttime,
      'AAA' AS mydata 
    FROM
      input
    WHERE
      cf_date NOT LIKE '#%'
  ) w
GROUP BY
  w.jsttime
;
Query: select w.jsttime, count(mydata) from (select substr(cast(from_utc_timestamp(from_unixtime(unix_timestamp(concat(cf_date, " ", cf_time), 'yyyy-MM-dd HH:mm:ss')), 'JST') as STRING), 1, 13) as jsttime, 'AAA' as mydata  from input where cf_date not like '#%') w group by w.jsttime
+---------------+---------------+
| jsttime       | count(mydata) |
+---------------+---------------+
| 2014-01-30 16 | 95            |
| 2014-01-30 17 | 33            |
+---------------+---------------+
Returned 2 row(s) in 0.88s
1秒かかりませんでした。



Hiveとの速度比較


これと同じ集計をHiveで行ってみます。
Total MapReduce jobs = 1
Launching Job 1 out of 1
Number of reduce tasks not specified. Estimated from input data size: 1
In order to change the average load for a reducer (in bytes):
  set hive.exec.reducers.bytes.per.reducer=<number>
In order to limit the maximum number of reducers:
  set hive.exec.reducers.max=<number>
In order to set a constant number of reducers:
  set mapred.reduce.tasks=<number>
Starting Job = job_1391362544864_0002, Tracking URL = http://10.132.128.116:9046/proxy/application_1391362544864_0002/
Kill Command = /home/hadoop/bin/hadoop job  -kill job_1391362544864_0002
Hadoop job information for Stage-1: number of mappers: 2; number of reducers: 1
2014-02-02 17:46:18,013 Stage-1 map = 0%,  reduce = 0%
2014-02-02 17:46:29,536 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:30,585 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:31,646 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:32,707 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:33,773 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:34,822 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:35,872 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:36,925 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:37,975 Stage-1 map = 100%,  reduce = 100%, Cumulative CPU 6.85 sec
2014-02-02 17:46:39,034 Stage-1 map = 100%,  reduce = 100%, Cumulative CPU 6.85 sec
2014-02-02 17:46:40,083 Stage-1 map = 100%,  reduce = 100%, Cumulative CPU 6.85 sec
MapReduce Total cumulative CPU time: 6 seconds 850 msec
Ended Job = job_1391362544864_0002
Counters:
MapReduce Jobs Launched:
Job 0: Map: 2  Reduce: 1   Cumulative CPU: 6.85 sec   HDFS Read: 58636 HDFS Write: 34 SUCCESS
Total MapReduce CPU Time Spent: 6 seconds 850 msec
OK
2014-01-30 16 95
2014-01-30 17 33
Time taken: 40.334 seconds, Fetched: 2 row(s)
40秒も掛かりました。いかにImpalaが速いかがわかります。



8億レコードでやってみた


上と全く同じ内容のクエリを使って、8億レコードのCloudFrontログ解析をImpala上で実行してみました。

データコピー(S3→HDFS)

$ . /home/hadoop/impala/conf/impala.conf
$
$ hadoop jar /home/hadoop/share/hadoop/common/lib/EmrS3DistCp-1.0.jar -Dmapreduce.job.reduces=30 --src s3://cloudfront-big-log/logs/cf/ --dest hdfs://$HADOOP_NAMENODE_HOST:$HADOOP_NAMENODE_PORT/input/ --outputCodec 'none'
14/02/03 05:21:10 INFO s3distcp.S3DistCp: Running with args: -Dmapreduce.job.reduces=30 --src s3://cloudfront-big-log/logs/cf/ --dest hdfs://172.31.0.95:9000/input/ --outputCodec none
14/02/03 05:21:10 INFO s3distcp.S3DistCp: S3DistCp args: --src s3://cloudfront-big-log/logs/cf/ --dest hdfs://172.31.0.95:9000/input/ --outputCodec none
14/02/03 05:21:14 INFO s3distcp.S3DistCp: Using output path 'hdfs:/tmp/9dc925c6-0f4e-4043-a19a-f21930f89b6b/output'
14/02/03 05:21:15 INFO s3distcp.S3DistCp: GET http://169.254.169.254/latest/meta-data/placement/availability-zone result: ap-northeast-1a
14/02/03 05:21:15 INFO s3distcp.S3DistCp: Created AmazonS3Client with conf KeyId XXXXXXXXXXXXXXXXXXXXXXXX
14/02/03 05:21:16 INFO s3distcp.S3DistCp: Skipping key 'logs/cf/' because it ends with '/'
14/02/03 05:21:16 INFO s3distcp.FileInfoListing: Opening new file: hdfs:/tmp/9dc925c6-0f4e-4043-a19a-f21930f89b6b/files/1
14/02/03 05:21:57 INFO s3distcp.S3DistCp: Created 1 files to copy 211761 files
14/02/03 05:21:57 INFO s3distcp.S3DistCp: Reducer number: 29
〜(中略)〜
  CPU time spent (ms)=16164160
  Physical memory (bytes) snapshot=18180935680
  Virtual memory (bytes) snapshot=53996851200
  Total committed heap usage (bytes)=14206107648
 Shuffle Errors
  BAD_ID=0
  CONNECTION=0
  IO_ERROR=0
  WRONG_LENGTH=0
  WRONG_MAP=0
  WRONG_REDUCE=0
 File Input Format Counters
  Bytes Read=39560891
 File Output Format Counters
  Bytes Written=0
14/02/03 06:30:50 INFO s3distcp.S3DistCp: Try to recursively delete hdfs:/tmp/9dc925c6-0f4e-4043-a19a-f21930f89b6b/tempspace


入力テーブル作成(input)

$ impala-shell
>
> CREATE EXTERNAL TABLE IF NOT EXISTS input (
>   cf_date STRING,
>   cf_time STRING,
>   x_edge_location STRING,
>   sc_bytes INT,
>   c_ip STRING,
>   cs_method STRING,
>   cs_host STRING,
>   cs_uri_stem STRING,
>   sc_status STRING,
>   cs_referrer STRING,
>   cs_user_agent STRING,
>   cs_uri_query STRING,
>   cs_cookie STRING,
>   x_edge_result_type STRING,
>   x_edge_request_id STRING,
>   x_host_header STRING,
>   cs_protocol STRING,
>   cs_bytes INT
> )
> ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
> LOCATION '/input/';
Query: create EXTERNAL TABLE IF NOT EXISTS input ( cf_date STRING, cf_time STRING, x_edge_location STRING, sc_bytes INT, c_ip STRING, cs_method STRING, cs_host STRING, cs_uri_stem STRING, sc_status STRING, cs_referrer STRING, cs_user_agent STRING, cs_uri_query STRING, cs_cookie STRING, x_edge_result_type STRING, x_edge_request_id STRING, x_host_header STRING, cs_protocol STRING, cs_bytes INT ) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LOCATION '/input/'

Returned 0 row(s) in 266.06s


入力件数

> select count(*) from input;
Query: select count(*) from input

+-----------+
| count(*)  |
+-----------+
| 820545673 |
+-----------+

Returned 1 row(s) in 642.03s


出力テーブル作成(output)

> CREATE EXTERNAL TABLE IF NOT EXISTS output_pv (
>   dth STRING,
>   cnt BIGINT
> )
> ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
> LOCATION '/output_pv/';
Query: create EXTERNAL TABLE IF NOT EXISTS output_pv ( dth STRING, cnt BIGINT ) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LOCATION '/output_pv/'

Returned 0 row(s) in 2.23s


解析(input→output)

> insert into output
> select w.jsttime, count(mydata) from
> (select substr(cast(from_utc_timestamp(from_unixtime(unix_timestamp(concat(cf_date, " ", cf_time), 'yyyy-MM-dd HH:mm:ss')), 'JST') as STRING), 1, 13) as jsttime, 'AAA' as mydata  from input where cf_date not like '#%') w
> group by w.jsttime
> ;
Query: insert into output select w.jsttime, count(mydata) from (select substr(cast(from_utc_timestamp(from_unixtime(unix_timestamp(concat(cf_date, " ", cf_time), 'yyyy-MM-dd HH:mm:ss')), 'JST') as STRING), 1, 13) as jsttime, 'AAA' as mydata  from input where cf_date not like '#%') w group by w.jsttime

Inserted 759 rows in 14940.46s



結果

データのコピー(S3→HDFS)約1時間
テーブル作成(input)約4分
テーブル作成(output)約2秒
解析(input→output)約4時間

想像していたより全然速いです。


ちなみに、特定のURIに絞ってクエリを実行したら15分ほどで終わってしまいました。
> insert into output_pv
> select w.jsttime, count(mydata) from
> (select substr(cast(from_utc_timestamp(from_unixtime(unix_timestamp(concat(cf_date, " ", cf_time), 'yyyy-MM-dd HH:mm:ss')), 'JST') as STRING), 1, 13) as jsttime, 'AAA' as mydata  from input where cf_date not like '#%' and (cs_uri_stem = '/' or cs_uri_stem = '/index.html')) w
> group by w.jsttime
> ;
Query: insert into output_pv select w.jsttime, count(mydata) from (select substr(cast(from_utc_timestamp(from_unixtime(unix_timestamp(concat(cf_date, " ", cf_time), 'yyyy-MM-dd HH:mm:ss')), 'JST') as STRING), 1, 13) as jsttime, 'AAA' as mydata  from input where cf_date not like '#%' and (cs_uri_stem = '/' or cs_uri_stem = '/index.html')) w group by w.jsttime

Inserted 755 rows in 852.34s

今回はHiveで8億レコードは試せませんでしたが、いままでHiveを使用していた感覚からするとImpalaは異常なほどの速さです。
これからEMRでクエリを使用する場合はImpala一択になりそうです。

以上です。



2013年2月12日火曜日

S3ってなんじゃ?(CloudFrontでアクセス制御:Origin Access Identity × 署名付きURL)

CloudFrontでアプリを通してのアクセス以外にはコンテンツを配信させたくないという場合があります。
ここでは、CDPデザインパターンに以下のようなパターンがありました。

CDP:Private Cache Distributionパターン


このパターンによると、CloudFrontに対して署名付きURLを送信することで、アクセス条件を制限することができるようです。今回はこれを使ってみたいと思います。



S3バケットの作成


まず、S3バケットをひとつ用意して適当にファイルをアップします。




ちなみにindex.htmlは以下の様な内容です。
<html>
  <head>
    <title>private</title>
  </head>
  <body>
    <h1>This is Private Contents</h1>
  </body>
</html>



CloudFrontディストリビューションの作成


次にCloudFrontでディストリビューションを追加します。「Create new Distribution」をクリックします。
すると、以下のようにディストリビューションの設定画面が表示されるので、以下のように入力します。



Origin Settings


  • Origin Domain Name:先ほど追加したS3バケットを選択
  • Origin ID:自動で設定されるので今回はこのまま
  • Restrict Bucket Access:Yes(YesにするとCloudFrontからしかS3にアクセスできないようにできます)
  • Origin Access Identity:Create a New Identity(S3にアクセスするこのCloudFrontの接続子です)
  • Comment:コメントです
  • Grant Read Permissions on Bucket:Yes(S3に対してCloudFrontからのBucketPolicyを設定します。)

Default Cache Behavior Settings


  • PathPattern:Default
  • View Protocol Policy:HTTP and HTTPS
  • Object Caching:Use Origin Cache Headers(Customizeにすると次の3項目が設定出来ます。)
  • Minimum TTL:0(最短TTL、デフォルト24時間)
  • Forward Cookies:None(キャッシュURLにCookieを含めることができます)
  • Whitelist Cookies:Forward Cookiesを有効にすると、Whitelist内のCookieだけをオリジンに転送することができます。
  • Forward Query Strings:No(YesにするとQueryParameter付きURLでキャッシュできます)
  • Restrict View Access:Yes(署名付きURLでしかアクセスできないようにします)
  • Trusted Signers:Self (Specify Accoutsにチェックを入れると別アカウントの署名も有効になります。)




Distribution Settings


ここの項目はひとまずデフォルトのままにしておきます。



ここまで設定できたら「Create Distribution」をクリックします。
すると以下のように、CloudFrontをprivateアクセスするために必要な次の手順が表示されます。


ざっくり訳すと以下のような内容です。

Step1:S3バケットへのアクセス制限

  •  ディストリビューションを新規作成したままS3の設定をいじっていないのであれば、S3バケットはCloudFrontとバケットオーナーからしかアクセスを受け付けないように正しく設定されています。

Step2:署名付きURL

  •  信頼された署名者用にCloudFrontのキーペアを作成します。
  •  署名付きURLを作成するには、コーディングするかサードパーティツールを使用する必要があります。
  •  ディストリビューションへ信頼された署名者を追加します。

どうやら自動でS3のパーミッションが変更されたようです。
S3のパーミッションを見てみます。
S3のバケットのPermissionsからEdit Bucket Policyをクリックします。
すると以下のように設定されています。

{
 "Version": "2008-10-17",
 "Id": "PolicyForCloudFrontPrivateContent",
 "Statement": [
  {
   "Sid": "1",
   "Effect": "Allow",
   "Principal": {
    "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E366VCXL4Q6CB9"
   },
   "Action": "s3:GetObject",
   "Resource": "arn:aws:s3:::memorycraft-private/*"
  }
 ]
}

Cloud Front Origin Access Identityとして設定されています。


また、作成されたディストリビューションを確認すると、以下のように無事登録されているのがわかります。
ディストリビューションのホスト名は
dapubd7a26puj.cloudfront.net
となっています。



この段階でブラウザでアクセスすると以下のようになります。


これは、すでに署名付きURLしか受け付けないため、通常のURLではエラーになるためです。



署名付きURLの作成


次に署名付きURLを作成してみたいと思います。

まず、署名をするためのCloudFront用のキーペアを取得します。
AWSの証明書のページで「一対の鍵」のタブをクリックします。


CloudFrontの一対の鍵という項目があるので、「新しい一対の鍵を作成する」をクリックします。


作成完了のモーダルウィンドウが表示されるので、確認して閉じます。


すると、「一対の鍵」にCloudFront用のキーペアが追加されているのがわかります。
「一対の鍵ID」というキーペアIDが表示され、下にダウンロードリンクが現れます。


「(公開鍵をダウンロード)」をクリックすると、rsa-キーペアID.pem, pk-キーペアID.pemというファイルがダウンロードされます。


どこか適当な環境にSDKをダウンロードします。
ここではPHPのSDKを利用します。
また、keysというディレクトリを作りpk-キーペアID.pemを配置します。
$tree -L 2 .

.
├── app
│   ├── key.php
│   ├── sdk -> sdk-1.6.0
│   ├── sdk-1.6.0
│   └── sdk-latest.zip
└── keys
    └── pk-キーペアID.pem


そして、以下のようにAmazonCloudFrontクラスを使って署名付きURLを生成します。
cat key.php

<?php
require_once('sdk/sdk.class.php');
date_default_timezone_set('Asia/Tokyo');

//CloudFrontクラスを初期化
$cf = new AmazonCloudFront(array('key'=>'通常のAWSアクセスキー', 'secret'=>'通常のAWSシークレットキー'));

//キーペアIDをセット
$cf->set_keypair_id('CloudFrontのキーペアID');

//配置した鍵ファイルの中身をセット
$cf->set_private_key(file_get_contents(dirname(__FILE__).'/../keys/pk-キーペアID.pem'));

//index.htmlのURLを期限付きで取得します(expireはstrtotimeが解釈できる文字列かUnixTimeならOK)
$url = $cf->get_private_object_url('dapubd7a26puj.cloudfront.net', 'index.html', strtotime('+5 minutes'));
echo $url;


確認


これを実行します。
# php app/key.php

http://dapubd7a26puj.cloudfront.net/index.html?Expires=1360671363&Key-Pair-Id=APKAIGNAXKEEXGUYEZ5A&Signature=UQantZ1gIp5-mm6d6Lb-HKQr1jxKDRW2NfFyC-E2dDx33ekBbFjLKmO6vHCOhv7liBrfcaTFc7~yKZCd4P1tfZE4TltU6TilhnAUFT6mCC-Db-dmnrU7XbcWSjt29~yOVVhLTv5RoPtIjoW~iPFR2BTyoM~4vlV40y9ypbO5M1U_


URLが出力されました。
これをつかってアクセスしてみます。




表示されました!

先ほどのコードでは5分後がアクセス期限でした。
5分以上経過してからアクセスすると、、、



ちゃんとアクセス拒否になりました。


おまけ


ユーザーがアクセスするごとにURLの期限を設定したい場合があります。
その場合は、以下のように元のURLにクエリーパラメータを付与してあげるといいかもしれません。
$url = $cf->get_private_object_url('dapubd7a26puj.cloudfront.net', 'index.html?u='.session_id(), strtotime('+5 minutes'));


以上の手順の多くはAWSのAPI経由でも設定可能です。必要に応じてプログラムで設定てもよいかもしれません。
また、署名の仕方や署名ポリシーにはいくつかパターンがあるため、機会があればもっといろいろな方法を試してみたいと思います。

2012年2月6日月曜日

S3ってなんじゃ?(CloudFrontでストリーミングログを取得)

CloudFrontでストリーミングをするときに、デフォルトで試聴ログが取れるので、試してみました。


ストリーミングの設定

まずストリーミング用とログ用のにバケットを2つ用意します。
  • stream.memorycraft.jp:CDNストリーミングとプレイヤーのWEBホスト用のバケット
  • log.stream.memorycraft.jp:ストリームログ用のバケット



CDN用のバケットではプレイヤーのページをWEBホスティングするので、「Website」の設定をしておきます。




次にストリーミング用のバケットにプレイヤーとムービーなどをアップします。
今回は以下のWEBプレイヤーを使用しました。

JWPlayer
http://www.longtailvideo.com/players/jw-flv-player/

このフリープレイヤーにはムービーのサンプルとプレイヤーを埋め込むためのHTMLのテンプレートが付属しているので、以下のように変更してCDN用のバケットにアップします。
また今回はユーザーを特定するために、rtmpアクセスにuser_idのクエリをつけて見ました。
変更部分は赤字で示しています。

<!--     THIS IS A BASIC HTML FILE TO PLAY FLV's USING JW PLAYER
The following code is from longtailvideo.com's 'Setup Wizard', found at http://www.longtailvideo.com/support/jw-player-setup-wizard
-->
<HTML>
<HEAD>
<TITLE> 
Streaming Video with JW Player
</TITLE> 
</HEAD>
<BODY>
<!-- Put a header above your video, if you like
-->
<H1>This is my header</H1>
<!--This is where you call on the required JavaScript file. You uploaded this in step 2a; you can recognize it by the .js suffix
-->
<script type='text/javascript' src='http://s3-ap-northeast-1.amazonaws.com/stream.memorycraft.jp/swfobject.js'></script> 
<div id='mediaspace'>This text will be replaced</div>
<!--Where it says YOUR_BUCKET/player.swf is where you specify the player that you uploaded in step 2b
-->
<script type='text/javascript'>
var so = new SWFObject('http://s3-ap-northeast-1.amazonaws.com/stream.memorycraft.jp/player.swf','mpl','470','290','9');
   so.addParam('allowfullscreen','true');
   so.addParam('allowscriptaccess','always');
   so.addParam('wmode','opaque');
<!--This next line is where you specify your video file (uploaded in step 2c). For .flv files, you must write it as below ('YOUR_VIDEO_FILE'),  *without*  the .flv extension.
-->
   so.addVariable('file','mp4:mymovie');
<!--This next line is where you point to your streaming distribution on cloudfront (done in step 3 above). NOTE: you *MUST* add "/cfx/st/" for it to work. This specifies it is a cloudfront object (cfx) and that it is to be streamed (st). This is the URL of the streaming distribution--it ends with .cloudfront.net. In the example above, I would write: 'rtmp:// s1jng015tymskd.cloudfront.net/cfx/st/'
--> 
so.addVariable('streamer','rtmp://s23sut0ft0hkv9.cloudfront.net/cfx/st/?user_id=5678');
   so.write('mediaspace');
</script>
</BODY>
</HTML>
<!--Common problems:
- You cannot have any spaces in any of your URL's (including your 'rtmp://...'  URL)    (i.e.,  http:// thereisaspaceatthebeginninghere.xxx)
- Be sure you are calling the correctly numbered version of the flowplayer objects/players (i.e. flowplayer-3.2.2.swf
- You cannot have duplicates of 's3.amazonaws.com' or 'cloudfront.net' in the same address (i.e. (http://s3.amazonaws.com/s3.amazonaws.com/YOUR_BUCKET/player.swf)
- There is a different naming protocol for mp4 vs flv files. For .mp4 files, YOU MUST write it as 'mp4:YOUR_VIDEO_FILE_WITHOUT_THE _MP4_SUFFIX'. For .flv files, you simply write the name of the file, 'YOUR_VIDEO_FILE_WITHOUT_THE_FLV_SUFFIX'.
-->

バケットにアップされるファイルはこのようになります。



次にCDNの設定をします。CloudFrontタブで「Create Distribution」を選択し、最初のDISTRIBUTION TYPEのフェーズで「Delivery Method」にStreamingを選択します。またBucketには先程ムービー一式をアップしたCDN用のバケットを指定します。



次のDISTRIBUTION DETAILSフェーズでは「Logging」の項目をOnに設定して、「Select Log Bucket」から先ほどのログ用のバケットを指定します。また、ここでは「Log Prefix」にstreamと入力して作成を終了します。



それでは、S3ホスティングされたプレイヤーのページを表示してみます。


おお、表示されました!



ログの確認

しばらく(数時間)すると、ログ用のバケットにストリーミングのログが吐き出され始めます。



これらの一つをダウンロードして開いてみると以下のようになっています。いわゆるFMSのログです。


#Version: 1.0
#Fields: date time x-edge-location c-ip x-event sc-bytes x-cf-status x-cf-client-id cs-uri-stem cs-uri-query c-referrer x-page-url c-user-agent x-sname x-sname-query x-file-ext x-sid
2012-02-02 18:12:00 SFO4 219.117.233.241 connect 3073 OK 66f18822-d17a-4e1d-9319-e279ffe82caa rtmp://s23sut0ft0hkv9.cloudfront.net/cfx/st/ user_id=5678 http://s3-ap-northeast-1.amazonaws.com/stream.memorycraft.jp/player.swf http://s3-ap-northeast-1.amazonaws.com/stream.memorycraft.jp/player.html MAC%2011,2,202,183 - - - -
2012-02-02 18:12:00 SFO4 219.117.233.241 play 3476 OK 66f18822-d17a-4e1d-9319-e279ffe82caa rtmp://s23sut0ft0hkv9.cloudfront.net/cfx/st/ user_id=5678 http://s3-ap-northeast-1.amazonaws.com/stream.memorycraft.jp/player.swf http://s3-ap-northeast-1.amazonaws.com/stream.memorycraft.jp/player.html MAC%2011,2,202,183 mymovie - mp4 1
2012-02-02 18:12:01 SFO4 219.117.233.241 stop 205127 OK 66f18822-d17a-4e1d-9319-e279ffe82caa rtmp://s23sut0ft0hkv9.cloudfront.net/cfx/st/ user_id=5678 http://s3-ap-northeast-1.amazonaws.com/stream.memorycraft.jp/player.swf http://s3-ap-northeast-1.amazonaws.com/stream.memorycraft.jp/player.html MAC%2011,2,202,183 mymovie - mp4 1
2012-02-02 18:12:04 SFO4 219.117.233.241 pause 205295 OK 66f18822-d17a-4e1d-9319-e279ffe82caa rtmp://s23sut0ft0hkv9.cloudfront.net/cfx/st/ user_id=5678 http://s3-ap-northeast-1.amazonaws.com/stream.memorycraft.jp/player.swf http://s3-ap-northeast-1.amazonaws.com/stream.memorycraft.jp/player.html MAC%2011,2,202,183 mymovie - mp4 -
2012-02-02 18:12:18 SFO4 219.117.233.241 unpause 205295 OK 66f18822-d17a-4e1d-9319-e279ffe82caa rtmp://s23sut0ft0hkv9.cloudfront.net/cfx/st/ user_id=5678 http://s3-ap-northeast-1.amazonaws.com/stream.memorycraft.jp/player.swf http://s3-ap-northeast-1.amazonaws.com/stream.memorycraft.jp/player.html MAC%2011,2,202,183 mymovie - mp4 -
2012-02-02 18:12:21 SFO4 219.117.233.241 disconnect 205295 OK 66f18822-d17a-4e1d-9319-e279ffe82caa rtmp://s23sut0ft0hkv9.cloudfront.net/cfx/st/ user_id=5678 http://s3-ap-northeast-1.amazonaws.com/stream.memorycraft.jp/player.swf http://s3-ap-northeast-1.amazonaws.com/stream.memorycraft.jp/player.html MAC%2011,2,202,183 - - - -


これらの項目は以下のようになっています。

date日付
time時刻
x-edge-locationCDNのエッジロケーションを空港名のような3文字コード+数字で示しています。
c-ipクライアントのIP
x-eventストリームのイベントです。
Connect, Disconnect, Play, Stop, Pause, Unpause, Seekがあります。
sc-bytesサーバーから送られたバイト数
x-cf-statusイベントのステータス
x-cf-client-id
コネクションに対して一意のクライアントID
cs-uri-stemストリームURIのベース部分
cs-uri-queryストリームURIのクエリ部分
c-referrerストリーミングアクセスのリファラ
x-page-urlストリーミングアクセス元のWEBページのURL
c-user-agentユーザーエージェント
x-snameストリーミングされるファイル名
x-sname-queryストリーミングアクセスのクエリ
x-file-extストリーミングされるファイルの拡張子
x-sidコネクション内で一意なストリームID

ここで、x-eventでストリームの状態(Play, Stop, Pause, Unpauseなど)と、x-sname-queryでユーザーの特定やその他のデータを送信できることで、工夫すれば試聴解析ができそうです。

とりあえず今回は以上です。