funasaki memo

このブログ上の投稿は個人のものであり、所属する企業を代表する投稿ではありません。所属:AWSのSolutions Architect

AWS SDK for JavaScriptを使ってS3にダイレクトでファイルをアップロードする

AWS SDK for JavaScriptがリリースされました。これにより、例えばS3にJavaScriptのファイルを配置して、クライアントサイドでJavaScriptを実行、その中でS3へのファイルアップロードの処理を行えば、別途S3へのアップロードを行うためのWebサーバが必要なくなります。

まず、最初にJavaScriptを使って、S3のバケットに対してlistObjectsしてみます。そのJavaScriptを含むHTMLファイルが以下です。

<head>
<title>Sample</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.0.0-rc1.min.js"></script>
<script type="text/javascript">
<!--
AWS.config.update({accessKeyId: 'ACCESS_KEY_ID', secretAccessKey: 'SECRET_ACCESS_KEY'});
var s3 = new AWS.S3({region: 'ap-northeast-1', maxRetries: 15});
s3.listObjects({Bucket: 'BUCKET_NAME'}, function(error, data) {
  if (error) {
    console.log(error); // an error occurred
  } else {
    console.log(data); // request succeeded
  }
});
// -->
</script>
</body>
</html>

これをWebサーバもしくはS3上に配置します。
この作業だけでは、Access Deniedと表示されてしまうので、さらに該当するバケットのCORS(Cross-Origin Resource Sharing)を設定します。CORSとは、S3とは違う他のドメインのサイトからS3上のリソースにアクセスするときの共有設定のことです。デフォルトではAllowedOriginが*のものに対して、GETメソッドのみ許可されているようです。それを以下のように変更します。

<CORSConfiguration>
    <CORSRule>
        <AllowedHeader>*</AllowedHeader>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <AllowedMethod>POST</AllowedMethod>
        <AllowedMethod>DELETE</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>Authorization</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

