AWS OpsWorksでのカスタムAMIの利用について
AWS OpsWorksで2013年10月現在サポートされているAMIは、Amazon LinuxとUbuntu 12.04 LTSベースです。
これらを素のまま使うこともできますし、事前にEC2コンソールで起動して、必要なパッケージをインストールしておいて、それをOpsWorksで使うこともできます。
そこで、実際にOpsWorksでインスタンスを起動するときに、必要となるOpsWorks Agentをどうやってインストールさせているんだろう?と疑問に思っていたのですが、解決しました!
Cloud-initを使ってインストールさせてるんですね。
OpsWorksで起動したインスタンスを、EC2 ConsoleでUser Dataをみたところ、OpsWorks Agentをインストールさせるためのスクリプトが書かれていました。
なるほど!これで納得。
逆に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画面で、レイヤーを追加してあげるだけです。
これで追加したレイヤーのChefレシピも動作するということですね。
これでより多くの構成をカバーできますね。
ただ、レイヤー間の互換性があるため、非互換のものは同居させることは出来ないようです。以下のように表示され、非互換のものは選択できません。
以下はその例です。
- 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)を取得します。
今回は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も取得できますので、いろいろ使いまわせそうですね。
AWS Security Token Serviceを使ってみる
AWS SDKやAWSコマンドラインツールを使う場合、Security Credentialsが必要になります。Security Credentialsの中にはアクセスキーとシークレットアクセスキーが含まれています。AWSのサービスやリソースをAPIを使って管理するときには、この2つのキーを使って認証します。
この2つのキーをどうやって保管するか?が本記事の目標とするところです。
2つのキーをソースコードやもしくはテキストファイル、DB等に格納して使うこともできますが、これだとキーの置き換えをするのが大変です。そこでAWSから提供されているSecurity Token Serviceというサービスを使って、キーの管理を安全に、かつ効率的に行うことを試してみました。
Security Token Serviceとは?
一時的な、かつ制限された特権を持つAWSアカウント、またはIAMユーザのCredential情報を取得可能にする機能です。Security Token Serviceは、以下の3つのアクションが可能です。
- AssumeRole
- IAM Roleの権限を一時的に取得可能。AssumeRoleを実行する時にはIAMのCredentialを使って呼び出す必要あり。AWS親アカウントでは呼び出せないらしい。
- GetFederationToken
- IAMユーザにfederatedされた権限を一時的に取得可能。リクエスト内でユーザ名とポリシーを指定する。
- GetSessionToken
- AWSアカウントまたはIAMユーザの権限を一時的に取得可能。
AWS SDKでSecurity Token Serviceより権限を取得できる。対応しているSDKは2013/04/16時点では以下。
http://docs.aws.amazon.com/STS/latest/UsingSTS/AccessingSTS.html
AWS SDK for Javaを使って、GetSessionTokenした例が以下。今回は取得した権限でS3のバケットリストを表示させている。
import java.io.IOException; import java.util.List; import com.amazonaws.AmazonWebServiceClient; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.auth.BasicSessionCredentials; import com.amazonaws.auth.PropertiesCredentials; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.Bucket; import com.amazonaws.services.s3.model.ObjectListing; import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient; import com.amazonaws.services.securitytoken.model.Credentials; import com.amazonaws.services.securitytoken.model.GetSessionTokenRequest; import com.amazonaws.services.securitytoken.model.GetSessionTokenResult; public class MySecurityTokenSample { public static void main(String[] args) throws IOException { //STSからトークンを取得するために必要なCredential情報 AWSCredentials credentials = new PropertiesCredentials( MySecurityTokenSample.class.getResourceAsStream("AwsCredentials.properties")); //SecurityTokenServiceClientの設定 AWSSecurityTokenServiceClient sts = new AWSSecurityTokenServiceClient(credentials); GetSessionTokenRequest req = new GetSessionTokenRequest(); GetSessionTokenResult res = sts.getSessionToken(req); Credentials tmpCredentials = res.getCredentials(); String accessKeyId = tmpCredentials.getAccessKeyId(); String secretAccessKeyId = tmpCredentials.getSecretAccessKey(); //取得したアクセスキー、シークレットアクセスキーでS3のバケットリストを表示してみる。 BasicSessionCredentials c = new BasicSessionCredentials(tmpCredentials.getAccessKeyId(), tmpCredentials.getSecretAccessKey(), tmpCredentials.getSessionToken()); AmazonS3Client s3Client = new AmazonS3Client(c); s3Client.setEndpoint("s3-ap-northeast-1.amazonaws.com"); List<Bucket> list = s3Client.listBuckets(); for(Bucket b : list) { System.out.println(b.getName()); } } }
こちらを実行すると、バケット名のリストが表示された。
トークンを取得するためには、やはりSecurity Credentialsの情報が必要。なので、システムの中に1台だけトークン管理サーバなるものを立てて、そこの中では唯一Credentials情報を持つ。他のサーバで一時的にCredentials情報が必要な場合は、このサーバが何かしらの安全な方法で配ってあげる形が良さそう。取得したトークンは、デフォルトで12時間まで有効。その時間が過ぎると無効になる。以下のように実行することで、その時間も変更できる。
getSessionTokenRequest.setDurationSeconds(7200);
上記例では、AWSアカウントの権限を取得していたが、以下のようにgetFederatedTokenを使って指定したIAM Userの権限を取得もできる。
import java.io.IOException; import java.util.List; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.auth.BasicSessionCredentials; import com.amazonaws.auth.PropertiesCredentials; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.Bucket; import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient; import com.amazonaws.services.securitytoken.model.Credentials; import com.amazonaws.services.securitytoken.model.GetFederationTokenRequest; import com.amazonaws.services.securitytoken.model.GetFederationTokenResult; import com.amazonaws.services.securitytoken.model.GetSessionTokenRequest; import com.amazonaws.services.securitytoken.model.GetSessionTokenResult; public class MySecurityTokenSample2 { public static void main(String[] args) throws IOException { //STSからトークンを取得するために必要なCredential情報 AWSCredentials credentials = new PropertiesCredentials( MySecurityTokenSample.class.getResourceAsStream("AwsCredentials.properties")); //SecurityTokenServiceClientの設定 AWSSecurityTokenServiceClient sts = new AWSSecurityTokenServiceClient(credentials); GetFederationTokenRequest req = new GetFederationTokenRequest(); req.setName("TVMUser1"); req.setPolicy("{\"Statement\": [{\"Effect\": \"Allow\",\"Action\": \"s3:*\",\"Resource\": \"*\"}]}"); GetFederationTokenResult res = sts.getFederationToken(req); Credentials tmpCredentials = res.getCredentials(); //取得したアクセスキー、シークレットアクセスキーでS3のバケットリストを表示してみる。 BasicSessionCredentials c = new BasicSessionCredentials(tmpCredentials.getAccessKeyId(), tmpCredentials.getSecretAccessKey(), tmpCredentials.getSessionToken()); AmazonS3Client s3Client = new AmazonS3Client(c); s3Client.setEndpoint("s3-ap-northeast-1.amazonaws.com"); List<Bucket> list = s3Client.listBuckets(); for(Bucket b : list) { System.out.println(b.getName()); } } }
TVMUser1というIAM Userで、S3のみにアクセス可能なポリシーをJSONで書いたものを使ってGetFederationTokenしている。
req.setPolicyでは以下のJSONデータを引数に入れている。
{ "Statement": [ { "Effect": "Allow", "Action": "s3:*", "Resource": "*" } ] }
このように、Security Token Serviceを使えば、一時的なCredentials情報を取得できることがわかった!
リンク
- http://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingTempFederationTokenJava.html
- http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/securitytoken/AWSSecurityTokenServiceClient.html
- http://docs.aws.amazon.com/general/latest/gr/rande.html#sts_region
- http://docs.aws.amazon.com/STS/latest/UsingSTS/STSMgmtConsole.html
DynamoDBでscanを実行してみる
DynamoDBのテーブル名が SampleTable, AttributeがIdとNameのみのテーブルに対してScanを実行した。
AWS SDK for Javaで以下のように実行してみた。
import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.PropertiesCredentials; import com.amazonaws.services.dynamodb.AmazonDynamoDBClient; import com.amazonaws.services.dynamodb.model.AttributeValue; import com.amazonaws.services.dynamodb.model.ComparisonOperator; import com.amazonaws.services.dynamodb.model.Condition; import com.amazonaws.services.dynamodb.model.GetItemRequest; import com.amazonaws.services.dynamodb.model.GetItemResult; import com.amazonaws.services.dynamodb.model.Key; import com.amazonaws.services.dynamodb.model.PutItemRequest; import com.amazonaws.services.dynamodb.model.PutItemResult; import com.amazonaws.services.dynamodb.model.ScanRequest; import com.amazonaws.services.dynamodb.model.ScanResult; public class DynamoDBLoadGenerator { static AmazonDynamoDBClient dynamoDB; public static void main(String[] args) throws IOException { //Credentialsの設定 AWSCredentials credentials = new PropertiesCredentials( DynamoDBSample.class.getResourceAsStream("AwsCredentials.properties")); //リクエストのパラメータの設定 dynamoDB = new AmazonDynamoDBClient(credentials); dynamoDB.setEndpoint("http://dynamodb.ap-northeast-1.amazonaws.com"); String tableName = "SampleTable"; //ScanFilterConditionの設定 Condition scanFilterCondition = new Condition() .withComparisonOperator(ComparisonOperator.LT.toString()) //〇〇よりも小さい .withAttributeValueList(new AttributeValue().withN("500")); //500 Map<String, Condition> conditions = new HashMap<String, Condition>(); conditions.put("Id", scanFilterCondition); ScanRequest scanRequest = new ScanRequest() .withTableName(tableName) .withScanFilter(conditions) .withAttributesToGet(Arrays.asList("Id")); //Idに対してScanFilterを実行 //Scan実行 ScanResult scanResult = dynamoDB.scan(scanRequest); //結果表示 for (Map<String, AttributeValue> item : scanResult.getItems()){ System.out.println(item.get("Id")); } } }
実行結果は以下のように出力される。
{N: 251, } {N: 187, } {N: 154, } {N: 7, } {N: 115, } {N: 286, } {N: 361, } {N: 380, } {N: 117, } {N: 401, } {N: 47, } {N: 184, } {N: 403, } {N: 304, } {N: 156, } {N: 122, } {N: 273, } 以下省略
IDが500以下のデータが出力された。
また今回使用したScanFilterConditionでComparisonOperatorで、BETWEENやBEGIN WITHやCONTAINなどいろいろ指定できる。今回使ったのはLT(おそらくはLess Thanのことだと思う)。これでscanするフィルターをいろいろ設定できそう。
試しにNameが"Book 500"とイコールであるIdをFilterConditionで指定したのが以下。
//ScanFilterConditionの設定 Condition scanFilterCondition = new Condition() .withComparisonOperator(ComparisonOperator.EQ.toString()) .withAttributeValueList(new AttributeValue().withS("Book 500"));
以下はComparisonOperatorのJavaDoc
[http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodb/model/ComparisonOperator.html:title=
http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodb/model/ComparisonOperator.html]