funasaki memo

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

AWS CodeBuildで internet connectivity error になる件について

AWS CodeBuildで毎回Provisioningでfailedになり、エラーメッセージを見ると"Build does not have internet connectivity. Please check subnet network configuration."と表示されていました。

VPCのサブネットはpublic subnetを選択していて、Route tableもInternet gatewayにroutingされていて、Security Groupもすべてのtrafficを許可していたのに、なぜ繋がらないのだろう?と思っていたら、原因が分かりました。

CodeBuildでprovisionされるインスタンスには、Public IPが割り当てられないようです。そのためPrivate Subnetに立てて、NAT GatewayかNATインスタンス経由でインターネットにoutbound通信させないといけないようです。(気づくのに時間かかってしまった。。)

docs.aws.amazon.com

Amazon VPCAWS CodeBuild を使用するには、NAT ゲートウェイまたは NAT インスタンスが必要です。これにより、AWS CodeBuild がパブリックエンドポイントに到達できるようになります (ビルド実行時に CLI コマンドを実行するなど)。AWS CodeBuild は、作成したネットワークインターフェイスに Elastic IP アドレスを割り当てることをサポートしていないため、NAT ゲートウェイまたは NAT インスタンスの代わりにインターネットゲートウェイを使用することはできません。パブリック IP アドレスの自動割り当ては、Amazon EC2 インスタンスによる起動以外で作成されたネットワークインタフェースに対しては Amazon EC2 ではサポートされていません。

Amazon CognitoのGetOpenIdTokenForDeveloperIdentityとGetCredentialsForIdentityを使ってみる。

Amazon CognitoのGetOpenIdTokenForDeveloperIdentityとGetCredentialsForIdentityを使ってみたときのサンプルコードのメモ。
Amazon Cognito側でのIdentity Poolの作成は済んでいる前提。

getOpenIdTokenForDeveloperIdentity

AWSCredentials credentials = new BasicAWSCredentials("XXXX", "XXXXX");
AmazonCognitoIdentityClient client = new AmazonCognitoIdentityClient(credentials);
client.setEndpoint("cognito-identity.ap-northeast-1.amazonaws.com");
		
GetOpenIdTokenForDeveloperIdentityRequest getOpenIdTokenForDeveloperIdentityRequest = new GetOpenIdTokenForDeveloperIdentityRequest();
getOpenIdTokenForDeveloperIdentityRequest.setIdentityPoolId("ap-northeast-1:XXXX");
Map<String,String> logins = new HashMap<String,String>();
logins.put("login.mycompany.myapp","user1");
getOpenIdTokenForDeveloperIdentityRequest.setLogins(logins);
GetOpenIdTokenForDeveloperIdentityResult getOpenIdTokenForDeveloperIdentityResult = client.getOpenIdTokenForDeveloperIdentity(getOpenIdTokenForDeveloperIdentityRequest);

getCredentialsForIdentity

GetCredentialsForIdentityRequest getCredentialsForIdentityRequest = new GetCredentialsForIdentityRequest();
Map<String,String> map = new HashMap<String,String>();
map.put("cognito-identity.amazonaws.com", getOpenIdTokenForDeveloperIdentityResult.getToken());
getCredentialsForIdentityRequest.setLogins(map);
getCredentialsForIdentityRequest.setIdentityId(getOpenIdTokenForDeveloperIdentityResult.getIdentityId());
		
GetCredentialsForIdentityResult result = client.getCredentialsForIdentity(getCredentialsForIdentityRequest);
Credentials userCredentials = result.getCredentials();
System.out.println("AccessKey: " + userCredentials.getAccessKeyId());
System.out.println("SecretKey: " + userCredentials.getSecretKey());

mod_rewriteを使ってAmazon S3のコンテンツを表示させる

自分で立てたWebサーバから静的コンテンツ(index.html等)を配信していた形を、元々のWebサーバのURLは変えずに、Amazon S3から配信させてみます。(事前にAmazon S3の該当バケットにindex.html等のコンテンツをアップしておきます。)

今回は、Amazon LinuxApache HTTP Serverで試しました。

まずは、Apacheをインストール、起動します。

sudo yum install -y httpd
sudo service httpd start

適当なindex.htmlファイルを/var/www/html以下に作成します。

動作するサーバにアクセスすると

curl http://ec2-xx-xx-xxx-xxx.ap-northeast-1.compute.amazonaws.com
(自分で立てたサーバ上のコンテンツが表示)

次に、/etc/httpd/conf/httpd.confの中身を見て、mod_rewriteがロードされていることを確認します。

LoadModule rewrite_module modules/mod_rewrite.so

続けて、/etc/httpd/conf/httpd.confの中に以下を追記します。

<VirtualHost *:80>
    DocumentRoot /var/www/html
    ServerName ec2-xx-xx-xxx-xxx.ap-northeast-1.compute.amazonaws.com
    RewriteEngine On
    RewriteRule ^/(.*)$ http://xxxxx.s3-website-ap-northeast-1.amazonaws.com/$1 [P,L]
</VirtualHost>

※上記例では、S3のWebサイトホスティングのURLに直接変更させています。

その後、Apacheを再起動して

sudo service httpd restart

同様にアクセスする。

curl http://ec2-xx-xx-xxx-xxx.ap-northeast-1.compute.amazonaws.com
(xxxxx.s3-website-ap-northeast-1.amazonaws.com/index.htmlの内容が表示される)

httpd.confのRewriteRuleで末尾の[P,L]にしておくと、URLはec2-xx-xx-xxx-xxx...のままで、S3の画像を表示させます。末尾を[R,L]にすると、S3のURLにリダイレクトされました。

リダイレクト先のドメイン名は元々のS3のものを使っていますが、これをRoute 53等のDNSでカスタムドメインを登録して、カスタムドメインでS3へアクセスさせることも勿論できます。

この方法を使えば、アプリ側を特に修正を入れずに、URLにマッチした場合にS3の画像を表示させることができそうですね!(勿論rewriteさせる形ではなく、アプリ側を修正させる方が望ましいですけども。)

Amazon LinuxにRedisをインストール

yumを使ってRedisをAmazon Linuxにインストールしてみる。

$ sudo yum --enablerepo=epel install redis

redis起動

$ sudo /etc/init.d/redis start

redisの情報を確認

$ redis-cli -h localhost info
redis_version:2.4.10
redis_git_sha1:00000000
redis_git_dirty:0
arch_bits:64
multiplexing_api:epoll
gcc_version:4.4.6
process_id:17580
uptime_in_seconds:318
uptime_in_days:0
lru_clock:304917
used_cpu_sys:0.28
used_cpu_user:0.08
used_cpu_sys_children:0.00
used_cpu_user_children:0.00
connected_clients:1
connected_slaves:0
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0
used_memory:726192
used_memory_human:709.17K
used_memory_rss:1847296
used_memory_peak:726328
used_memory_peak_human:709.30K
mem_fragmentation_ratio:2.54
mem_allocator:jemalloc-2.2.5
loading:0
aof_enabled:0
changes_since_last_save:0
bgsave_in_progress:0
last_save_time:1387169174
bgrewriteaof_in_progress:0
total_connections_received:2
total_commands_processed:2
expired_keys:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:0
vm_enabled:0
role:slave
master_host:replica1.wfisum.0001.apne1.cache.amazonaws.com
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
master_link_down_since_seconds:1387169493

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