※AllowedOriginを、特定のURL(例:http://www.example.com/)に限定すると、より権限を限定できて良いです。

FirefoxのプラグインのFirebugを使って、Console Logを見てみると、以下のようにコンテンツを取得できていることがわかりました。
f:id:kenjifunasaki:20131106131637p:plain

次に本記事のメインとなるS3 Uploadを試してみます。
以下のようなs3upload.htmlを作成しました。

<html>
<head>
<title>Sample</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<input type="file" id="file-chooser" />
<button id="upload-button">Upload to S3</button>
<div id="results"></div>

<script src="https://sdk.amazonaws.com/js/aws-sdk-2.0.0-rc1.min.js"></script>
<script type="text/javascript">
<!--

  AWS.config.update({accessKeyId: 'AKIAXXXXXXXXXXXXXX', secretAccessKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'});
  var bucket = new AWS.S3({params: {Bucket: 'BUCKET-NAME'}});

  var fileChooser = document.getElementById('file-chooser');
  var button = document.getElementById('upload-button');
  var results = document.getElementById('results');
  button.addEventListener('click', function() {
    var file = fileChooser.files[0];
    if (file) {
      results.innerHTML = '';

      var params = {Key: file.name, ContentType: file.type, Body: file};
      bucket.putObject(params, function (err, data) {
        results.innerHTML = err ? 'ERROR!' : 'UPLOADED.';
      });
    } else {
      results.innerHTML = 'Nothing to upload.';
    }
  }, false);
// -->
</script>
</body>
</html>

作成したs3upload.htmlを表示すると以下のような画面が表示されます。
f:id:kenjifunasaki:20131106163200p:plain
適当なテキストファイルを選択してアップロードすると、以下のように表示されます。
f:id:kenjifunasaki:20131106163224p:plain
S3 Management Consoleで確認すると、確かにアップロードされてました!
f:id:kenjifunasaki:20131106163249p:plain

あとは、JavaScript内にAWS Security Credential情報がベタ書きされてしまっているのを、以前書いた記事にあるようにSecurity Token Serviceを使って、一時的なTokenを使うようにすれば、権限を制限させることもできます。

以上ですー。

S3バケット以下の特定のフォルダにのみアクセス可能なIAMユーザをつくる

以下のJSONポリシーをIAMユーザに割り当てることで、examplebucket以下のfolder1にのみアクセス可能でした。

{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:*"],
      "Resource": ["arn:aws:s3:::examplebucket"],
      "Condition":{"StringLike":{"s3:prefix":["folder1/*"] }
       }
    }
  ]
}

詳細はこちらをご覧ください。
http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingIAMPolicies.html

AWS OpsWorksでのカスタムAMIの利用について

AWS OpsWorksで2013年10月現在サポートされているAMIは、Amazon LinuxUbuntu 12.04 LTSベースです。

これらを素のまま使うこともできますし、事前にEC2コンソールで起動して、必要なパッケージをインストールしておいて、それをOpsWorksで使うこともできます。

そこで、実際にOpsWorksでインスタンスを起動するときに、必要となるOpsWorks Agentをどうやってインストールさせているんだろう?と疑問に思っていたのですが、解決しました!

Cloud-initを使ってインストールさせてるんですね。

OpsWorksで起動したインスタンスを、EC2 ConsoleでUser Dataをみたところ、OpsWorks Agentをインストールさせるためのスクリプトが書かれていました。
f:id:kenjifunasaki:20131017154453p:plain

なるほど!これで納得。

逆にCloud-initを使ってOpsWorks Agentをインストール・動作できるのであれば、他のOSのイメージででも技術的には動作可能かもしれないですね。(サポートの有無はまた別の話として。)

詳細はこちら。
http://docs.aws.amazon.com/opsworks/latest/userguide/workinginstances-custom-ami.html

OpsWorksで起動されるインスタンスは複数のレイヤーに属することができる

OpsWorksで作成したインスタンスは複数のレイヤーに属することができます。

例えばStatic Web Serverレイヤーに属するインスタンスを作成した場合、それにMySQLレイヤーも属させることで、1インスタンスで2つのレイヤーの役割をこなすことができます。

ただし、その設定が行えるのはインスタンスがstopの状態にあるときです。

具体的には以下のようなインスタンスのEdit画面で、レイヤーを追加してあげるだけです。
f:id:kenjifunasaki:20131016164816p:plain
これで追加したレイヤーのChefレシピも動作するということですね。

これでより多くの構成をカバーできますね。

ただ、レイヤー間の互換性があるため、非互換のものは同居させることは出来ないようです。以下のように表示され、非互換のものは選択できません。
f:id:kenjifunasaki:20131016165654p:plain

以下はその例です。

  • PHP App Serverレイヤーが互換性のあるレイヤー:custom, db-master, memcached, monitoring-master, and rails-app.
  • MySQLレイヤーが互換性のあるレイヤー:custom, lb, memcached, monitoring-master nodejs-app, php-app, rails-app, and web.
  • HAProxyレイヤーが互換性のあるレイヤー:custom, db-master, and memcached

詳細はこちら。
http://docs.aws.amazon.com/opsworks/latest/userguide/layers.html

aws-cliを使ってAutoScaling Groupを作成してみる。

launch-configurationを作成

$ aws autoscaling create-launch-configuration --launch-configuration-name config1 --image-id ami-39b23d38 --instance-type t1.micro --key-name ap-northeast

作成結果を確認

$ aws autoscaling describe-launch-configurations --output text

        ap-northeast    arn:aws:autoscaling:ap-northeast-1:XXXXXXXXXXXX:launchConfiguration:0ad52a06-34d4-4c70-940c-b4b5a23adbd9:launchConfigurationName/config1        ami-39b23d38            False   config1 2013-08-23T10:44:36.777Z                t1.micro
INSTANCEMONITORING      True
RESPONSEMETADATA        5d980e12-0be1-11e3-a812-5b0041b22e4e

auto-scaling-groupを作成

$ aws autoscaling create-auto-scaling-group --auto-scaling-group-name group1 --launch-configuration-name config1 --min-size 2 --max-size 2 --vpc-zone-identifier subnet-d8de6cb0,subnet-96d96bfe

作成結果の確認

$ aws autoscaling describe-auto-scaling-groups --output text

arn:aws:autoscaling:ap-northeast-1:XXXXXXXXXXXXXX:autoScalingGroup:3cffd102-8a3e-4756-8a20-3343b39ba977:autoScalingGroupName/group1       0       2       group1  300     2       2       subnet-d8de6cb0,subnet-96d96bfe config1 2013-08-23T10:58:55.700Z        EC2
i-02215307      ap-northeast-1c Healthy InService       config1
i-9fe2059a      ap-northeast-1a Healthy InService       config1
RESPONSEMETADATA        45062903-0be3-11e3-84d9-670da55b233a

インスタンス2つが、異なるサブネット(異なるAZ)で起動しているのが分かりますね。

ついでに削除処理もメモとして残しておきます。

$ aws autoscaling update-auto-scaling-group --auto-scaling-group-name group1 --min-size 0 --max-size 0
$ aws autoscaling delete-auto-scaling-group --auto-scaling-group-name group1
$ aws autoscaling delete-launch-configuration --launch-configuration-name config1
以上で削除完了です。AutoScaling groupの削除は、インスタンスが完全にterminateされてからでないと削除はできませんので、ご注意を。

Amazon Linux 2013.03にデフォルトでインストールされているAWS CLIにhelpが入ってない。

https://forums.aws.amazon.com/thread.jspa?messageID=449307
こちらのディスカッションフォーラムにもあるように、Amazon Linux 2013.03にはaws-cliのhelp(manual entry)が入っていないです。

$ aws --version
aws-cli/0.9.3 Python/2.6.8 Linux/3.4.43-43.43.amzn1.x86_64

以下のようにhelpコマンドを実行しても表示されません。

$ aws help
No manual entry for aws

ので、aws-cliを一旦削除して再インストールし直すことにしました。

$ sudo yum remove  aws-cli
$ sudo yum -y update
$ sudo easy_install pip
$ sudo pip install awscli

インストール後、helpを実行すると、

$ aws help
AWS()                                                                    AWS()



NAME
       aws -

DESCRIPTION
       The  AWS  Command Line Interface is a unified tool that provides a con-
       sistent interface for interacting with all parts of AWS.

SYNOPSIS
           aws [options] <service_name> <operation> [parameters]

       Use aws service help for information on a specific service.

OPTIONS
       --debug (boolean)

       Turn on debug logging.

       --endpoint-url (string)

正しく表示されました。よかったよかった。
ちなみに新しく入れたaws-cliのバージョンは

$ aws --version
aws-cli/0.14.1 Python/2.6.8 Linux/3.4.43-43.43.amzn1.x86_64

元々入っていたのが0.9.3だったので、元々のは結構古かったみたいですね。

AWS CLIを使ってTagの値のみを取得する

AWS CLIを使って、以下のように割り当てられたインスタンスのTagの値のみ(今回はWordPress)を取得します。
f:id:kenjifunasaki:20130814155254p:plain
今回はAmazon Linuxを使うので、AWS CLIのインストール方法は割愛します。

# Amazon Linux 2013.03に入っているデフォルトのaws-cliを使いました。バージョンは以下です。

$ aws --version
aws-cli/0.9.3 Python/2.6.8 Linux/3.4.43-43.43.amzn1.x86_64

#バージョンが異なると、オプションの指定が異なる可能性があります。

まず以下の環境変数を設定します。

export AWS_DEFAULT_REGION=ap-northeast-1
export AWS_CREDENTIAL_FILE=/opt/aws/credential-file

credential-fileを作成します。

$ sudo cp /opt/aws/credential-file-path /opt/aws/credential-file
$ sudo vi /opt/aws/credential-file
AWSAccessKeyId=XXXXXXXXXXXXXXXXXX
AWSSecretKey=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

以下コマンドを実行して動作確認。

$ aws ec2 describe-instances
{
    "Reservations": [
        {
            "OwnerId": "000000000000",
            "ReservationId": "r-883ce98b",
            "Groups": [
(以下省略)

ちゃんと出力されることを確認。

$ aws ec2 describe-tags
{
    "ResponseMetadata": {
        "RequestId": "b98ff86b-24e7-4950-9161-c864e11c20a7"
    },
    "Tags": [
        {
            "ResourceType": "snapshot",
            "ResourceId": "snap-6d0d4c4d",
            "Key": "Name",
            "Value": "dev-urandom-1G"
        },
        {
            "ResourceType": "security-group",
            "ResourceId": "sg-958dff94",
            "Key": "aws:cloudformation:stack-name",
            "Value": "WordpressSite"
        },
(以下省略)

通常describe-tagsを実行すると、全部のTagが出てきてしまうので、絞り込みをします。

$ aws ec2 describe-tags --filter '{"name":"resource-id","values":"i-06672203"}'
{
    "ResponseMetadata": {
        "RequestId": "8c60bc2e-16bc-4882-8bfb-de5889eb84c3"
    },
    "Tags": [
        {
            "ResourceType": "instance",
            "ResourceId": "i-06672203",
            "Key": "Name",
            "Value": "WordPress"
        }
    ]
}

これをさらに絞り込むためにjqを使います。jqは入力内容を元に出力を絞り込むためのプログラムです。
まずは、jqをインストール。

$ sudo yum install jq

そして、上記出力をjqを使って絞り込みます。

$aws ec2 describe-tags --filter '{"name":"resource-id","values":"i-06672203"}' | jq '.Tags[] | .Value'
"WordPress"

以上で、WordPressの値のみを抽出することができました!

もちろん、Name以外のTagも取得できますので、いろいろ使いまわせそうですね。